@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.
@@ -1,5 +1,5 @@
1
1
  import { createContext, useState, useCallback } from "react";
2
- import type { RenderSelectionUiParams } from "@canva/intents/data";
2
+ import type { RenderSelectionUiRequest } from "@canva/intents/data";
3
3
  import type { AccessTokenResponse } from "@canva/user";
4
4
  import { auth } from "@canva/user";
5
5
  import type { APIResponseItem, DataSourceHandler } from "src/api/data_source";
@@ -8,7 +8,7 @@ import { type DataSourceConfig } from "src/api/data_source";
8
8
  export interface AppContextType {
9
9
  appError: string;
10
10
  setAppError: (value: string) => void;
11
- dataParams: RenderSelectionUiParams;
11
+ request: RenderSelectionUiRequest;
12
12
  isAuthenticated: boolean;
13
13
  accessToken: AccessTokenResponse | undefined;
14
14
  setAccessToken: (token: AccessTokenResponse | undefined) => void;
@@ -27,7 +27,7 @@ export interface AppContextType {
27
27
  export const AppContext = createContext<AppContextType>({
28
28
  appError: "",
29
29
  setAppError: () => {},
30
- dataParams: {} as RenderSelectionUiParams,
30
+ request: {} as RenderSelectionUiRequest,
31
31
  isAuthenticated: false,
32
32
  accessToken: undefined,
33
33
  setAccessToken: () => {},
@@ -51,15 +51,15 @@ export const AppContext = createContext<AppContextType>({
51
51
  * For more information on React Context, refer to the official React documentation: {@link https://react.dev/learn/passing-data-deeply-with-context}.
52
52
  */
53
53
  export const ContextProvider = ({
54
- renderSelectionUiParams,
54
+ renderSelectionUiRequest,
55
55
  children,
56
56
  }: {
57
- renderSelectionUiParams: RenderSelectionUiParams;
57
+ renderSelectionUiRequest: RenderSelectionUiRequest;
58
58
  children: React.ReactNode;
59
59
  }): JSX.Element => {
60
60
  const [appError, setAppError] = useState<string>("");
61
- const [dataParams] = useState<RenderSelectionUiParams>(
62
- renderSelectionUiParams,
61
+ const [request] = useState<RenderSelectionUiRequest>(
62
+ renderSelectionUiRequest,
63
63
  );
64
64
 
65
65
  // authentication
@@ -77,7 +77,7 @@ export const ContextProvider = ({
77
77
  // data connection
78
78
  const loadDataSource = useCallback(
79
79
  async (title: string, source: DataSourceConfig) => {
80
- const result = await dataParams.updateDataRef({
80
+ const result = await request.updateDataRef({
81
81
  title,
82
82
  source: JSON.stringify(source),
83
83
  });
@@ -91,7 +91,7 @@ export const ContextProvider = ({
91
91
  setAppError("");
92
92
  }
93
93
  },
94
- [dataParams],
94
+ [request],
95
95
  );
96
96
 
97
97
  const logout = useCallback(async () => {
@@ -107,7 +107,7 @@ export const ContextProvider = ({
107
107
  const value: AppContextType = {
108
108
  appError,
109
109
  setAppError,
110
- dataParams,
110
+ request,
111
111
  isAuthenticated,
112
112
  accessToken,
113
113
  setAccessToken,
@@ -26,27 +26,24 @@ const parseDataSource = (source: string) => {
26
26
  export const Entrypoint = () => {
27
27
  const navigate = useNavigate();
28
28
  const context = useAppContext();
29
- const { dataParams, setAppError, setDataSourceHandler } = context;
29
+ const { request, setAppError, setDataSourceHandler } = context;
30
30
 
31
31
  useEffect(() => {
32
32
  // if the app was loaded with a populated data ref, we should reload the previous state.
33
33
  // otherwise, if this is a first launch or there is an error, we should navigate to the first screen for a user - selecting a data source
34
34
  let navigateTo: Paths | undefined;
35
35
 
36
- if (isDataRefEmpty(dataParams)) {
36
+ if (isDataRefEmpty(request)) {
37
37
  // probably a first time launch - the user will need to select a data source
38
38
  navigateTo = Paths.DATA_SOURCE_SELECTION;
39
- } else if (
40
- isOutdatedSource(dataParams) ||
41
- isLaunchedWithError(dataParams)
42
- ) {
39
+ } else if (isOutdatedSource(request) || isLaunchedWithError(request)) {
43
40
  // the configured source does not match the expected data source types
44
41
  // so prompt the user to reconfigure the data source
45
42
  setAppError("The data source configuration needs to be updated.");
46
43
  navigateTo = Paths.DATA_SOURCE_SELECTION;
47
44
  } else {
48
45
  // there is a data ref, so we should parse it and navigate to the appropriate page
49
- const dataRef = dataParams.invocationContext.dataSourceRef;
46
+ const dataRef = request.invocationContext.dataSourceRef;
50
47
  const parsedSource = parseDataSource(dataRef?.source ?? "");
51
48
 
52
49
  if (parsedSource) {
@@ -67,7 +64,7 @@ export const Entrypoint = () => {
67
64
  }
68
65
 
69
66
  navigate(navigateTo || Paths.DATA_SOURCE_SELECTION);
70
- }, [dataParams]);
67
+ }, [request]);
71
68
 
72
69
  return <LoadingIndicator />;
73
70
  };
@@ -1,7 +1,7 @@
1
1
  import type {
2
- RenderSelectionUiParams,
3
- FetchDataTableParams,
4
- FetchDataTableResult,
2
+ RenderSelectionUiRequest,
3
+ GetDataTableRequest,
4
+ GetDataTableResponse,
5
5
  } from "@canva/intents/data";
6
6
  import { prepareDataConnector } from "@canva/intents/data";
7
7
  import { createRoot } from "react-dom/client";
@@ -19,15 +19,15 @@ prepareDataConnector({
19
19
  *
20
20
  * This action is called in two scenarios:
21
21
  *
22
- * - During data selection to preview data before import (when {@link RenderSelectionUiParams.updateDataRef} is called).
22
+ * - During data selection to preview data before import (when {@link RenderSelectionUiRequest.updateDataRef} is called).
23
23
  * - When refreshing previously imported data (when the user requests an update).
24
24
  *
25
25
  * @param params - Parameters for the data fetching operation.
26
26
  * @returns A promise resolving to either a successful result with data or an error.
27
27
  */
28
- fetchDataTable: async (
29
- params: FetchDataTableParams,
30
- ): Promise<FetchDataTableResult> => {
28
+ getDataTable: async (
29
+ params: GetDataTableRequest,
30
+ ): Promise<GetDataTableResponse> => {
31
31
  const oauth = auth.initOauth();
32
32
  const token = await oauth.getAccessToken({ scope });
33
33
  return buildDataTableResult(params, token?.token);
@@ -39,12 +39,12 @@ prepareDataConnector({
39
39
  * When selection is complete, the implementation must call the `updateDataRef`
40
40
  * callback provided in the params to preview and confirm the data selection.
41
41
  *
42
- * @param params - parameters that provide context and configuration for the data selection UI.
42
+ * @param request - parameters that provide context and configuration for the data selection UI.
43
43
  * Contains invocation context, size limits, and the updateDataRef callback
44
44
  */
45
- renderSelectionUi: async (params: RenderSelectionUiParams) => {
45
+ renderSelectionUi: async (request: RenderSelectionUiRequest) => {
46
46
  function render() {
47
- root.render(<App dataParams={params} />);
47
+ root.render(<App request={request} />);
48
48
  }
49
49
 
50
50
  render();
@@ -1,17 +1,17 @@
1
- import type { RenderSelectionUiParams } from "@canva/intents/data";
1
+ import type { RenderSelectionUiRequest } from "@canva/intents/data";
2
2
 
3
- export const isLaunchedWithError = (dataParams: RenderSelectionUiParams) => {
4
- return dataParams.invocationContext.reason === "app_error";
3
+ export const isLaunchedWithError = (request: RenderSelectionUiRequest) => {
4
+ return request.invocationContext.reason === "app_error";
5
5
  };
6
6
 
7
- export const isOutdatedSource = (dataParams: RenderSelectionUiParams) => {
8
- return dataParams.invocationContext.reason === "outdated_source_ref";
7
+ export const isOutdatedSource = (request: RenderSelectionUiRequest) => {
8
+ return request.invocationContext.reason === "outdated_source_ref";
9
9
  };
10
10
 
11
- export const isDataRefEmpty = (dataParams: RenderSelectionUiParams) => {
11
+ export const isDataRefEmpty = (request: RenderSelectionUiRequest) => {
12
12
  return (
13
- !dataParams?.invocationContext ||
14
- (!isLaunchedWithError(dataParams) &&
15
- !dataParams.invocationContext.dataSourceRef?.source)
13
+ !request?.invocationContext ||
14
+ (!isLaunchedWithError(request) &&
15
+ !request.invocationContext.dataSourceRef?.source)
16
16
  );
17
17
  };
@@ -5,6 +5,7 @@ import type {
5
5
  DateDataTableCell,
6
6
  NumberDataTableCell,
7
7
  StringDataTableCell,
8
+ ColumnConfig,
8
9
  } from "@canva/intents/data";
9
10
  import type { APIResponseItem } from "src/api";
10
11
 
@@ -20,9 +21,9 @@ export function toDataTable<T extends APIResponseItem>(
20
21
  rowLimit: number,
21
22
  ): DataTable {
22
23
  const items = apiData.slice(0, rowLimit);
23
- const headerRow = columns.map((column) => stringCell(column.label));
24
24
  const dataTable: DataTable = {
25
- rows: [{ cells: headerRow }],
25
+ columnConfigs: columnConfig(columns),
26
+ rows: [],
26
27
  };
27
28
  items.forEach((item) => {
28
29
  const cells = columns.map((column) => {
@@ -37,6 +38,20 @@ export function toDataTable<T extends APIResponseItem>(
37
38
  return dataTable;
38
39
  }
39
40
 
41
+ /**
42
+ * Converts an array of DataTableColumn to ColumnConfig.
43
+ * @param columns Array of DataTableColumn
44
+ * @returns Array of ColumnConfig
45
+ */
46
+ function columnConfig<T extends APIResponseItem>(
47
+ columns: DataTableColumn<T>[],
48
+ ): ColumnConfig[] {
49
+ return columns.map((column) => ({
50
+ name: column.label,
51
+ type: column.toCell({} as unknown).type, // Use an empty object to infer the type
52
+ }));
53
+ }
54
+
40
55
  /**
41
56
  * Creates a string cell for the data table.
42
57
  * @param value String containing up to 10,000 characters
@@ -1,14 +1,14 @@
1
1
  import type {
2
2
  DataTable,
3
3
  DataTableMetadata,
4
- FetchDataTableError,
5
- FetchDataTableCompleted,
4
+ GetDataTableError,
5
+ GetDataTableCompleted,
6
6
  } from "@canva/intents/data";
7
7
 
8
8
  export const completeDataTable = (
9
9
  dataTable: DataTable,
10
10
  metadata?: DataTableMetadata,
11
- ): FetchDataTableCompleted => {
11
+ ): GetDataTableCompleted => {
12
12
  return {
13
13
  status: "completed",
14
14
  dataTable,
@@ -16,20 +16,20 @@ export const completeDataTable = (
16
16
  };
17
17
  };
18
18
 
19
- export const appError = (message?: string): FetchDataTableError => {
19
+ export const appError = (message?: string): GetDataTableError => {
20
20
  return {
21
21
  status: "app_error",
22
22
  message,
23
23
  };
24
24
  };
25
25
 
26
- export const outdatedSourceRef = (): FetchDataTableError => {
26
+ export const outdatedSourceRef = (): GetDataTableError => {
27
27
  return {
28
28
  status: "outdated_source_ref",
29
29
  };
30
30
  };
31
31
 
32
- export const remoteRequestFailed = (): FetchDataTableError => {
32
+ export const remoteRequestFailed = (): GetDataTableError => {
33
33
  return {
34
34
  status: "remote_request_failed",
35
35
  };
@@ -68,11 +68,11 @@ describe("data table utils", () => {
68
68
  },
69
69
  ];
70
70
 
71
- test("toDataTable creates correct structure with header row", () => {
71
+ test("toDataTable creates correct structure", () => {
72
72
  const result = toDataTable(testItems, columns, 10);
73
73
 
74
- expect(result.rows.length).toBe(3); // header + 2 data rows
75
- expect(result.rows[0].cells.map((cell) => cell.value)).toEqual([
74
+ expect(result.rows.length).toBe(testItems.length);
75
+ expect(result.columnConfigs?.map((cell) => cell.name)).toEqual([
76
76
  "ID",
77
77
  "Name",
78
78
  "Count",
@@ -85,15 +85,15 @@ describe("data table utils", () => {
85
85
  test("toDataTable respects row limit", () => {
86
86
  const result = toDataTable(testItems, columns, 1);
87
87
 
88
- expect(result.rows.length).toBe(2); // header + 1 data row (limited)
88
+ expect(result.rows.length).toBe(1);
89
89
  });
90
90
 
91
91
  test("toDataTable handles function-based getValue", () => {
92
92
  const result = toDataTable(testItems, columns, 10);
93
93
 
94
94
  // Check the custom column values
95
- expect(result.rows[1].cells[5].value).toBe("Test Item 1 (42)");
96
- expect(result.rows[2].cells[5].value).toBe("Test Item 2 (0)");
95
+ expect(result.rows[0].cells[5].value).toBe("Test Item 1 (42)");
96
+ expect(result.rows[1].cells[5].value).toBe("Test Item 2 (0)");
97
97
  });
98
98
 
99
99
  test("cell formatter functions create correct cell structures", () => {
@@ -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: "localhost",
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",
@@ -19,11 +19,11 @@
19
19
  },
20
20
  "dependencies": {
21
21
  "@canva/app-i18n-kit": "^1.0.2",
22
- "@canva/app-ui-kit": "^4.9.0",
22
+ "@canva/app-ui-kit": "^4.10.0",
23
23
  "@canva/asset": "^2.2.0",
24
- "@canva/design": "^2.4.1",
24
+ "@canva/design": "^2.6.0",
25
25
  "@canva/error": "^2.1.0",
26
- "@canva/platform": "^2.1.0",
26
+ "@canva/platform": "^2.2.0",
27
27
  "@canva/user": "^2.1.0",
28
28
  "cookie-parser": "1.4.7",
29
29
  "cors": "2.8.5",
@@ -93,7 +93,7 @@
93
93
  "url-loader": "4.1.1",
94
94
  "webpack": "5.97.1",
95
95
  "webpack-cli": "5.1.4",
96
- "webpack-dev-server": "5.2.0",
96
+ "webpack-dev-server": "5.2.2",
97
97
  "yargs": "17.7.2"
98
98
  }
99
99
  }
@@ -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: "localhost",
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
  },
21
21
  "dependencies": {
22
22
  "@canva/app-i18n-kit": "^1.0.2",
23
- "@canva/app-ui-kit": "^4.9.0",
23
+ "@canva/app-ui-kit": "^4.10.0",
24
24
  "@canva/asset": "^2.2.0",
25
- "@canva/design": "^2.4.1",
25
+ "@canva/design": "^2.6.0",
26
26
  "@canva/error": "^2.1.0",
27
- "@canva/platform": "^2.1.0",
27
+ "@canva/platform": "^2.2.0",
28
28
  "@canva/user": "^2.1.0",
29
29
  "react": "18.3.1",
30
30
  "react-dom": "18.3.1",
@@ -82,7 +82,7 @@
82
82
  "url-loader": "4.1.1",
83
83
  "webpack": "5.97.1",
84
84
  "webpack-cli": "5.1.4",
85
- "webpack-dev-server": "5.2.0",
85
+ "webpack-dev-server": "5.2.2",
86
86
  "yargs": "17.7.2"
87
87
  }
88
88
  }
@@ -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: "localhost",
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",