@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.
- package/CHANGELOG.md +7 -0
- package/README.md +1 -9
- package/cli.js +408 -409
- package/package.json +1 -2
- 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,81 +0,0 @@
|
|
|
1
|
-
import { FormField, Rows, Text, TextInput } from "@canva/app-ui-kit";
|
|
2
|
-
import type {
|
|
3
|
-
PublishRefValidityState,
|
|
4
|
-
PublishSettingsSettingsUiContext,
|
|
5
|
-
RenderSettingsUiRequest,
|
|
6
|
-
} from "@canva/intents/content";
|
|
7
|
-
import { useCallback, useEffect, useState } from "react";
|
|
8
|
-
import { useIntl } from "react-intl";
|
|
9
|
-
import * as styles from "styles/components.css";
|
|
10
|
-
import type { PublishSettings } from "./types";
|
|
11
|
-
|
|
12
|
-
// Settings UI component for configuring publish settings
|
|
13
|
-
export const SettingsUi = ({
|
|
14
|
-
updatePublishSettings,
|
|
15
|
-
registerOnContextChange,
|
|
16
|
-
}: RenderSettingsUiRequest) => {
|
|
17
|
-
const intl = useIntl();
|
|
18
|
-
const [settings, setSettings] = useState<PublishSettings>({ caption: "" });
|
|
19
|
-
const [settingsUiContext, setSettingsUiContext] =
|
|
20
|
-
useState<PublishSettingsSettingsUiContext | null>(null);
|
|
21
|
-
|
|
22
|
-
// Listen for settings UI context changes (e.g., when output type changes)
|
|
23
|
-
useEffect(() => {
|
|
24
|
-
const dispose = registerOnContextChange({
|
|
25
|
-
onContextChange: (context) => {
|
|
26
|
-
if (context.reason !== "publish_settings") return;
|
|
27
|
-
setSettingsUiContext(context);
|
|
28
|
-
},
|
|
29
|
-
});
|
|
30
|
-
return dispose;
|
|
31
|
-
}, [registerOnContextChange]);
|
|
32
|
-
|
|
33
|
-
// Helper function to both set the settings locally and propagate them to Canva
|
|
34
|
-
const setAndPropagateSettings = useCallback(
|
|
35
|
-
(updatedSettings: PublishSettings) => {
|
|
36
|
-
setSettings(updatedSettings);
|
|
37
|
-
updatePublishSettings({
|
|
38
|
-
publishRef: JSON.stringify(updatedSettings),
|
|
39
|
-
validityState: validatePublishRef(updatedSettings),
|
|
40
|
-
});
|
|
41
|
-
},
|
|
42
|
-
[updatePublishSettings],
|
|
43
|
-
);
|
|
44
|
-
|
|
45
|
-
return (
|
|
46
|
-
<div className={styles.scrollContainer}>
|
|
47
|
-
<Rows spacing="2u">
|
|
48
|
-
{settingsUiContext?.outputType.displayName && (
|
|
49
|
-
<Text>{settingsUiContext?.outputType.displayName}</Text>
|
|
50
|
-
)}
|
|
51
|
-
<FormField
|
|
52
|
-
label={intl.formatMessage({
|
|
53
|
-
defaultMessage: "Caption",
|
|
54
|
-
description: "Label for the caption input field",
|
|
55
|
-
})}
|
|
56
|
-
control={(props) => (
|
|
57
|
-
<TextInput
|
|
58
|
-
{...props}
|
|
59
|
-
value={settings.caption}
|
|
60
|
-
onChange={(caption) => {
|
|
61
|
-
setAndPropagateSettings({ ...settings, caption });
|
|
62
|
-
}}
|
|
63
|
-
/>
|
|
64
|
-
)}
|
|
65
|
-
/>
|
|
66
|
-
</Rows>
|
|
67
|
-
</div>
|
|
68
|
-
);
|
|
69
|
-
};
|
|
70
|
-
|
|
71
|
-
// Validates the publish settings to enable/disable the publish button
|
|
72
|
-
// Returns "valid" when all required fields are filled
|
|
73
|
-
const validatePublishRef = (
|
|
74
|
-
publishRef: PublishSettings,
|
|
75
|
-
): PublishRefValidityState => {
|
|
76
|
-
// caption is required
|
|
77
|
-
if (publishRef.caption.length === 0) {
|
|
78
|
-
return "invalid_missing_required_fields";
|
|
79
|
-
}
|
|
80
|
-
return "valid";
|
|
81
|
-
};
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Type definition for publish settings.
|
|
3
|
-
* These settings are serialized and passed between different parts of the publish flow.
|
|
4
|
-
* In production, include all platform-specific settings needed for publishing.
|
|
5
|
-
*/
|
|
6
|
-
export interface PublishSettings {
|
|
7
|
-
caption: string;
|
|
8
|
-
// TODO: Add additional fields for your platform's publishing requirements
|
|
9
|
-
// Examples:
|
|
10
|
-
// scheduledTime: Date;
|
|
11
|
-
// visibility: "public" | "private" | "unlisted";
|
|
12
|
-
// tags: string[];
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* Utility function to safely parse publish settings from a JSON string.
|
|
17
|
-
* This is used to deserialize settings that were passed through the publish flow.
|
|
18
|
-
*/
|
|
19
|
-
export function parsePublishSettings(
|
|
20
|
-
publishRef?: string,
|
|
21
|
-
): PublishSettings | undefined {
|
|
22
|
-
if (!publishRef) return undefined;
|
|
23
|
-
|
|
24
|
-
try {
|
|
25
|
-
return JSON.parse(publishRef) as PublishSettings;
|
|
26
|
-
} catch {
|
|
27
|
-
return undefined;
|
|
28
|
-
}
|
|
29
|
-
}
|
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
/* Scroll container */
|
|
2
|
-
.scrollContainer {
|
|
3
|
-
box-sizing: border-box;
|
|
4
|
-
overflow-y: scroll;
|
|
5
|
-
height: 100%;
|
|
6
|
-
padding-top: var(--ui-kit-space-200);
|
|
7
|
-
padding-right: var(--ui-kit-space-200);
|
|
8
|
-
padding-bottom: var(--ui-kit-space-200);
|
|
9
|
-
|
|
10
|
-
/* for firefox */
|
|
11
|
-
scrollbar-width: thin;
|
|
12
|
-
scrollbar-color: var(--ui-kit-color-content-placeholder-fg) transparent;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
.scrollContainer::-webkit-scrollbar {
|
|
16
|
-
position: absolute;
|
|
17
|
-
width: var(--ui-kit-base-unit);
|
|
18
|
-
height: 0;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
.scrollContainer::-webkit-scrollbar-track {
|
|
22
|
-
background: transparent;
|
|
23
|
-
width: var(--ui-kit-base-unit);
|
|
24
|
-
margin-top: var(--ui-kit-space-100);
|
|
25
|
-
margin-bottom: var(--ui-kit-space-100);
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
.scrollContainer::-webkit-scrollbar-thumb {
|
|
29
|
-
border-radius: var(--ui-kit-radius-element-standard);
|
|
30
|
-
background: var(--ui-kit-color-content-placeholder-fg);
|
|
31
|
-
visibility: hidden;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
.scrollContainer:hover::-webkit-scrollbar-thumb,
|
|
35
|
-
.scrollContainer:focus::-webkit-scrollbar-thumb,
|
|
36
|
-
.scrollContainer:focus-within::-webkit-scrollbar-thumb {
|
|
37
|
-
visibility: visible;
|
|
38
|
-
}
|
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
/* Scale down preview on mobile devices */
|
|
2
|
-
@media (max-width: 600px) {
|
|
3
|
-
.container {
|
|
4
|
-
transform: scale(0.3);
|
|
5
|
-
}
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
/* User profile section styling */
|
|
9
|
-
.user {
|
|
10
|
-
display: flex;
|
|
11
|
-
align-items: center;
|
|
12
|
-
gap: 8px;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
/* Avatar styling to match social media appearance */
|
|
16
|
-
.avatar {
|
|
17
|
-
transform: scale(0.6);
|
|
18
|
-
width: 24px;
|
|
19
|
-
height: 24px;
|
|
20
|
-
transform-origin: top left;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
/* Text placeholder for loading states */
|
|
24
|
-
.textPlaceholder {
|
|
25
|
-
min-width: calc(8 * 20);
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
/* Container for media images */
|
|
29
|
-
.imageContainer {
|
|
30
|
-
overflow: hidden; /* Enable border radius */
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
/* Row containing all images */
|
|
34
|
-
.imageRow {
|
|
35
|
-
overflow-y: hidden;
|
|
36
|
-
overflow-x: auto;
|
|
37
|
-
height: 400px;
|
|
38
|
-
display: flex;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
/* Individual image and placeholder styling */
|
|
42
|
-
.imagePlaceholder,
|
|
43
|
-
.image {
|
|
44
|
-
width: 400px;
|
|
45
|
-
height: 400px;
|
|
46
|
-
object-fit: cover;
|
|
47
|
-
display: inline-block;
|
|
48
|
-
position: relative;
|
|
49
|
-
}
|
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"compilerOptions": {
|
|
3
|
-
"jsx": "react-jsx",
|
|
4
|
-
"lib": [
|
|
5
|
-
"dom",
|
|
6
|
-
"dom.iterable",
|
|
7
|
-
"es2018",
|
|
8
|
-
"es2019.array",
|
|
9
|
-
"es2019.object",
|
|
10
|
-
"es2019.string",
|
|
11
|
-
"es2020.promise",
|
|
12
|
-
"es2020.string"
|
|
13
|
-
],
|
|
14
|
-
"types": ["node", "webpack-env", "jest"],
|
|
15
|
-
"composite": false,
|
|
16
|
-
"declaration": false,
|
|
17
|
-
"declarationMap": false,
|
|
18
|
-
"experimentalDecorators": true,
|
|
19
|
-
"importHelpers": true,
|
|
20
|
-
"noImplicitOverride": true,
|
|
21
|
-
"moduleResolution": "bundler",
|
|
22
|
-
"esModuleInterop": true,
|
|
23
|
-
"rootDir": ".",
|
|
24
|
-
"outDir": "dist",
|
|
25
|
-
"strict": true,
|
|
26
|
-
"skipLibCheck": true,
|
|
27
|
-
"target": "ES2019",
|
|
28
|
-
"sourceMap": true,
|
|
29
|
-
"inlineSources": true,
|
|
30
|
-
"module": "ESNext",
|
|
31
|
-
"noImplicitAny": true,
|
|
32
|
-
"noImplicitReturns": true,
|
|
33
|
-
"noFallthroughCasesInSwitch": true,
|
|
34
|
-
"noUncheckedIndexedAccess": true,
|
|
35
|
-
"removeComments": true,
|
|
36
|
-
"preserveConstEnums": true,
|
|
37
|
-
"allowSyntheticDefaultImports": true,
|
|
38
|
-
"baseUrl": "./",
|
|
39
|
-
"paths": {
|
|
40
|
-
"styles": ["./styles"]
|
|
41
|
-
}
|
|
42
|
-
},
|
|
43
|
-
"include": [
|
|
44
|
-
"./src/**/*",
|
|
45
|
-
"./backend/**/*",
|
|
46
|
-
"./utils/**/*",
|
|
47
|
-
"./scripts/**/*",
|
|
48
|
-
"./declarations/declarations.d.ts",
|
|
49
|
-
"./styles/**/*"
|
|
50
|
-
],
|
|
51
|
-
"ts-node": {
|
|
52
|
-
"compilerOptions": {
|
|
53
|
-
"module": "commonjs"
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
}
|
|
@@ -1,247 +0,0 @@
|
|
|
1
|
-
import type { Configuration } from "webpack";
|
|
2
|
-
import { DefinePlugin, optimize } from "webpack";
|
|
3
|
-
import path from "path";
|
|
4
|
-
import TerserPlugin from "terser-webpack-plugin";
|
|
5
|
-
import { transform } from "@formatjs/ts-transformer";
|
|
6
|
-
import chalk from "chalk";
|
|
7
|
-
import { config } from "dotenv";
|
|
8
|
-
import { Configuration as DevServerConfiguration } from "webpack-dev-server";
|
|
9
|
-
|
|
10
|
-
config();
|
|
11
|
-
|
|
12
|
-
type DevConfig = {
|
|
13
|
-
port: number;
|
|
14
|
-
enableHmr: boolean;
|
|
15
|
-
enableHttps: boolean;
|
|
16
|
-
appOrigin?: string;
|
|
17
|
-
appId?: string; // Deprecated in favour of appOrigin
|
|
18
|
-
certFile?: string;
|
|
19
|
-
keyFile?: string;
|
|
20
|
-
};
|
|
21
|
-
|
|
22
|
-
export function buildConfig({
|
|
23
|
-
devConfig,
|
|
24
|
-
appEntry = path.join(process.cwd(), "src", "index.tsx"),
|
|
25
|
-
backendHost = process.env.CANVA_BACKEND_HOST,
|
|
26
|
-
// For IN_HARNESS, refer to the following docs for more information: https://www.canva.dev/docs/apps/test-harness/
|
|
27
|
-
inHarness = process.env.IN_HARNESS?.toLowerCase() === "true",
|
|
28
|
-
}: {
|
|
29
|
-
devConfig?: DevConfig;
|
|
30
|
-
appEntry?: string;
|
|
31
|
-
backendHost?: string;
|
|
32
|
-
inHarness?: boolean;
|
|
33
|
-
} = {}): Configuration & DevServerConfiguration {
|
|
34
|
-
const mode = devConfig ? "development" : "production";
|
|
35
|
-
|
|
36
|
-
if (!backendHost) {
|
|
37
|
-
console.warn(
|
|
38
|
-
chalk.yellow.bold("BACKEND_HOST is undefined."),
|
|
39
|
-
`If your app requires a backend, refer to "Customizing the backend host" in the README.md for more information.`,
|
|
40
|
-
);
|
|
41
|
-
} else if (backendHost.includes("localhost") && mode === "production") {
|
|
42
|
-
console.error(
|
|
43
|
-
chalk.redBright.bold(
|
|
44
|
-
"BACKEND_HOST should not be set to localhost for production builds!",
|
|
45
|
-
),
|
|
46
|
-
`Refer to "Customizing the backend host" in the README.md for more information.`,
|
|
47
|
-
);
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
return {
|
|
51
|
-
mode,
|
|
52
|
-
context: path.resolve(process.cwd(), "./"),
|
|
53
|
-
entry: inHarness
|
|
54
|
-
? {
|
|
55
|
-
harness: path.join(process.cwd(), "harness", "harness.tsx"),
|
|
56
|
-
init: path.join(process.cwd(), "harness", "init.ts"),
|
|
57
|
-
}
|
|
58
|
-
: {
|
|
59
|
-
app: appEntry,
|
|
60
|
-
},
|
|
61
|
-
target: "web",
|
|
62
|
-
resolve: {
|
|
63
|
-
alias: {
|
|
64
|
-
styles: path.resolve(process.cwd(), "styles"),
|
|
65
|
-
src: path.resolve(process.cwd(), "src"),
|
|
66
|
-
},
|
|
67
|
-
extensions: [".ts", ".tsx", ".js", ".css", ".svg", ".woff", ".woff2"],
|
|
68
|
-
},
|
|
69
|
-
infrastructureLogging: {
|
|
70
|
-
level: inHarness ? "info" : "none",
|
|
71
|
-
},
|
|
72
|
-
module: {
|
|
73
|
-
rules: [
|
|
74
|
-
{
|
|
75
|
-
test: /\.tsx?$/,
|
|
76
|
-
exclude: /node_modules/,
|
|
77
|
-
use: [
|
|
78
|
-
{
|
|
79
|
-
loader: "ts-loader",
|
|
80
|
-
options: {
|
|
81
|
-
transpileOnly: true,
|
|
82
|
-
getCustomTransformers() {
|
|
83
|
-
return {
|
|
84
|
-
before: [
|
|
85
|
-
transform({
|
|
86
|
-
overrideIdFn: "[sha512:contenthash:base64:6]",
|
|
87
|
-
}),
|
|
88
|
-
],
|
|
89
|
-
};
|
|
90
|
-
},
|
|
91
|
-
},
|
|
92
|
-
},
|
|
93
|
-
],
|
|
94
|
-
},
|
|
95
|
-
{
|
|
96
|
-
test: /\.css$/,
|
|
97
|
-
exclude: /node_modules/,
|
|
98
|
-
use: [
|
|
99
|
-
"style-loader",
|
|
100
|
-
{
|
|
101
|
-
loader: "css-loader",
|
|
102
|
-
options: {
|
|
103
|
-
modules: true,
|
|
104
|
-
},
|
|
105
|
-
},
|
|
106
|
-
{
|
|
107
|
-
loader: "postcss-loader",
|
|
108
|
-
options: {
|
|
109
|
-
postcssOptions: {
|
|
110
|
-
plugins: [require("cssnano")({ preset: "default" })],
|
|
111
|
-
},
|
|
112
|
-
},
|
|
113
|
-
},
|
|
114
|
-
],
|
|
115
|
-
},
|
|
116
|
-
{
|
|
117
|
-
test: /\.(png|jpg|jpeg)$/i,
|
|
118
|
-
type: "asset/inline",
|
|
119
|
-
},
|
|
120
|
-
{
|
|
121
|
-
test: /\.(woff|woff2)$/,
|
|
122
|
-
type: "asset/inline",
|
|
123
|
-
},
|
|
124
|
-
{
|
|
125
|
-
test: /\.svg$/,
|
|
126
|
-
oneOf: [
|
|
127
|
-
{
|
|
128
|
-
issuer: /\.[jt]sx?$/,
|
|
129
|
-
resourceQuery: /react/, // *.svg?react
|
|
130
|
-
use: ["@svgr/webpack", "url-loader"],
|
|
131
|
-
},
|
|
132
|
-
{
|
|
133
|
-
type: "asset/resource",
|
|
134
|
-
parser: {
|
|
135
|
-
dataUrlCondition: {
|
|
136
|
-
maxSize: 200,
|
|
137
|
-
},
|
|
138
|
-
},
|
|
139
|
-
},
|
|
140
|
-
],
|
|
141
|
-
},
|
|
142
|
-
{
|
|
143
|
-
test: /\.css$/,
|
|
144
|
-
include: /node_modules/,
|
|
145
|
-
use: [
|
|
146
|
-
"style-loader",
|
|
147
|
-
"css-loader",
|
|
148
|
-
{
|
|
149
|
-
loader: "postcss-loader",
|
|
150
|
-
options: {
|
|
151
|
-
postcssOptions: {
|
|
152
|
-
plugins: [require("cssnano")({ preset: "default" })],
|
|
153
|
-
},
|
|
154
|
-
},
|
|
155
|
-
},
|
|
156
|
-
],
|
|
157
|
-
},
|
|
158
|
-
],
|
|
159
|
-
},
|
|
160
|
-
optimization: {
|
|
161
|
-
minimizer: [
|
|
162
|
-
new TerserPlugin({
|
|
163
|
-
terserOptions: {
|
|
164
|
-
format: {
|
|
165
|
-
// Turned on because emoji and regex is not minified properly using default
|
|
166
|
-
// https://github.com/facebook/create-react-app/issues/2488
|
|
167
|
-
ascii_only: true,
|
|
168
|
-
},
|
|
169
|
-
},
|
|
170
|
-
}),
|
|
171
|
-
],
|
|
172
|
-
},
|
|
173
|
-
output: {
|
|
174
|
-
filename: `[name].js`,
|
|
175
|
-
path: path.resolve(process.cwd(), "dist"),
|
|
176
|
-
clean: true,
|
|
177
|
-
},
|
|
178
|
-
plugins: [
|
|
179
|
-
new DefinePlugin({
|
|
180
|
-
BACKEND_HOST: JSON.stringify(backendHost),
|
|
181
|
-
}),
|
|
182
|
-
// Apps can only submit a single JS file via the Developer Portal
|
|
183
|
-
new optimize.LimitChunkCountPlugin({ maxChunks: 1 }),
|
|
184
|
-
].filter(Boolean),
|
|
185
|
-
...buildDevConfig(devConfig),
|
|
186
|
-
};
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
function buildDevConfig(options?: DevConfig): {
|
|
190
|
-
devtool?: string;
|
|
191
|
-
devServer?: DevServerConfiguration;
|
|
192
|
-
} {
|
|
193
|
-
if (!options) {
|
|
194
|
-
return {};
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
const { port, enableHmr, appOrigin, enableHttps, certFile, keyFile } =
|
|
198
|
-
options;
|
|
199
|
-
const host = "localhost";
|
|
200
|
-
|
|
201
|
-
let devServer: DevServerConfiguration = {
|
|
202
|
-
server: enableHttps
|
|
203
|
-
? {
|
|
204
|
-
type: "https",
|
|
205
|
-
options: {
|
|
206
|
-
cert: certFile,
|
|
207
|
-
key: keyFile,
|
|
208
|
-
},
|
|
209
|
-
}
|
|
210
|
-
: "http",
|
|
211
|
-
host,
|
|
212
|
-
allowedHosts: [host],
|
|
213
|
-
historyApiFallback: {
|
|
214
|
-
rewrites: [{ from: /^\/$/, to: "/app.js" }],
|
|
215
|
-
},
|
|
216
|
-
port,
|
|
217
|
-
client: {
|
|
218
|
-
logging: "verbose",
|
|
219
|
-
},
|
|
220
|
-
};
|
|
221
|
-
|
|
222
|
-
if (enableHmr && appOrigin) {
|
|
223
|
-
devServer = {
|
|
224
|
-
...devServer,
|
|
225
|
-
allowedHosts: [host, new URL(appOrigin).hostname],
|
|
226
|
-
headers: {
|
|
227
|
-
"Access-Control-Allow-Origin": appOrigin,
|
|
228
|
-
"Access-Control-Allow-Credentials": "true",
|
|
229
|
-
"Access-Control-Allow-Private-Network": "true",
|
|
230
|
-
},
|
|
231
|
-
};
|
|
232
|
-
} else {
|
|
233
|
-
if (enableHmr && !appOrigin) {
|
|
234
|
-
console.warn(
|
|
235
|
-
"Attempted to enable Hot Module Replacement (HMR) without configuring App Origin... Disabling HMR.",
|
|
236
|
-
);
|
|
237
|
-
}
|
|
238
|
-
devServer.webSocketServer = false;
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
return {
|
|
242
|
-
devtool: "source-map",
|
|
243
|
-
devServer,
|
|
244
|
-
};
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
export default buildConfig;
|
|
@@ -1,108 +0,0 @@
|
|
|
1
|
-
import type { Container, Resource } from "@canva/app-components";
|
|
2
|
-
import crypto from "crypto";
|
|
3
|
-
import express from "express";
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Generates a unique hash for a URL.
|
|
7
|
-
* Used for creating unique identifiers for digital assets from external URLs.
|
|
8
|
-
*/
|
|
9
|
-
export async function generateHash(message: string) {
|
|
10
|
-
const msgUint8 = new TextEncoder().encode(message);
|
|
11
|
-
const hashBuffer = await crypto.subtle.digest("SHA-256", msgUint8);
|
|
12
|
-
const hashArray = Array.from(new Uint8Array(hashBuffer));
|
|
13
|
-
const hashHex = hashArray
|
|
14
|
-
.map((b) => b.toString(16).padStart(2, "0"))
|
|
15
|
-
.join("");
|
|
16
|
-
return hashHex;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
// Mock image URLs for demonstration purposes - replace with your actual digital asset sources
|
|
20
|
-
const imageUrls = [
|
|
21
|
-
"https://images.pexels.com/photos/1495580/pexels-photo-1495580.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=2",
|
|
22
|
-
"https://images.pexels.com/photos/3943197/pexels-photo-3943197.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=2",
|
|
23
|
-
"https://images.pexels.com/photos/7195267/pexels-photo-7195267.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=2",
|
|
24
|
-
"https://images.pexels.com/photos/2904142/pexels-photo-2904142.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=2",
|
|
25
|
-
"https://images.pexels.com/photos/5403478/pexels-photo-5403478.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=2",
|
|
26
|
-
];
|
|
27
|
-
|
|
28
|
-
export const createDamRouter = () => {
|
|
29
|
-
const router = express.Router();
|
|
30
|
-
|
|
31
|
-
/*
|
|
32
|
-
Main endpoint for finding digital assets and containers.
|
|
33
|
-
This should be replaced with actual integration to your digital asset management system.
|
|
34
|
-
*/
|
|
35
|
-
router.post("/resources/find", async (req, res) => {
|
|
36
|
-
/*
|
|
37
|
-
Extract relevant fields from the FindResourcesRequest.
|
|
38
|
-
Replace this mock implementation with actual queries to your digital asset management system.
|
|
39
|
-
Consider implementing proper filtering, sorting, and pagination based on these parameters.
|
|
40
|
-
*/
|
|
41
|
-
const {
|
|
42
|
-
types,
|
|
43
|
-
continuation,
|
|
44
|
-
locale,
|
|
45
|
-
// other available fields from the `FindResourcesRequest`
|
|
46
|
-
// containerTypes,
|
|
47
|
-
// limit,
|
|
48
|
-
// filters,
|
|
49
|
-
// query,
|
|
50
|
-
// sort,
|
|
51
|
-
// tab,
|
|
52
|
-
// containerId,
|
|
53
|
-
// parentContainerType,
|
|
54
|
-
} = req.body;
|
|
55
|
-
|
|
56
|
-
let resources: Resource[] = [];
|
|
57
|
-
|
|
58
|
-
// Handle image resource requests
|
|
59
|
-
if (types.includes("IMAGE")) {
|
|
60
|
-
resources = await Promise.all(
|
|
61
|
-
Array.from({ length: 40 }, async (_, i) => {
|
|
62
|
-
const imageUrl = imageUrls[i % imageUrls.length];
|
|
63
|
-
|
|
64
|
-
if (!imageUrl) {
|
|
65
|
-
throw new Error(`Image URL not found for index ${i}`);
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
return {
|
|
69
|
-
id: await generateHash(i + ""),
|
|
70
|
-
mimeType: "image/jpeg",
|
|
71
|
-
name: `My new thing in ${locale}`, // Uses locale for demonstration - implement proper i18n
|
|
72
|
-
type: "IMAGE",
|
|
73
|
-
thumbnail: {
|
|
74
|
-
url: imageUrl,
|
|
75
|
-
},
|
|
76
|
-
url: imageUrl,
|
|
77
|
-
};
|
|
78
|
-
}),
|
|
79
|
-
);
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
// Handle container (folder) resource requests
|
|
83
|
-
if (types.includes("CONTAINER")) {
|
|
84
|
-
const containers = await Promise.all(
|
|
85
|
-
Array.from(
|
|
86
|
-
{ length: 10 },
|
|
87
|
-
async (_, i) =>
|
|
88
|
-
({
|
|
89
|
-
id: await generateHash(i + ""),
|
|
90
|
-
containerType: "folder",
|
|
91
|
-
name: `My folder ${i}`,
|
|
92
|
-
type: "CONTAINER",
|
|
93
|
-
}) satisfies Container,
|
|
94
|
-
),
|
|
95
|
-
);
|
|
96
|
-
|
|
97
|
-
resources = resources.concat(containers);
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
// Send response with resources and pagination token
|
|
101
|
-
res.send({
|
|
102
|
-
resources,
|
|
103
|
-
continuation: +(continuation || 0) + 1,
|
|
104
|
-
});
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
return router;
|
|
108
|
-
};
|
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
import { user } from "@canva/app-middleware/express";
|
|
2
|
-
import cors from "cors";
|
|
3
|
-
import express from "express";
|
|
4
|
-
import { createBaseServer } from "../utils/backend/base_backend/create";
|
|
5
|
-
import { createDamRouter } from "./routers/dam";
|
|
6
|
-
|
|
7
|
-
async function main() {
|
|
8
|
-
// TODO: Set the CANVA_APP_ID environment variable in the project's .env file
|
|
9
|
-
const APP_ID = process.env.CANVA_APP_ID;
|
|
10
|
-
|
|
11
|
-
if (!APP_ID) {
|
|
12
|
-
throw new Error(
|
|
13
|
-
`The CANVA_APP_ID environment variable is undefined. Set the variable in the project's .env file.`,
|
|
14
|
-
);
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
const router = express.Router();
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* TODO: Configure your CORS Policy
|
|
21
|
-
*
|
|
22
|
-
* Cross-Origin Resource Sharing
|
|
23
|
-
* ([CORS](https://developer.mozilla.org/en-US/docs/Glossary/CORS)) is an
|
|
24
|
-
* [HTTP](https://developer.mozilla.org/en-US/docs/Glossary/HTTP)-header based
|
|
25
|
-
* mechanism that allows a server to indicate any
|
|
26
|
-
* [origins](https://developer.mozilla.org/en-US/docs/Glossary/Origin)
|
|
27
|
-
* (domain, scheme, or port) other than its own from which a browser should
|
|
28
|
-
* permit loading resources.
|
|
29
|
-
*
|
|
30
|
-
* A basic CORS configuration would include the origin of your app in the
|
|
31
|
-
* following example:
|
|
32
|
-
* const corsOptions = {
|
|
33
|
-
* origin: 'https://app-abcdefg.canva-apps.com',
|
|
34
|
-
* optionsSuccessStatus: 200
|
|
35
|
-
* }
|
|
36
|
-
*
|
|
37
|
-
* The origin of your app is https://app-${APP_ID}.canva-apps.com, and note
|
|
38
|
-
* that the APP_ID should to be converted to lowercase.
|
|
39
|
-
*
|
|
40
|
-
* https://www.npmjs.com/package/cors#configuring-cors
|
|
41
|
-
*
|
|
42
|
-
* You may need to include multiple permissible origins, or dynamic origins
|
|
43
|
-
* based on the environment in which the server is running. Further
|
|
44
|
-
* information can be found
|
|
45
|
-
* [here](https://www.npmjs.com/package/cors#configuring-cors-w-dynamic-origin).
|
|
46
|
-
*/
|
|
47
|
-
router.use(cors());
|
|
48
|
-
|
|
49
|
-
/**
|
|
50
|
-
* Initialize JWT middleware to verify Canva user tokens
|
|
51
|
-
* This middleware validates tokens sent from the frontend and extracts user information
|
|
52
|
-
*/
|
|
53
|
-
router.use(user.verifyToken({ appId: APP_ID }));
|
|
54
|
-
|
|
55
|
-
/**
|
|
56
|
-
* Add routes for digital asset management.
|
|
57
|
-
*/
|
|
58
|
-
const damRouter = createDamRouter();
|
|
59
|
-
router.use(damRouter);
|
|
60
|
-
|
|
61
|
-
const server = createBaseServer(router);
|
|
62
|
-
server.start(process.env.CANVA_BACKEND_PORT);
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
main();
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"$schema": "https://www.canva.dev/schemas/app/v1/manifest-schema.json",
|
|
3
|
-
"manifest_schema_version": 1,
|
|
4
|
-
"runtime": {
|
|
5
|
-
"permissions": [
|
|
6
|
-
{
|
|
7
|
-
"name": "canva:asset:private:write",
|
|
8
|
-
"type": "mandatory"
|
|
9
|
-
},
|
|
10
|
-
{
|
|
11
|
-
"name": "canva:design:content:read",
|
|
12
|
-
"type": "mandatory"
|
|
13
|
-
},
|
|
14
|
-
{
|
|
15
|
-
"name": "canva:design:content:write",
|
|
16
|
-
"type": "mandatory"
|
|
17
|
-
}
|
|
18
|
-
]
|
|
19
|
-
},
|
|
20
|
-
"intent": {
|
|
21
|
-
"design_editor": {
|
|
22
|
-
"enrolled": true
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
}
|