@canva/cli 0.0.1-beta.30 → 0.0.1-beta.32
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/cli.js +313 -276
- package/package.json +1 -1
- package/templates/base/package.json +4 -4
- package/templates/base/webpack.config.ts +5 -3
- package/templates/dam/package.json +4 -4
- package/templates/dam/webpack.config.ts +5 -3
- package/templates/data_connector/package.json +5 -5
- package/templates/data_connector/src/api/data_sources/designs.tsx +60 -17
- package/templates/data_connector/src/api/data_sources/templates.tsx +66 -24
- package/templates/data_connector/src/api/fetch_data_table.ts +12 -8
- package/templates/data_connector/src/app.tsx +3 -7
- package/templates/data_connector/src/components/inputs/messages.tsx +19 -0
- package/templates/data_connector/src/components/inputs/search_filter.tsx +108 -0
- package/templates/data_connector/src/context/app_context.tsx +10 -10
- package/templates/data_connector/src/entrypoint.tsx +5 -8
- package/templates/data_connector/src/index.tsx +10 -10
- package/templates/data_connector/src/utils/data_params.ts +9 -9
- package/templates/data_connector/src/utils/data_table.ts +17 -2
- package/templates/data_connector/src/utils/fetch_result.ts +6 -6
- package/templates/data_connector/src/utils/tests/data_table.test.ts +6 -6
- package/templates/data_connector/webpack.config.ts +5 -3
- package/templates/gen_ai/package.json +4 -4
- package/templates/gen_ai/webpack.config.ts +5 -3
- package/templates/hello_world/package.json +4 -4
- package/templates/hello_world/webpack.config.ts +5 -3
package/package.json
CHANGED
|
@@ -6,11 +6,11 @@
|
|
|
6
6
|
"license": "SEE LICENSE IN LICENSE.md",
|
|
7
7
|
"author": "Canva Pty Ltd.",
|
|
8
8
|
"dependencies": {
|
|
9
|
-
"@canva/app-ui-kit": "^4.
|
|
9
|
+
"@canva/app-ui-kit": "^4.10.0",
|
|
10
10
|
"@canva/asset": "^2.2.0",
|
|
11
|
-
"@canva/design": "^2.
|
|
11
|
+
"@canva/design": "^2.6.0",
|
|
12
12
|
"@canva/error": "^2.1.0",
|
|
13
|
-
"@canva/platform": "^2.
|
|
13
|
+
"@canva/platform": "^2.2.0",
|
|
14
14
|
"@canva/user": "^2.1.0",
|
|
15
15
|
"cookie-parser": "1.4.7",
|
|
16
16
|
"react": "18.3.1",
|
|
@@ -64,7 +64,7 @@
|
|
|
64
64
|
"url-loader": "4.1.1",
|
|
65
65
|
"webpack": "5.97.1",
|
|
66
66
|
"webpack-cli": "5.1.4",
|
|
67
|
-
"webpack-dev-server": "5.2.
|
|
67
|
+
"webpack-dev-server": "5.2.2",
|
|
68
68
|
"yargs": "17.7.2"
|
|
69
69
|
},
|
|
70
70
|
"keywords": [
|
|
@@ -199,6 +199,7 @@ function buildDevConfig(options?: DevConfig): {
|
|
|
199
199
|
|
|
200
200
|
const { port, enableHmr, appOrigin, appId, enableHttps, certFile, keyFile } =
|
|
201
201
|
options;
|
|
202
|
+
const host = "localhost";
|
|
202
203
|
|
|
203
204
|
let devServer: DevServerConfiguration = {
|
|
204
205
|
server: enableHttps
|
|
@@ -210,7 +211,8 @@ function buildDevConfig(options?: DevConfig): {
|
|
|
210
211
|
},
|
|
211
212
|
}
|
|
212
213
|
: "http",
|
|
213
|
-
host
|
|
214
|
+
host,
|
|
215
|
+
allowedHosts: [host],
|
|
214
216
|
historyApiFallback: {
|
|
215
217
|
rewrites: [{ from: /^\/$/, to: "/app.js" }],
|
|
216
218
|
},
|
|
@@ -227,7 +229,7 @@ function buildDevConfig(options?: DevConfig): {
|
|
|
227
229
|
if (enableHmr && appOrigin) {
|
|
228
230
|
devServer = {
|
|
229
231
|
...devServer,
|
|
230
|
-
allowedHosts: new URL(appOrigin).hostname,
|
|
232
|
+
allowedHosts: [host, new URL(appOrigin).hostname],
|
|
231
233
|
headers: {
|
|
232
234
|
"Access-Control-Allow-Origin": appOrigin,
|
|
233
235
|
"Access-Control-Allow-Credentials": "true",
|
|
@@ -245,7 +247,7 @@ function buildDevConfig(options?: DevConfig): {
|
|
|
245
247
|
const appDomain = `app-${appId.toLowerCase().trim()}.canva-apps.com`;
|
|
246
248
|
devServer = {
|
|
247
249
|
...devServer,
|
|
248
|
-
allowedHosts: appDomain,
|
|
250
|
+
allowedHosts: [host, appDomain],
|
|
249
251
|
headers: {
|
|
250
252
|
"Access-Control-Allow-Origin": `https://${appDomain}`,
|
|
251
253
|
"Access-Control-Allow-Credentials": "true",
|
|
@@ -20,11 +20,11 @@
|
|
|
20
20
|
"dependencies": {
|
|
21
21
|
"@canva/app-components": "^1.3.0",
|
|
22
22
|
"@canva/app-i18n-kit": "^1.0.2",
|
|
23
|
-
"@canva/app-ui-kit": "^4.
|
|
23
|
+
"@canva/app-ui-kit": "^4.10.0",
|
|
24
24
|
"@canva/asset": "^2.2.0",
|
|
25
|
-
"@canva/design": "^2.
|
|
25
|
+
"@canva/design": "^2.6.0",
|
|
26
26
|
"@canva/error": "^2.1.0",
|
|
27
|
-
"@canva/platform": "^2.
|
|
27
|
+
"@canva/platform": "^2.2.0",
|
|
28
28
|
"@canva/user": "^2.1.0",
|
|
29
29
|
"cookie-parser": "1.4.7",
|
|
30
30
|
"cors": "2.8.5",
|
|
@@ -89,7 +89,7 @@
|
|
|
89
89
|
"url-loader": "4.1.1",
|
|
90
90
|
"webpack": "5.97.1",
|
|
91
91
|
"webpack-cli": "5.1.4",
|
|
92
|
-
"webpack-dev-server": "5.2.
|
|
92
|
+
"webpack-dev-server": "5.2.2",
|
|
93
93
|
"yargs": "17.7.2"
|
|
94
94
|
}
|
|
95
95
|
}
|
|
@@ -199,6 +199,7 @@ function buildDevConfig(options?: DevConfig): {
|
|
|
199
199
|
|
|
200
200
|
const { port, enableHmr, appOrigin, appId, enableHttps, certFile, keyFile } =
|
|
201
201
|
options;
|
|
202
|
+
const host = "localhost";
|
|
202
203
|
|
|
203
204
|
let devServer: DevServerConfiguration = {
|
|
204
205
|
server: enableHttps
|
|
@@ -210,7 +211,8 @@ function buildDevConfig(options?: DevConfig): {
|
|
|
210
211
|
},
|
|
211
212
|
}
|
|
212
213
|
: "http",
|
|
213
|
-
host
|
|
214
|
+
host,
|
|
215
|
+
allowedHosts: [host],
|
|
214
216
|
historyApiFallback: {
|
|
215
217
|
rewrites: [{ from: /^\/$/, to: "/app.js" }],
|
|
216
218
|
},
|
|
@@ -227,7 +229,7 @@ function buildDevConfig(options?: DevConfig): {
|
|
|
227
229
|
if (enableHmr && appOrigin) {
|
|
228
230
|
devServer = {
|
|
229
231
|
...devServer,
|
|
230
|
-
allowedHosts: new URL(appOrigin).hostname,
|
|
232
|
+
allowedHosts: [host, new URL(appOrigin).hostname],
|
|
231
233
|
headers: {
|
|
232
234
|
"Access-Control-Allow-Origin": appOrigin,
|
|
233
235
|
"Access-Control-Allow-Credentials": "true",
|
|
@@ -245,7 +247,7 @@ function buildDevConfig(options?: DevConfig): {
|
|
|
245
247
|
const appDomain = `app-${appId.toLowerCase().trim()}.canva-apps.com`;
|
|
246
248
|
devServer = {
|
|
247
249
|
...devServer,
|
|
248
|
-
allowedHosts: appDomain,
|
|
250
|
+
allowedHosts: [host, appDomain],
|
|
249
251
|
headers: {
|
|
250
252
|
"Access-Control-Allow-Origin": `https://${appDomain}`,
|
|
251
253
|
"Access-Control-Allow-Credentials": "true",
|
|
@@ -20,12 +20,12 @@
|
|
|
20
20
|
},
|
|
21
21
|
"dependencies": {
|
|
22
22
|
"@canva/app-i18n-kit": "^1.0.2",
|
|
23
|
-
"@canva/app-ui-kit": "^4.
|
|
23
|
+
"@canva/app-ui-kit": "^4.10.0",
|
|
24
24
|
"@canva/asset": "^2.2.0",
|
|
25
|
-
"@canva/design": "^2.
|
|
25
|
+
"@canva/design": "^2.6.0",
|
|
26
26
|
"@canva/error": "^2.1.0",
|
|
27
|
-
"@canva/intents": "^
|
|
28
|
-
"@canva/platform": "^2.
|
|
27
|
+
"@canva/intents": "^2.0.0",
|
|
28
|
+
"@canva/platform": "^2.2.0",
|
|
29
29
|
"@canva/user": "^2.1.0",
|
|
30
30
|
"react": "18.3.1",
|
|
31
31
|
"react-dom": "18.3.1",
|
|
@@ -85,7 +85,7 @@
|
|
|
85
85
|
"url-loader": "4.1.1",
|
|
86
86
|
"webpack": "5.97.1",
|
|
87
87
|
"webpack-cli": "5.1.4",
|
|
88
|
-
"webpack-dev-server": "5.2.
|
|
88
|
+
"webpack-dev-server": "5.2.2",
|
|
89
89
|
"yargs": "17.7.2"
|
|
90
90
|
}
|
|
91
91
|
}
|
|
@@ -16,9 +16,11 @@ import { SelectField } from "src/components/inputs/select_field";
|
|
|
16
16
|
import { dateCell, numberCell, stringCell } from "src/utils";
|
|
17
17
|
import { Paths } from "src/routes";
|
|
18
18
|
import { useNavigate } from "react-router-dom";
|
|
19
|
-
import { useState } from "react";
|
|
19
|
+
import { useEffect, useState } from "react";
|
|
20
|
+
import { SearchFilter } from "src/components/inputs/search_filter";
|
|
20
21
|
|
|
21
22
|
export interface DesignsDataSource extends DataSourceConfig {
|
|
23
|
+
query: string;
|
|
22
24
|
ownership: "any" | "owned" | "shared";
|
|
23
25
|
sort_by:
|
|
24
26
|
| "relevance"
|
|
@@ -41,6 +43,7 @@ export const designsSource = new DataSourceHandler<
|
|
|
41
43
|
>(
|
|
42
44
|
{
|
|
43
45
|
schema: "designs/v1",
|
|
46
|
+
query: "",
|
|
44
47
|
ownership: "any",
|
|
45
48
|
sort_by: "relevance",
|
|
46
49
|
},
|
|
@@ -77,7 +80,14 @@ export const designsSource = new DataSourceHandler<
|
|
|
77
80
|
rowLimit: number,
|
|
78
81
|
signal: AbortSignal | undefined,
|
|
79
82
|
) =>
|
|
80
|
-
getDesigns(
|
|
83
|
+
getDesigns(
|
|
84
|
+
authToken,
|
|
85
|
+
rowLimit,
|
|
86
|
+
signal,
|
|
87
|
+
source.query,
|
|
88
|
+
source.ownership,
|
|
89
|
+
source.sort_by,
|
|
90
|
+
),
|
|
81
91
|
DesignSelection,
|
|
82
92
|
DesignsSourceConfig,
|
|
83
93
|
);
|
|
@@ -86,15 +96,24 @@ export async function getDesigns(
|
|
|
86
96
|
authToken: string,
|
|
87
97
|
rowLimit: number,
|
|
88
98
|
signal: AbortSignal | undefined,
|
|
99
|
+
query: string,
|
|
89
100
|
ownership: string,
|
|
90
101
|
sort_by: string,
|
|
91
102
|
continuation?: string,
|
|
92
103
|
allItems: CanvaDesign[] = [],
|
|
93
104
|
): Promise<CanvaDesign[]> {
|
|
94
105
|
const baseUrl = `https://api.canva.com/rest/v1/designs`;
|
|
95
|
-
const
|
|
96
|
-
|
|
97
|
-
|
|
106
|
+
const params = new URLSearchParams();
|
|
107
|
+
if (continuation) {
|
|
108
|
+
params.set("continuation", continuation);
|
|
109
|
+
} else {
|
|
110
|
+
if (query) {
|
|
111
|
+
params.set("query", query);
|
|
112
|
+
}
|
|
113
|
+
params.set("ownership", ownership);
|
|
114
|
+
params.set("sort_by", sort_by);
|
|
115
|
+
}
|
|
116
|
+
const url = `${baseUrl}?${params.toString()}`;
|
|
98
117
|
|
|
99
118
|
return fetch(url, {
|
|
100
119
|
headers: {
|
|
@@ -118,6 +137,7 @@ export async function getDesigns(
|
|
|
118
137
|
authToken,
|
|
119
138
|
rowLimit,
|
|
120
139
|
signal,
|
|
140
|
+
query,
|
|
121
141
|
ownership,
|
|
122
142
|
sort_by,
|
|
123
143
|
data.continuation,
|
|
@@ -171,14 +191,29 @@ function DesignSelection() {
|
|
|
171
191
|
function DesignsSourceConfig(sourceConfig: DesignsDataSource) {
|
|
172
192
|
const intl = useIntl();
|
|
173
193
|
const { loadDataSource } = useAppContext();
|
|
194
|
+
const [query, setQuery] = useState<string>(sourceConfig.query);
|
|
174
195
|
const [ownership, setOwnership] = useState<string>(sourceConfig.ownership);
|
|
175
196
|
const [sortOrder, setSortOrder] = useState<string>(sourceConfig.sort_by);
|
|
176
197
|
const [isLoading, setIsLoading] = useState(false);
|
|
177
198
|
|
|
199
|
+
const [filterCount, setFilterCount] = useState(0);
|
|
200
|
+
useEffect(() => {
|
|
201
|
+
// Update the filter count based on the selected filters
|
|
202
|
+
// consider a filter to be applied if not set to the default value
|
|
203
|
+
setFilterCount(
|
|
204
|
+
(ownership !== "any" ? 1 : 0) + (sortOrder !== "relevance" ? 1 : 0),
|
|
205
|
+
);
|
|
206
|
+
}, [ownership, sortOrder]);
|
|
207
|
+
const resetFilters = () => {
|
|
208
|
+
setOwnership("any");
|
|
209
|
+
setSortOrder("relevance");
|
|
210
|
+
};
|
|
211
|
+
|
|
178
212
|
const loadDesigns = async () => {
|
|
179
213
|
setIsLoading(true);
|
|
180
214
|
loadDataSource("Canva Designs", {
|
|
181
215
|
schema: "designs/v1",
|
|
216
|
+
query,
|
|
182
217
|
ownership,
|
|
183
218
|
sort_by: sortOrder,
|
|
184
219
|
} as DesignsDataSource).then(() => {
|
|
@@ -223,18 +258,26 @@ function DesignsSourceConfig(sourceConfig: DesignsDataSource) {
|
|
|
223
258
|
showBack={true}
|
|
224
259
|
/>
|
|
225
260
|
|
|
226
|
-
<
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
261
|
+
<SearchFilter
|
|
262
|
+
value={query}
|
|
263
|
+
onChange={setQuery}
|
|
264
|
+
filterCount={filterCount}
|
|
265
|
+
resetFilters={resetFilters}
|
|
266
|
+
>
|
|
267
|
+
<SelectField
|
|
268
|
+
label={intl.formatMessage(ownershipFilter.label)}
|
|
269
|
+
options={ownershipOptions}
|
|
270
|
+
value={ownership}
|
|
271
|
+
onChange={setOwnership}
|
|
272
|
+
/>
|
|
273
|
+
<SelectField
|
|
274
|
+
label={intl.formatMessage(sortOrderField.label)}
|
|
275
|
+
options={sortOrderOptions}
|
|
276
|
+
value={sortOrder}
|
|
277
|
+
onChange={setSortOrder}
|
|
278
|
+
/>
|
|
279
|
+
</SearchFilter>
|
|
280
|
+
|
|
238
281
|
<Button
|
|
239
282
|
variant="primary"
|
|
240
283
|
loading={isLoading}
|
|
@@ -21,9 +21,11 @@ import { SelectField } from "src/components/inputs/select_field";
|
|
|
21
21
|
import { Header } from "src/components";
|
|
22
22
|
import { useNavigate } from "react-router-dom";
|
|
23
23
|
import { Paths } from "src/routes";
|
|
24
|
-
import { useState } from "react";
|
|
24
|
+
import { useEffect, useState } from "react";
|
|
25
|
+
import { SearchFilter } from "src/components/inputs/search_filter";
|
|
25
26
|
|
|
26
27
|
export interface BrandTemplatesDataSource extends DataSourceConfig {
|
|
28
|
+
query: string;
|
|
27
29
|
dataset: "any" | "non_empty" | "empty";
|
|
28
30
|
ownership: "any" | "owned" | "shared";
|
|
29
31
|
sort_by:
|
|
@@ -48,6 +50,7 @@ export const brandTemplatesSource = new DataSourceHandler<
|
|
|
48
50
|
>(
|
|
49
51
|
{
|
|
50
52
|
schema: "brand_templates/v1",
|
|
53
|
+
query: "",
|
|
51
54
|
dataset: "any",
|
|
52
55
|
ownership: "any",
|
|
53
56
|
sort_by: "relevance",
|
|
@@ -94,8 +97,9 @@ export const brandTemplatesSource = new DataSourceHandler<
|
|
|
94
97
|
authToken,
|
|
95
98
|
rowLimit,
|
|
96
99
|
signal,
|
|
97
|
-
source.
|
|
100
|
+
source.query,
|
|
98
101
|
source.ownership,
|
|
102
|
+
source.dataset,
|
|
99
103
|
source.sort_by,
|
|
100
104
|
),
|
|
101
105
|
BrandTemplatesSelection,
|
|
@@ -106,6 +110,7 @@ export async function getBrandTemplates(
|
|
|
106
110
|
authToken: string,
|
|
107
111
|
rowLimit: number,
|
|
108
112
|
signal: AbortSignal | undefined,
|
|
113
|
+
query: string,
|
|
109
114
|
ownership: string,
|
|
110
115
|
dataset: string,
|
|
111
116
|
sort_by: string,
|
|
@@ -113,9 +118,19 @@ export async function getBrandTemplates(
|
|
|
113
118
|
allItems: CanvaBrandTemplate[] = [],
|
|
114
119
|
): Promise<CanvaBrandTemplate[]> {
|
|
115
120
|
const baseUrl = `https://api.canva.com/rest/v1/brand-templates`;
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
121
|
+
|
|
122
|
+
const params = new URLSearchParams();
|
|
123
|
+
if (continuation) {
|
|
124
|
+
params.set("continuation", continuation);
|
|
125
|
+
} else {
|
|
126
|
+
if (query) {
|
|
127
|
+
params.set("query", query);
|
|
128
|
+
}
|
|
129
|
+
params.set("ownership", ownership);
|
|
130
|
+
params.set("dataset", dataset);
|
|
131
|
+
params.set("sort_by", sort_by);
|
|
132
|
+
}
|
|
133
|
+
const url = `${baseUrl}?${params.toString()}`;
|
|
119
134
|
|
|
120
135
|
return fetch(url, {
|
|
121
136
|
headers: {
|
|
@@ -139,6 +154,7 @@ export async function getBrandTemplates(
|
|
|
139
154
|
authToken,
|
|
140
155
|
rowLimit,
|
|
141
156
|
signal,
|
|
157
|
+
query,
|
|
142
158
|
ownership,
|
|
143
159
|
dataset,
|
|
144
160
|
sort_by,
|
|
@@ -191,16 +207,34 @@ function BrandTemplatesSelection() {
|
|
|
191
207
|
}
|
|
192
208
|
|
|
193
209
|
function BrandTemplatesSourceConfig(sourceConfig: BrandTemplatesDataSource) {
|
|
210
|
+
const intl = useIntl();
|
|
194
211
|
const { loadDataSource } = useAppContext();
|
|
212
|
+
const [query, setQuery] = useState<string>(sourceConfig.query);
|
|
195
213
|
const [ownership, setOwnership] = useState<string>(sourceConfig.ownership);
|
|
196
214
|
const [sortOrder, setSortOrder] = useState<string>(sourceConfig.sort_by);
|
|
197
215
|
const [dataset, setDataset] = useState<string>(sourceConfig.dataset);
|
|
198
216
|
const [isLoading, setIsLoading] = useState(false);
|
|
199
|
-
|
|
217
|
+
|
|
218
|
+
const [filterCount, setFilterCount] = useState(0);
|
|
219
|
+
useEffect(() => {
|
|
220
|
+
// Update the filter count based on the selected filters
|
|
221
|
+
// consider a filter to be applied if not set to the default value
|
|
222
|
+
setFilterCount(
|
|
223
|
+
(ownership !== "any" ? 1 : 0) +
|
|
224
|
+
(dataset !== "any" ? 1 : 0) +
|
|
225
|
+
(sortOrder !== "relevance" ? 1 : 0),
|
|
226
|
+
);
|
|
227
|
+
}, [ownership, dataset, sortOrder]);
|
|
228
|
+
const resetFilters = () => {
|
|
229
|
+
setOwnership("any");
|
|
230
|
+
setDataset("any");
|
|
231
|
+
setSortOrder("relevance");
|
|
232
|
+
};
|
|
200
233
|
|
|
201
234
|
const loadTemplates = async () => {
|
|
202
235
|
loadDataSource("Canva Brand Templates", {
|
|
203
236
|
schema: "brand_templates/v1",
|
|
237
|
+
query,
|
|
204
238
|
ownership,
|
|
205
239
|
dataset,
|
|
206
240
|
sort_by: sortOrder,
|
|
@@ -251,24 +285,32 @@ function BrandTemplatesSourceConfig(sourceConfig: BrandTemplatesDataSource) {
|
|
|
251
285
|
showBack={true}
|
|
252
286
|
/>
|
|
253
287
|
<Rows spacing="2u">
|
|
254
|
-
<
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
288
|
+
<SearchFilter
|
|
289
|
+
value={query}
|
|
290
|
+
onChange={setQuery}
|
|
291
|
+
filterCount={filterCount}
|
|
292
|
+
resetFilters={resetFilters}
|
|
293
|
+
>
|
|
294
|
+
<SelectField
|
|
295
|
+
label={intl.formatMessage(ownershipFilter.label)}
|
|
296
|
+
options={ownershipOptions}
|
|
297
|
+
value={ownership}
|
|
298
|
+
onChange={setOwnership}
|
|
299
|
+
/>
|
|
300
|
+
<SelectField
|
|
301
|
+
label={intl.formatMessage(datasetFilter.label)}
|
|
302
|
+
options={datasetOptions}
|
|
303
|
+
value={dataset}
|
|
304
|
+
onChange={setDataset}
|
|
305
|
+
/>
|
|
306
|
+
<SelectField
|
|
307
|
+
label={intl.formatMessage(sortOrderField.label)}
|
|
308
|
+
options={sortOrderOptions}
|
|
309
|
+
value={sortOrder}
|
|
310
|
+
onChange={setSortOrder}
|
|
311
|
+
/>
|
|
312
|
+
</SearchFilter>
|
|
313
|
+
|
|
272
314
|
<Button
|
|
273
315
|
variant="primary"
|
|
274
316
|
loading={isLoading}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type {
|
|
2
|
-
|
|
3
|
-
|
|
2
|
+
GetDataTableRequest,
|
|
3
|
+
GetDataTableResponse,
|
|
4
4
|
} from "@canva/intents/data";
|
|
5
5
|
import { DATA_SOURCES } from "./data_sources";
|
|
6
6
|
import {
|
|
@@ -13,16 +13,16 @@ import { DataAPIError } from ".";
|
|
|
13
13
|
|
|
14
14
|
/**
|
|
15
15
|
* This function handles parsing the data fetch parameters and calling the appropriate handler for the data source.
|
|
16
|
-
* @param
|
|
16
|
+
* @param request
|
|
17
17
|
* @param authToken
|
|
18
18
|
* @returns
|
|
19
19
|
*/
|
|
20
20
|
export const buildDataTableResult = async (
|
|
21
|
-
|
|
21
|
+
request: GetDataTableRequest,
|
|
22
22
|
authToken?: string,
|
|
23
|
-
): Promise<
|
|
24
|
-
const source = JSON.parse(
|
|
25
|
-
const rowLimit =
|
|
23
|
+
): Promise<GetDataTableResponse> => {
|
|
24
|
+
const source = JSON.parse(request.dataSourceRef.source);
|
|
25
|
+
const rowLimit = request.limit.row - 1; // -1 for the header row
|
|
26
26
|
|
|
27
27
|
const dataHandler = DATA_SOURCES.find((handler) =>
|
|
28
28
|
handler.matchSource(source),
|
|
@@ -37,8 +37,12 @@ export const buildDataTableResult = async (
|
|
|
37
37
|
source,
|
|
38
38
|
authToken || "",
|
|
39
39
|
rowLimit,
|
|
40
|
-
|
|
40
|
+
request.signal,
|
|
41
41
|
);
|
|
42
|
+
if (dataTable.rows.length === 0) {
|
|
43
|
+
// if the data table is empty, return an error to prompt the user to reconfigure the data source
|
|
44
|
+
return appError("No results found.");
|
|
45
|
+
}
|
|
42
46
|
return completeDataTable(dataTable);
|
|
43
47
|
} catch (error) {
|
|
44
48
|
if (error instanceof DataAPIError) {
|
|
@@ -5,17 +5,13 @@ import { AppUiProvider } from "@canva/app-ui-kit";
|
|
|
5
5
|
import { AppI18nProvider } from "@canva/app-i18n-kit";
|
|
6
6
|
import { ErrorBoundary } from "react-error-boundary";
|
|
7
7
|
import { ErrorPage } from "./pages";
|
|
8
|
-
import type {
|
|
8
|
+
import type { RenderSelectionUiRequest } from "@canva/intents/data";
|
|
9
9
|
|
|
10
|
-
export const App = ({
|
|
11
|
-
dataParams,
|
|
12
|
-
}: {
|
|
13
|
-
dataParams: RenderSelectionUiParams;
|
|
14
|
-
}) => (
|
|
10
|
+
export const App = ({ request }: { request: RenderSelectionUiRequest }) => (
|
|
15
11
|
<AppI18nProvider>
|
|
16
12
|
<AppUiProvider>
|
|
17
13
|
<ErrorBoundary fallback={<ErrorPage />}>
|
|
18
|
-
<ContextProvider
|
|
14
|
+
<ContextProvider renderSelectionUiRequest={request}>
|
|
19
15
|
<RouterProvider router={createHashRouter(routes)} />
|
|
20
16
|
</ContextProvider>
|
|
21
17
|
</ErrorBoundary>
|
|
@@ -78,3 +78,22 @@ export const sortOrderField = defineMessages({
|
|
|
78
78
|
description: "Option for sort order",
|
|
79
79
|
},
|
|
80
80
|
});
|
|
81
|
+
|
|
82
|
+
export const filterMenu = defineMessages({
|
|
83
|
+
search: {
|
|
84
|
+
defaultMessage: "Search",
|
|
85
|
+
description: "Label for a search input field",
|
|
86
|
+
},
|
|
87
|
+
clear: {
|
|
88
|
+
defaultMessage: "Clear all",
|
|
89
|
+
description: "Label for a button to clear all filters",
|
|
90
|
+
},
|
|
91
|
+
apply: {
|
|
92
|
+
defaultMessage: "Apply",
|
|
93
|
+
description: "Label for a button to apply filters",
|
|
94
|
+
},
|
|
95
|
+
count: {
|
|
96
|
+
defaultMessage: "Filter count",
|
|
97
|
+
description: "Label for the number of active filters",
|
|
98
|
+
},
|
|
99
|
+
});
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Badge,
|
|
3
|
+
Box,
|
|
4
|
+
Button,
|
|
5
|
+
Column,
|
|
6
|
+
Columns,
|
|
7
|
+
Flyout,
|
|
8
|
+
Rows,
|
|
9
|
+
SearchInputMenu,
|
|
10
|
+
SlidersIcon,
|
|
11
|
+
} from "@canva/app-ui-kit";
|
|
12
|
+
import { useState } from "react";
|
|
13
|
+
import { useIntl } from "react-intl";
|
|
14
|
+
import { filterMenu } from "./messages";
|
|
15
|
+
|
|
16
|
+
interface SearchFilterProps {
|
|
17
|
+
value: string;
|
|
18
|
+
onChange: (value: string) => void;
|
|
19
|
+
filterCount: number;
|
|
20
|
+
resetFilters: () => void;
|
|
21
|
+
children?: React.ReactNode;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export const SearchFilter = ({
|
|
25
|
+
value,
|
|
26
|
+
onChange,
|
|
27
|
+
filterCount,
|
|
28
|
+
resetFilters,
|
|
29
|
+
children,
|
|
30
|
+
}: SearchFilterProps) => {
|
|
31
|
+
const intl = useIntl();
|
|
32
|
+
const [triggerRef, setTriggerRef] = useState<HTMLDivElement | null>(null);
|
|
33
|
+
const [isFilterMenuOpen, setIsFilterMenuOpen] = useState(false);
|
|
34
|
+
|
|
35
|
+
const onFilterClick = () => {
|
|
36
|
+
setIsFilterMenuOpen(!isFilterMenuOpen);
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const filterButton = (
|
|
40
|
+
<Button
|
|
41
|
+
size="small"
|
|
42
|
+
variant="tertiary"
|
|
43
|
+
icon={SlidersIcon}
|
|
44
|
+
onClick={onFilterClick}
|
|
45
|
+
/>
|
|
46
|
+
);
|
|
47
|
+
return (
|
|
48
|
+
<>
|
|
49
|
+
<Box paddingStart="0.5u">
|
|
50
|
+
<SearchInputMenu
|
|
51
|
+
value={value}
|
|
52
|
+
placeholder={intl.formatMessage(filterMenu.search)}
|
|
53
|
+
onChange={(value) => onChange(value)}
|
|
54
|
+
onClear={() => onChange("")}
|
|
55
|
+
ref={setTriggerRef}
|
|
56
|
+
end={
|
|
57
|
+
filterCount === 0 ? (
|
|
58
|
+
filterButton
|
|
59
|
+
) : (
|
|
60
|
+
<Badge
|
|
61
|
+
tone="assist"
|
|
62
|
+
wrapInset="0"
|
|
63
|
+
shape="circle"
|
|
64
|
+
text={filterCount.toString()}
|
|
65
|
+
ariaLabel={intl.formatMessage(filterMenu.count)}
|
|
66
|
+
>
|
|
67
|
+
{filterButton}
|
|
68
|
+
</Badge>
|
|
69
|
+
)
|
|
70
|
+
}
|
|
71
|
+
/>
|
|
72
|
+
</Box>
|
|
73
|
+
<Flyout
|
|
74
|
+
open={isFilterMenuOpen}
|
|
75
|
+
onRequestClose={() => setIsFilterMenuOpen(false)}
|
|
76
|
+
width="trigger"
|
|
77
|
+
trigger={triggerRef}
|
|
78
|
+
placement="bottom-center"
|
|
79
|
+
footer={
|
|
80
|
+
<Box padding="2u" background="surface">
|
|
81
|
+
<Columns spacing="1u">
|
|
82
|
+
<Column>
|
|
83
|
+
<Button variant="secondary" onClick={resetFilters} stretch>
|
|
84
|
+
{intl.formatMessage(filterMenu.clear)}
|
|
85
|
+
</Button>
|
|
86
|
+
</Column>
|
|
87
|
+
<Column>
|
|
88
|
+
<Button
|
|
89
|
+
variant="primary"
|
|
90
|
+
onClick={() => {
|
|
91
|
+
setIsFilterMenuOpen(false);
|
|
92
|
+
}}
|
|
93
|
+
stretch
|
|
94
|
+
>
|
|
95
|
+
{intl.formatMessage(filterMenu.apply)}
|
|
96
|
+
</Button>
|
|
97
|
+
</Column>
|
|
98
|
+
</Columns>
|
|
99
|
+
</Box>
|
|
100
|
+
}
|
|
101
|
+
>
|
|
102
|
+
<Box padding="2u">
|
|
103
|
+
<Rows spacing="2u">{children}</Rows>
|
|
104
|
+
</Box>
|
|
105
|
+
</Flyout>
|
|
106
|
+
</>
|
|
107
|
+
);
|
|
108
|
+
};
|