@canva/cli 1.19.0 → 1.21.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 +26 -0
  2. package/README.md +1 -9
  3. package/cli.js +625 -626
  4. package/package.json +2 -3
  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,169 +0,0 @@
1
- import {
2
- Box,
3
- Button,
4
- Grid,
5
- Placeholder,
6
- ProgressBar,
7
- Rows,
8
- Text,
9
- } from "@canva/app-ui-kit";
10
- import { useEffect, useState } from "react";
11
- import { FormattedMessage, useIntl } from "react-intl";
12
- import { useNavigate } from "react-router-dom";
13
- import { cancelImageGenerationJob, getImageGenerationJobStatus } from "src/api";
14
- import { useAppContext } from "src/context";
15
- import { Paths } from "src/routes/paths";
16
-
17
- const INTERVAL_DURATION_IN_MS = 100;
18
- const TOTAL_PROGRESS_PERCENTAGE = 100;
19
- const LOADING_THRESHOLD_IN_SECONDS = 1;
20
-
21
- /**
22
- * Manages loading progress by updating progress at regular intervals.
23
- * @param {number} durationInSeconds - Total duration of the loading process in seconds.
24
- * @param {boolean} loading - Indicates if loading is in progress.
25
- * @param {function} setProgress - Function to set progress.
26
- * @returns {function} Function to clear the interval.
27
- */
28
- const manageLoadingProgress = (
29
- durationInSeconds: number,
30
- loading: boolean,
31
- setProgress: (value: number) => void,
32
- ) => {
33
- let intervalId = 0;
34
- let progress = 0;
35
- const totalSteps = (durationInSeconds * 1000) / INTERVAL_DURATION_IN_MS;
36
-
37
- if (loading) {
38
- intervalId = window.setInterval(() => {
39
- progress += TOTAL_PROGRESS_PERCENTAGE / totalSteps;
40
- // If progress reaches 100%, clear the interval
41
- if (progress >= TOTAL_PROGRESS_PERCENTAGE) {
42
- clearInterval(intervalId);
43
- } else {
44
- setProgress(progress);
45
- }
46
- }, INTERVAL_DURATION_IN_MS);
47
- }
48
-
49
- // Return function to clear the interval
50
- return () => clearInterval(intervalId);
51
- };
52
-
53
- /**
54
- * Renders a component to display loading results.
55
- * @param {Object} props - Props for the component.
56
- * @param {number} props.durationInSeconds - Total duration of the loading process in seconds.
57
- */
58
- export const LoadingResults = ({
59
- durationInSeconds,
60
- }: {
61
- durationInSeconds: number;
62
- }) => {
63
- const navigate = useNavigate();
64
- const [progress, setProgress] = useState(0);
65
- const {
66
- isLoadingImages,
67
- setIsLoadingImages,
68
- jobId,
69
- setJobId,
70
- promptInput,
71
- setGeneratedImages,
72
- setRemainingCredits,
73
- } = useAppContext();
74
-
75
- useEffect(() => {
76
- const clearLoadingProgress = manageLoadingProgress(
77
- durationInSeconds,
78
- isLoadingImages,
79
- setProgress,
80
- );
81
-
82
- const pollJobStatus = async () => {
83
- if (jobId) {
84
- try {
85
- const { images, credits } = await getImageGenerationJobStatus({
86
- jobId,
87
- });
88
- setGeneratedImages(images);
89
- setRemainingCredits(credits);
90
- // Clear the jobId after fetching images
91
- setJobId("");
92
- setIsLoadingImages(false);
93
- } catch (error) {
94
- if (error === "Job not found") {
95
- // job may have been cancelled.
96
- setJobId("");
97
- setIsLoadingImages(false);
98
- navigate(Paths.HOME);
99
- }
100
- }
101
- }
102
- };
103
-
104
- pollJobStatus();
105
-
106
- return () => {
107
- // Cleanup function to clear the interval when the component unmounts.
108
- // This prevents the interval from continuing when a user cancels.
109
- clearLoadingProgress();
110
- };
111
- }, [
112
- durationInSeconds,
113
- isLoadingImages,
114
- setIsLoadingImages,
115
- setProgress,
116
- jobId,
117
- ]);
118
-
119
- const onCancelClick = async () => {
120
- await cancelImageGenerationJob(jobId);
121
- setIsLoadingImages(false);
122
- navigate(Paths.HOME);
123
- };
124
-
125
- const intl = useIntl();
126
-
127
- // Check if loading duration is below the threshold
128
- if (durationInSeconds <= LOADING_THRESHOLD_IN_SECONDS) {
129
- // Return nothing if loading duration is too short to prevent flashing/loading screen from appearing momentarily
130
- return null;
131
- }
132
-
133
- return (
134
- <Rows spacing="2u">
135
- <Box paddingTop="4u">
136
- {/** Wrapping this grid in a box with paddingTop so that the placeholders render at the same point as the generated images. */}
137
- <Grid columns={2} spacing="2u">
138
- {Array.from({ length: 4 }, (_, index) => (
139
- <Placeholder shape="square" key={index} />
140
- ))}
141
- </Grid>
142
- </Box>
143
- <Text size="large" alignment="center">
144
- <FormattedMessage
145
- defaultMessage="Generating “<strong>{value}</strong>”. This may take up to a minute."
146
- description="A message to indicate that the app is generating an image based on the user's prompt, but that this could take some time"
147
- values={{
148
- value: promptInput,
149
- strong: (chunks) => <strong>{chunks}</strong>,
150
- }}
151
- />
152
- </Text>
153
- <ProgressBar
154
- value={Math.min(progress, 100)}
155
- ariaLabel={intl.formatMessage({
156
- defaultMessage: "image generation",
157
- description:
158
- "An aria label for the progress bar for image generation",
159
- })}
160
- />
161
- <Button variant="secondary" onClick={onCancelClick} stretch={true}>
162
- {intl.formatMessage({
163
- defaultMessage: "Cancel",
164
- description: "A button label to stop generating an image",
165
- })}
166
- </Button>
167
- </Rows>
168
- );
169
- };
@@ -1,14 +0,0 @@
1
- import { defineMessages } from "react-intl";
2
-
3
- export const PromptInputMessages = defineMessages({
4
- /** Messages related to prompts and user input validation. */
5
- promptInspireMe: {
6
- defaultMessage: "Inspire me",
7
- description:
8
- "A button label to generate a prompt automatically, which may inspire the user to write their own",
9
- },
10
- promptTryAnother: {
11
- defaultMessage: "Try another",
12
- description: "A button label to try another image generation prompt",
13
- },
14
- });
@@ -1,154 +0,0 @@
1
- import {
2
- Box,
3
- Button,
4
- FormField,
5
- LightBulbIcon,
6
- MultilineInput,
7
- } from "@canva/app-ui-kit";
8
- import { useState } from "react";
9
- import { useIntl } from "react-intl";
10
- import { useLocation } from "react-router-dom";
11
- import { useAppContext } from "src/context";
12
- import { Paths } from "src/routes/paths";
13
- import { PromptInputMessages as Messages } from "./prompt_input.messages";
14
-
15
- // @TODO: Adjust according to your specific requirements.
16
- const MAX_INPUT_LENGTH = 280;
17
- const MIN_INPUT_ROWS = 3;
18
-
19
- /**
20
- * Array of example prompts that could be used to generate interesting pictures with an AI.
21
- * Consider fetching these prompts from a server or API call for dynamic and varied content.
22
- * These would need to be localised, but that is left out here as the method would depend on
23
- * the specific implementation or API used.
24
- */
25
- const examplePrompts: string[] = [
26
- "Cats ruling a parallel universe",
27
- "Futuristic city with friendly robots",
28
- "Magical forest with unicorns and dragons",
29
- "Underwater kingdom with colorful fish and mermaids",
30
- "World with altered gravity and flying people",
31
- "Alien landscape with strange creatures",
32
- "Steampunk adventure on a giant airship",
33
- "Whimsical tea party with talking animals",
34
- "Cyberpunk cityscape with neon lights",
35
- "Post-apocalyptic world reclaimed by nature",
36
- "Magical library where books come to life",
37
- "Space station orbiting a distant planet",
38
- "Time-traveling adventure through historical eras",
39
- "Enchanted garden where flowers sing and dance",
40
- "Fantasy castle floating among clouds",
41
- "Fairytale scene with magical objects",
42
- "Cosmic journey through distant galaxies",
43
- "World where every day is Halloween",
44
- "Futuristic sports arena with cyborgs",
45
- "Scene inspired by a classic myth or legend",
46
- ];
47
-
48
- /**
49
- * Generates a new example prompt different from the current prompt.
50
- * @param {string} currentPrompt - The current prompt.
51
- * @returns {string} A new example prompt different from the current prompt.
52
- */
53
- const generateExamplePrompt = (currentPrompt: string): string => {
54
- let newPrompt = currentPrompt;
55
-
56
- // Prevents generating the same prompt twice in a row.
57
- let attempts = 0;
58
- // Maximum attempts to generate a new prompt. Used as a safeguard against infinite loops.
59
- const MAX_ATTEMPTS = 3;
60
-
61
- while (currentPrompt === newPrompt && attempts < MAX_ATTEMPTS) {
62
- const randomPrompt =
63
- examplePrompts[Math.floor(Math.random() * examplePrompts.length)];
64
- if (randomPrompt) {
65
- newPrompt = randomPrompt;
66
- }
67
- attempts++;
68
- }
69
-
70
- return newPrompt;
71
- };
72
-
73
- export const PromptInput = () => {
74
- const intl = useIntl();
75
- const { pathname } = useLocation();
76
- const isHomeRoute = pathname === Paths.HOME;
77
- const { promptInput, setPromptInput, promptInputError } = useAppContext();
78
- const [showInspireMeButton, setShowInspireMeButton] = useState(true);
79
- const [inspireMeButtonLabel, setInspireMeButtonLabel] = useState(
80
- intl.formatMessage(Messages.promptInspireMe),
81
- );
82
-
83
- const onInspireClick = () => {
84
- setPromptInput(generateExamplePrompt(promptInput));
85
- setInspireMeButtonLabel(intl.formatMessage(Messages.promptTryAnother));
86
- };
87
-
88
- const onPromptInputChange = (value: string) => {
89
- setShowInspireMeButton(false);
90
- setPromptInput(value);
91
- };
92
-
93
- const InspireMeButton = () => {
94
- return (
95
- <Button variant="secondary" icon={LightBulbIcon} onClick={onInspireClick}>
96
- {inspireMeButtonLabel}
97
- </Button>
98
- );
99
- };
100
-
101
- const onClearClick = () => {
102
- setPromptInput("");
103
- setShowInspireMeButton(true);
104
- setInspireMeButtonLabel(intl.formatMessage(Messages.promptInspireMe));
105
- };
106
-
107
- const ClearButton = () => (
108
- <Button variant="tertiary" onClick={onClearClick}>
109
- {intl.formatMessage({
110
- defaultMessage: "Clear",
111
- description:
112
- "A button label to remove all contents of the prompt input field",
113
- })}
114
- </Button>
115
- );
116
-
117
- return (
118
- <FormField
119
- label={intl.formatMessage({
120
- defaultMessage: "Describe what you want to create",
121
- description:
122
- "A label for the input field to describe what the user wants to create",
123
- })}
124
- error={promptInputError}
125
- value={promptInput}
126
- control={(props) => (
127
- <MultilineInput
128
- {...props}
129
- placeholder={intl.formatMessage({
130
- defaultMessage: "Enter 5+ words to describe...",
131
- description:
132
- "A placeholder for the input field where the user can describe what they want the AI image generator to create, encouraging them to use a longer, more descriptive phrase. The number of words is not validated, but a longer text will likely improve the quality of the results. Feel free to translate loosely or idiomatically.",
133
- })}
134
- onChange={onPromptInputChange}
135
- maxLength={MAX_INPUT_LENGTH}
136
- minRows={MIN_INPUT_ROWS}
137
- footer={
138
- <Box
139
- padding="1u"
140
- display="flex"
141
- justifyContent={
142
- isHomeRoute && showInspireMeButton ? "spaceBetween" : "end"
143
- }
144
- >
145
- {isHomeRoute && showInspireMeButton && <InspireMeButton />}
146
- {promptInput && <ClearButton />}
147
- </Box>
148
- }
149
- required={true}
150
- />
151
- )}
152
- />
153
- );
154
- };
@@ -1,84 +0,0 @@
1
- import { Link, Rows, Text, TextPlaceholder } from "@canva/app-ui-kit";
2
- import { getPlatformInfo, requestOpenExternalUrl } from "@canva/platform";
3
- import type { JSX } from "react";
4
- import { FormattedMessage, useIntl } from "react-intl";
5
- import { useAppContext } from "src/context";
6
-
7
- // @TODO: Replace this URL with your custom upselling link.
8
- const PURCHASE_URL = "https://example.com";
9
-
10
- export const RemainingCredits = (): JSX.Element | undefined => {
11
- const { remainingCredits, loadingApp } = useAppContext();
12
- const platformInfo = getPlatformInfo();
13
-
14
- const RemainingCreditsText = () => {
15
- if (loadingApp) {
16
- return <TextPlaceholder size="small" />;
17
- }
18
-
19
- return (
20
- <Text alignment="center" size="small">
21
- {remainingCredits > 0 ? (
22
- <FormattedMessage
23
- defaultMessage="Use <strong>1 of {remainingCredits, number}</strong> {remainingCredits, plural,
24
- one {credit}
25
- other {credits}
26
- }."
27
- description="A message to indicate the number of credits, of their total remaining credits, that will be used when generating an image"
28
- values={{
29
- remainingCredits,
30
- strong: (chunks) => <strong>{chunks}</strong>,
31
- }}
32
- />
33
- ) : (
34
- <FormattedMessage
35
- defaultMessage="No credits remaining."
36
- description="A message to indicate that there are no credits available to be used"
37
- />
38
- )}
39
- </Text>
40
- );
41
- };
42
-
43
- const openExternalUrl = async (url: string) => {
44
- await requestOpenExternalUrl({
45
- url,
46
- });
47
- };
48
-
49
- const intl = useIntl();
50
-
51
- return (
52
- <Rows spacing="0">
53
- <RemainingCreditsText />
54
- <Text alignment="center" size="small">
55
- {platformInfo.canAcceptPayments ? (
56
- <FormattedMessage
57
- defaultMessage="Purchase more credits at <link>example.com</link>."
58
- description="A message to prompt the user to purchase more credits. Do not translate <link>example.com</link>."
59
- values={{
60
- link: (chunks) => (
61
- <Link
62
- href={PURCHASE_URL}
63
- requestOpenExternalUrl={() => openExternalUrl(PURCHASE_URL)}
64
- tooltipLabel={intl.formatMessage({
65
- defaultMessage: "Example Co. website",
66
- description:
67
- "A title for a link to the website of Example Co.",
68
- })}
69
- >
70
- {chunks}
71
- </Link>
72
- ),
73
- }}
74
- />
75
- ) : (
76
- <FormattedMessage
77
- defaultMessage="Open this app in a web browser to learn how to purchase more credits."
78
- description="A message shown when platform doesn't allow external payment links"
79
- />
80
- )}
81
- </Text>
82
- </Rows>
83
- );
84
- };
@@ -1,54 +0,0 @@
1
- import { Box, Column, Columns, FlagIcon, Link, Text } from "@canva/app-ui-kit";
2
- import { requestOpenExternalUrl } from "@canva/platform";
3
- import type { JSX } from "react";
4
- import { FormattedMessage, useIntl } from "react-intl";
5
- import { APP_NAME } from "src/config";
6
-
7
- const REPORT_URL = `https://www.canva.com/help/report-content/?app=${APP_NAME}`;
8
-
9
- /**
10
- * Component to display a box prompting users to report potentially inappropriate content generated by AI.
11
- * Providing users with a way to report inappropriate content is essential for maintaining a safe and respectful environment.
12
- */
13
- export const ReportBox = (): JSX.Element => {
14
- const openExternalUrl = async (url: string) => {
15
- await requestOpenExternalUrl({
16
- url,
17
- });
18
- };
19
-
20
- const intl = useIntl();
21
-
22
- return (
23
- <Box padding="2u" background="neutralLow" borderRadius="standard">
24
- <Columns spacing="1.5u" alignY="center">
25
- <Column width="content">
26
- <FlagIcon />
27
- </Column>
28
- <Column>
29
- <Text alignment="start">
30
- <FormattedMessage
31
- defaultMessage="We’re evolving this new technology with you so please <link>report these images</link> if they don’t seem right."
32
- description="A message to prompt users to report potentially inappropriate content generated by AI"
33
- values={{
34
- link: (chunks) => (
35
- <Link
36
- href={REPORT_URL}
37
- requestOpenExternalUrl={() => openExternalUrl(REPORT_URL)}
38
- tooltipLabel={intl.formatMessage({
39
- defaultMessage: "Report content",
40
- description:
41
- "A title for a link to report AI generated content that is problematic",
42
- })}
43
- >
44
- {chunks}
45
- </Link>
46
- ),
47
- }}
48
- />
49
- </Text>
50
- </Column>
51
- </Columns>
52
- </Box>
53
- );
54
- };
@@ -1,47 +0,0 @@
1
- /* eslint-disable formatjs/no-literal-string-in-jsx */
2
- import { TestAppI18nProvider } from "@canva/app-i18n-kit";
3
- import { TestAppUiProvider } from "@canva/app-ui-kit";
4
- import { getPlatformInfo, requestOpenExternalUrl } from "@canva/platform";
5
- import type { RenderResult } from "@testing-library/react";
6
- import { fireEvent, render } from "@testing-library/react";
7
- import type { ReactNode } from "react";
8
- import { RemainingCredits } from "../remaining_credits";
9
-
10
- function renderInTestProvider(node: ReactNode): RenderResult {
11
- return render(
12
- // In a test environment, you should wrap your apps in `TestAppI18nProvider` and `TestAppUiProvider`, rather than `AppI18nProvider` and `AppUiProvider`
13
- <TestAppI18nProvider>
14
- <TestAppUiProvider>{node}</TestAppUiProvider>,
15
- </TestAppI18nProvider>,
16
- );
17
- }
18
-
19
- // This test demonstrates how to test code that uses functions from the Canva Apps SDK
20
- // For more information on testing with the Canva Apps SDK, see https://www.canva.dev/docs/apps/testing/
21
- describe("Remaining Credit Tests", () => {
22
- const mockRequestOpenExternalUrl = jest.mocked(requestOpenExternalUrl);
23
- const mockGetPlatformInfo = jest.mocked(getPlatformInfo);
24
-
25
- beforeEach(() => {
26
- jest.resetAllMocks();
27
- mockGetPlatformInfo.mockReturnValue({
28
- canAcceptPayments: true,
29
- });
30
- });
31
-
32
- it("should call requestOpenExternalUrl when the link is clicked", () => {
33
- // assert that the mock is in the expected clean state
34
- expect(mockRequestOpenExternalUrl).not.toHaveBeenCalled();
35
-
36
- const result = renderInTestProvider(<RemainingCredits />);
37
-
38
- // get a reference to the link to purchase more credits
39
- const purchaseMoreLink = result.getByRole("button");
40
-
41
- // programmatically simulate clicking the button
42
- fireEvent.click(purchaseMoreLink);
43
-
44
- // we expect that requestOpenExternalUrl has been called
45
- expect(mockRequestOpenExternalUrl).toHaveBeenCalled();
46
- });
47
- });
@@ -1,21 +0,0 @@
1
- /**
2
- * Expected loading time for the progress bar.
3
- * Adjust this value to match the estimated loading time of your Generative AI model.
4
- */
5
- export const EXPECTED_LOADING_TIME_IN_SECONDS = 5;
6
-
7
- /**
8
- * Determines the default number of images generated when using the `generateImages` function.
9
- */
10
- export const NUMBER_OF_IMAGES_TO_GENERATE = 4;
11
-
12
- /**
13
- * Number of seconds to wait between polling for generated images.
14
- */
15
- export const POLLING_INTERVAL_IN_SECONDS = 3;
16
-
17
- /**
18
- * Your app's name. This is used when reporting generated content.
19
- * @TODO: Update your app's name here.
20
- */
21
- export const APP_NAME = "Add your app name here";