@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.
- package/CHANGELOG.md +26 -0
- package/README.md +1 -9
- package/cli.js +625 -626
- package/package.json +2 -3
- package/templates/base/backend/base_backend/create.ts +0 -114
- package/templates/base/backend/database/database.ts +0 -42
- package/templates/base/backend/routers/auth.ts +0 -288
- package/templates/base/declarations/declarations.d.ts +0 -29
- package/templates/base/eslint.config.mjs +0 -14
- package/templates/base/package.json +0 -91
- package/templates/base/scripts/copy_env.ts +0 -13
- package/templates/base/scripts/ssl/ssl.ts +0 -131
- package/templates/base/scripts/start/app_runner.ts +0 -223
- package/templates/base/scripts/start/context.ts +0 -171
- package/templates/base/scripts/start/start.ts +0 -46
- package/templates/base/scripts/start.tests.ts +0 -61
- package/templates/base/styles/components.css +0 -38
- package/templates/base/tsconfig.json +0 -56
- package/templates/base/webpack.config.ts +0 -247
- package/templates/common/.env.template +0 -6
- package/templates/common/.gitignore.template +0 -8
- package/templates/common/.nvmrc +0 -1
- package/templates/common/.prettierrc +0 -21
- package/templates/common/LICENSE.md +0 -48
- package/templates/common/README.md +0 -179
- package/templates/common/jest.config.mjs +0 -35
- package/templates/common/jest.setup.ts +0 -35
- package/templates/content_publisher/README.md +0 -58
- package/templates/content_publisher/canva-app.json +0 -17
- package/templates/content_publisher/declarations/declarations.d.ts +0 -29
- package/templates/content_publisher/eslint.config.mjs +0 -14
- package/templates/content_publisher/package.json +0 -90
- package/templates/content_publisher/scripts/copy_env.ts +0 -13
- package/templates/content_publisher/scripts/ssl/ssl.ts +0 -131
- package/templates/content_publisher/scripts/start/app_runner.ts +0 -223
- package/templates/content_publisher/scripts/start/context.ts +0 -171
- package/templates/content_publisher/scripts/start/start.ts +0 -46
- package/templates/content_publisher/src/index.tsx +0 -4
- package/templates/content_publisher/src/intents/content_publisher/index.tsx +0 -107
- package/templates/content_publisher/src/intents/content_publisher/post_preview.tsx +0 -240
- package/templates/content_publisher/src/intents/content_publisher/preview_ui.tsx +0 -62
- package/templates/content_publisher/src/intents/content_publisher/settings_ui.tsx +0 -81
- package/templates/content_publisher/src/intents/content_publisher/types.ts +0 -29
- package/templates/content_publisher/styles/components.css +0 -38
- package/templates/content_publisher/styles/preview_ui.css +0 -49
- package/templates/content_publisher/tsconfig.json +0 -56
- package/templates/content_publisher/webpack.config.ts +0 -247
- package/templates/dam/backend/routers/dam.ts +0 -108
- package/templates/dam/backend/server.ts +0 -65
- package/templates/dam/canva-app.json +0 -25
- package/templates/dam/declarations/declarations.d.ts +0 -29
- package/templates/dam/eslint.config.mjs +0 -14
- package/templates/dam/package.json +0 -97
- package/templates/dam/scripts/copy_env.ts +0 -13
- package/templates/dam/scripts/ssl/ssl.ts +0 -131
- package/templates/dam/scripts/start/app_runner.ts +0 -223
- package/templates/dam/scripts/start/context.ts +0 -171
- package/templates/dam/scripts/start/start.ts +0 -46
- package/templates/dam/src/index.tsx +0 -4
- package/templates/dam/src/intents/design_editor/adapter.ts +0 -44
- package/templates/dam/src/intents/design_editor/app.tsx +0 -35
- package/templates/dam/src/intents/design_editor/config.ts +0 -220
- package/templates/dam/src/intents/design_editor/index.css +0 -3
- package/templates/dam/src/intents/design_editor/index.tsx +0 -25
- package/templates/dam/tsconfig.json +0 -56
- package/templates/dam/utils/backend/base_backend/create.ts +0 -114
- package/templates/dam/webpack.config.ts +0 -247
- package/templates/data_connector/README.md +0 -84
- package/templates/data_connector/canva-app.json +0 -21
- package/templates/data_connector/declarations/declarations.d.ts +0 -29
- package/templates/data_connector/eslint.config.mjs +0 -14
- package/templates/data_connector/package.json +0 -92
- package/templates/data_connector/scripts/copy_env.ts +0 -13
- package/templates/data_connector/scripts/ssl/ssl.ts +0 -131
- package/templates/data_connector/scripts/start/app_runner.ts +0 -223
- package/templates/data_connector/scripts/start/context.ts +0 -171
- package/templates/data_connector/scripts/start/start.ts +0 -46
- package/templates/data_connector/src/api/connect_client.ts +0 -6
- package/templates/data_connector/src/api/data_source.ts +0 -97
- package/templates/data_connector/src/api/data_sources/designs.tsx +0 -296
- package/templates/data_connector/src/api/data_sources/index.ts +0 -4
- package/templates/data_connector/src/api/data_sources/templates.tsx +0 -328
- package/templates/data_connector/src/api/fetch_data_table.ts +0 -55
- package/templates/data_connector/src/api/index.ts +0 -4
- package/templates/data_connector/src/api/oauth.ts +0 -8
- package/templates/data_connector/src/api/tests/data_source.test.tsx +0 -99
- package/templates/data_connector/src/components/app_error.tsx +0 -15
- package/templates/data_connector/src/components/footer.tsx +0 -26
- package/templates/data_connector/src/components/header.tsx +0 -40
- package/templates/data_connector/src/components/index.ts +0 -3
- package/templates/data_connector/src/components/inputs/messages.tsx +0 -95
- package/templates/data_connector/src/components/inputs/search_filter.tsx +0 -109
- package/templates/data_connector/src/components/inputs/select_field.tsx +0 -26
- package/templates/data_connector/src/context/app_context.tsx +0 -125
- package/templates/data_connector/src/context/index.ts +0 -2
- package/templates/data_connector/src/context/use_app_context.ts +0 -17
- package/templates/data_connector/src/index.tsx +0 -4
- package/templates/data_connector/src/intents/data_connector/app.tsx +0 -20
- package/templates/data_connector/src/intents/data_connector/entrypoint.tsx +0 -70
- package/templates/data_connector/src/intents/data_connector/home.tsx +0 -21
- package/templates/data_connector/src/intents/data_connector/index.tsx +0 -56
- package/templates/data_connector/src/pages/data_source_config.tsx +0 -9
- package/templates/data_connector/src/pages/error.tsx +0 -37
- package/templates/data_connector/src/pages/index.ts +0 -4
- package/templates/data_connector/src/pages/login.tsx +0 -145
- package/templates/data_connector/src/pages/select_source.tsx +0 -24
- package/templates/data_connector/src/routes/index.ts +0 -2
- package/templates/data_connector/src/routes/paths.ts +0 -7
- package/templates/data_connector/src/routes/protected_route.tsx +0 -26
- package/templates/data_connector/src/routes/routes.tsx +0 -42
- package/templates/data_connector/src/utils/data_params.ts +0 -17
- package/templates/data_connector/src/utils/data_table.ts +0 -116
- package/templates/data_connector/src/utils/fetch_result.ts +0 -36
- package/templates/data_connector/src/utils/index.ts +0 -2
- package/templates/data_connector/src/utils/tests/data_table.test.ts +0 -133
- package/templates/data_connector/styles/components.css +0 -38
- package/templates/data_connector/tsconfig.json +0 -56
- package/templates/data_connector/webpack.config.ts +0 -247
- package/templates/gen_ai/README.md +0 -27
- package/templates/gen_ai/backend/routers/image.ts +0 -232
- package/templates/gen_ai/backend/server.ts +0 -65
- package/templates/gen_ai/canva-app.json +0 -25
- package/templates/gen_ai/declarations/declarations.d.ts +0 -29
- package/templates/gen_ai/eslint.config.mjs +0 -14
- package/templates/gen_ai/package.json +0 -101
- package/templates/gen_ai/scripts/copy_env.ts +0 -13
- package/templates/gen_ai/scripts/ssl/ssl.ts +0 -131
- package/templates/gen_ai/scripts/start/app_runner.ts +0 -223
- package/templates/gen_ai/scripts/start/context.ts +0 -171
- package/templates/gen_ai/scripts/start/start.ts +0 -46
- package/templates/gen_ai/src/api/api.ts +0 -194
- package/templates/gen_ai/src/api/index.ts +0 -1
- package/templates/gen_ai/src/components/app_error.tsx +0 -18
- package/templates/gen_ai/src/components/footer.messages.ts +0 -48
- package/templates/gen_ai/src/components/footer.tsx +0 -156
- package/templates/gen_ai/src/components/image_grid.tsx +0 -103
- package/templates/gen_ai/src/components/index.ts +0 -7
- package/templates/gen_ai/src/components/loading_results.tsx +0 -169
- package/templates/gen_ai/src/components/prompt_input.messages.ts +0 -14
- package/templates/gen_ai/src/components/prompt_input.tsx +0 -154
- package/templates/gen_ai/src/components/remaining_credits.tsx +0 -84
- package/templates/gen_ai/src/components/report_box.tsx +0 -54
- package/templates/gen_ai/src/components/tests/remaining_credit.tests.tsx +0 -47
- package/templates/gen_ai/src/config.ts +0 -21
- package/templates/gen_ai/src/context/app_context.tsx +0 -153
- package/templates/gen_ai/src/context/context.messages.ts +0 -30
- package/templates/gen_ai/src/context/index.ts +0 -2
- package/templates/gen_ai/src/context/use_app_context.ts +0 -17
- package/templates/gen_ai/src/index.tsx +0 -4
- package/templates/gen_ai/src/intents/design_editor/app.tsx +0 -19
- package/templates/gen_ai/src/intents/design_editor/home.tsx +0 -13
- package/templates/gen_ai/src/intents/design_editor/index.tsx +0 -17
- package/templates/gen_ai/src/pages/error.tsx +0 -41
- package/templates/gen_ai/src/pages/generate.tsx +0 -9
- package/templates/gen_ai/src/pages/index.ts +0 -3
- package/templates/gen_ai/src/pages/results.tsx +0 -31
- package/templates/gen_ai/src/routes/index.ts +0 -1
- package/templates/gen_ai/src/routes/paths.ts +0 -4
- package/templates/gen_ai/src/routes/routes.tsx +0 -24
- package/templates/gen_ai/src/utils/index.ts +0 -1
- package/templates/gen_ai/src/utils/obscenity_filter.ts +0 -33
- package/templates/gen_ai/styles/components.css +0 -38
- package/templates/gen_ai/styles/utils.css +0 -3
- package/templates/gen_ai/tsconfig.json +0 -56
- package/templates/gen_ai/utils/backend/base_backend/create.ts +0 -114
- package/templates/gen_ai/webpack.config.ts +0 -247
- package/templates/hello_world/canva-app.json +0 -21
- package/templates/hello_world/declarations/declarations.d.ts +0 -29
- package/templates/hello_world/eslint.config.mjs +0 -14
- package/templates/hello_world/package.json +0 -90
- package/templates/hello_world/scripts/copy_env.ts +0 -13
- package/templates/hello_world/scripts/ssl/ssl.ts +0 -131
- package/templates/hello_world/scripts/start/app_runner.ts +0 -223
- package/templates/hello_world/scripts/start/context.ts +0 -171
- package/templates/hello_world/scripts/start/start.ts +0 -46
- package/templates/hello_world/src/index.tsx +0 -4
- package/templates/hello_world/src/intents/design_editor/app.tsx +0 -86
- package/templates/hello_world/src/intents/design_editor/index.tsx +0 -25
- package/templates/hello_world/src/intents/design_editor/tests/__snapshots__/app.tests.tsx.snap +0 -45
- package/templates/hello_world/src/intents/design_editor/tests/app.tests.tsx +0 -92
- package/templates/hello_world/styles/components.css +0 -38
- package/templates/hello_world/tsconfig.json +0 -56
- package/templates/hello_world/webpack.config.ts +0 -247
- package/templates/mls/README.md +0 -81
- package/templates/mls/canva-app.json +0 -25
- package/templates/mls/declarations/declarations.d.ts +0 -29
- package/templates/mls/eslint.config.mjs +0 -14
- package/templates/mls/jest.config.mjs +0 -36
- package/templates/mls/jest.setup.ts +0 -39
- package/templates/mls/package.json +0 -117
- package/templates/mls/scripts/copy_env.ts +0 -13
- package/templates/mls/scripts/ssl/ssl.ts +0 -131
- package/templates/mls/scripts/start/app_runner.ts +0 -223
- package/templates/mls/scripts/start/context.ts +0 -171
- package/templates/mls/scripts/start/start.ts +0 -46
- package/templates/mls/src/__tests__/app.tests.tsx +0 -11
- package/templates/mls/src/__tests__/office_selection_page.tests.tsx +0 -72
- package/templates/mls/src/__tests__/utils.tsx +0 -19
- package/templates/mls/src/adapter.ts +0 -126
- package/templates/mls/src/components/agent/agent_card.tsx +0 -57
- package/templates/mls/src/components/agent/agent_grid.tsx +0 -37
- package/templates/mls/src/components/agent/agent_list.tsx +0 -17
- package/templates/mls/src/components/agent/agent_search_filters.tsx +0 -88
- package/templates/mls/src/components/breadcrumb/breadcrumb.tsx +0 -40
- package/templates/mls/src/components/listing/listing_card.tsx +0 -64
- package/templates/mls/src/components/listing/listing_grid.tsx +0 -37
- package/templates/mls/src/components/listing/listing_list.tsx +0 -21
- package/templates/mls/src/components/listing/listing_search_filters.tsx +0 -145
- package/templates/mls/src/components/placeholders/placeholders.tsx +0 -65
- package/templates/mls/src/data.ts +0 -359
- package/templates/mls/src/index.tsx +0 -4
- package/templates/mls/src/intents/design_editor/app.tsx +0 -44
- package/templates/mls/src/intents/design_editor/index.tsx +0 -25
- package/templates/mls/src/pages/agent_details_page/agent_details_page.tsx +0 -175
- package/templates/mls/src/pages/list_page/agent_tab_panel.tsx +0 -126
- package/templates/mls/src/pages/list_page/list_page.tsx +0 -67
- package/templates/mls/src/pages/list_page/listing_tab_panel.tsx +0 -135
- package/templates/mls/src/pages/listing_details_page/listing_details_page.tsx +0 -418
- package/templates/mls/src/pages/loading_page/loading_page.tsx +0 -152
- package/templates/mls/src/pages/office_selection_page/office_selection_page.tsx +0 -144
- package/templates/mls/src/real_estate.type.ts +0 -44
- package/templates/mls/src/util/use_add_element.tsx +0 -62
- package/templates/mls/src/util/use_drag_element.tsx +0 -68
- package/templates/mls/styles/components.css +0 -38
- package/templates/mls/tsconfig.json +0 -54
- package/templates/mls/webpack.config.ts +0 -248
- package/templates/optional/.cursor/mcp.json +0 -8
- package/templates/optional/.vscode/extensions.json +0 -6
- package/templates/optional/.vscode/mcp.json +0 -9
- package/templates/optional/AGENTS.md +0 -154
- 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";
|