@canva/cli 1.8.0 → 1.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/cli.js +266 -366
  3. package/lib/cjs/index.cjs +6 -6
  4. package/lib/esm/index.mjs +6 -6
  5. package/package.json +1 -1
  6. package/templates/base/backend/database/database.ts +2 -2
  7. package/templates/base/backend/routers/auth.ts +5 -5
  8. package/templates/base/backend/routers/oauth.ts +4 -4
  9. package/templates/base/package.json +1 -2
  10. package/templates/base/scripts/copy_env.ts +2 -2
  11. package/templates/base/scripts/ssl/ssl.ts +3 -3
  12. package/templates/base/scripts/start/app_runner.ts +29 -7
  13. package/templates/base/scripts/start/context.ts +2 -2
  14. package/templates/base/scripts/start/start.ts +1 -1
  15. package/templates/base/scripts/start.tests.ts +1 -1
  16. package/templates/base/tsconfig.json +7 -4
  17. package/templates/base/utils/backend/base_backend/create.ts +7 -7
  18. package/templates/base/utils/backend/bearer_middleware/bearer_middleware.ts +3 -5
  19. package/templates/base/utils/backend/jwt_middleware/jwt_middleware.ts +5 -10
  20. package/templates/base/utils/use_add_element.ts +10 -0
  21. package/templates/base/webpack.config.ts +4 -4
  22. package/templates/common/utils/backend/base_backend/create.ts +7 -7
  23. package/templates/common/utils/backend/jwt_middleware/jwt_middleware.ts +5 -10
  24. package/templates/common/utils/table_wrapper.ts +80 -37
  25. package/templates/common/utils/use_add_element.ts +10 -0
  26. package/templates/common/utils/use_overlay_hook.ts +6 -4
  27. package/templates/dam/backend/routers/dam.ts +37 -19
  28. package/templates/dam/backend/server.ts +2 -2
  29. package/templates/dam/package.json +1 -2
  30. package/templates/dam/scripts/copy_env.ts +2 -2
  31. package/templates/dam/scripts/ssl/ssl.ts +3 -3
  32. package/templates/dam/scripts/start/app_runner.ts +29 -7
  33. package/templates/dam/scripts/start/context.ts +2 -2
  34. package/templates/dam/scripts/start/start.ts +1 -1
  35. package/templates/dam/tsconfig.json +7 -4
  36. package/templates/dam/utils/backend/base_backend/create.ts +7 -7
  37. package/templates/dam/utils/backend/jwt_middleware/jwt_middleware.ts +5 -10
  38. package/templates/dam/webpack.config.ts +4 -4
  39. package/templates/data_connector/package.json +2 -2
  40. package/templates/data_connector/scripts/copy_env.ts +2 -2
  41. package/templates/data_connector/scripts/ssl/ssl.ts +3 -3
  42. package/templates/data_connector/scripts/start/app_runner.ts +29 -7
  43. package/templates/data_connector/scripts/start/context.ts +2 -2
  44. package/templates/data_connector/scripts/start/start.ts +1 -1
  45. package/templates/data_connector/src/utils/data_table.ts +2 -1
  46. package/templates/data_connector/src/utils/tests/data_table.test.ts +2 -2
  47. package/templates/data_connector/tsconfig.json +7 -4
  48. package/templates/data_connector/webpack.config.ts +4 -4
  49. package/templates/gen_ai/backend/database/database.ts +2 -2
  50. package/templates/gen_ai/backend/routers/image.ts +5 -7
  51. package/templates/gen_ai/backend/server.ts +2 -2
  52. package/templates/gen_ai/package.json +2 -2
  53. package/templates/gen_ai/scripts/copy_env.ts +2 -2
  54. package/templates/gen_ai/scripts/ssl/ssl.ts +3 -3
  55. package/templates/gen_ai/scripts/start/app_runner.ts +29 -7
  56. package/templates/gen_ai/scripts/start/context.ts +2 -2
  57. package/templates/gen_ai/scripts/start/start.ts +1 -1
  58. package/templates/gen_ai/src/components/image_grid.tsx +6 -2
  59. package/templates/gen_ai/src/components/prompt_input.tsx +4 -1
  60. package/templates/gen_ai/tsconfig.json +7 -4
  61. package/templates/gen_ai/utils/backend/base_backend/create.ts +7 -7
  62. package/templates/gen_ai/utils/backend/bearer_middleware/bearer_middleware.ts +3 -5
  63. package/templates/gen_ai/webpack.config.ts +4 -4
  64. package/templates/hello_world/package.json +2 -2
  65. package/templates/hello_world/scripts/copy_env.ts +2 -2
  66. package/templates/hello_world/scripts/ssl/ssl.ts +3 -3
  67. package/templates/hello_world/scripts/start/app_runner.ts +29 -7
  68. package/templates/hello_world/scripts/start/context.ts +2 -2
  69. package/templates/hello_world/scripts/start/start.ts +1 -1
  70. package/templates/hello_world/src/tests/app.tests.tsx +1 -1
  71. package/templates/hello_world/tsconfig.json +7 -4
  72. package/templates/hello_world/utils/use_add_element.ts +10 -0
  73. package/templates/hello_world/webpack.config.ts +4 -4
@@ -1,10 +1,10 @@
1
1
  /* eslint-disable no-console */
2
2
  import debug from "debug";
3
- import * as express from "express";
3
+ import express from "express";
4
4
  import type { NextFunction, Request, Response } from "express";
5
- import * as fs from "fs";
6
- import * as http from "http";
7
- import * as https from "https";
5
+ import fs from "fs";
6
+ import http from "http";
7
+ import https from "https";
8
8
 
9
9
  const serverDebug = debug("server");
10
10
 
@@ -41,12 +41,12 @@ export function createBaseServer(router: express.Router): BaseServer {
41
41
  app.disable("x-powered-by");
42
42
 
43
43
  // Health check endpoint
44
- app.get("/healthz", (req, res: Response) => {
44
+ app.get("/healthz", (req, res) => {
45
45
  res.sendStatus(200);
46
46
  });
47
47
 
48
48
  // logging middleware
49
- app.use((req: Request, res: Response, next: NextFunction) => {
49
+ app.use((req, _res, next) => {
50
50
  serverDebug(`${new Date().toISOString()}: ${req.method} ${req.url}`);
51
51
  next();
52
52
  });
@@ -62,7 +62,7 @@ export function createBaseServer(router: express.Router): BaseServer {
62
62
  });
63
63
 
64
64
  // default error handler
65
- app.use((err, req, res, next) => {
65
+ app.use((err: Error, _req: Request, res: Response, _next: NextFunction) => {
66
66
  console.error(err.stack);
67
67
  res.status(500).send({
68
68
  error: "something went wrong",
@@ -1,8 +1,6 @@
1
1
  /* eslint-disable no-console */
2
- import * as debug from "debug";
2
+ import debug from "debug";
3
3
  import type { NextFunction, Request, Response } from "express";
4
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
5
- import Express from "express-serve-static-core";
6
4
 
7
5
  /**
8
6
  * Prefix your start command with `DEBUG=express:middleware:bearer` to enable debug logging
@@ -47,13 +45,13 @@ export function createBearerMiddleware(
47
45
 
48
46
  req["user_id"] = user;
49
47
 
50
- next();
48
+ return next();
51
49
  } catch (e) {
52
50
  if (e instanceof AuthorizationError) {
53
51
  return sendUnauthorizedResponse(res, e.message);
54
52
  }
55
53
 
56
- next(e);
54
+ return next(e);
57
55
  }
58
56
  };
59
57
  }
@@ -1,10 +1,7 @@
1
1
  /* eslint-disable no-console */
2
- import * as chalk from "chalk";
3
- import * as debug from "debug";
2
+ import debug from "debug";
4
3
  import type { NextFunction, Request, Response } from "express";
5
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
6
- import Express from "express-serve-static-core";
7
- import * as jwt from "jsonwebtoken";
4
+ import jwt from "jsonwebtoken";
8
5
  import { JwksClient, SigningKeyNotFoundError } from "jwks-rsa";
9
6
 
10
7
  /**
@@ -116,7 +113,7 @@ export function createJwtMiddleware(
116
113
  userId: payload.userId,
117
114
  };
118
115
 
119
- next();
116
+ return next();
120
117
  } catch (e) {
121
118
  if (e instanceof JWTAuthorizationError) {
122
119
  return sendUnauthorizedResponse(res, e.message);
@@ -125,9 +122,7 @@ export function createJwtMiddleware(
125
122
  if (e instanceof SigningKeyNotFoundError) {
126
123
  return sendUnauthorizedResponse(
127
124
  res,
128
- `Public key not found. ${chalk.bgRedBright(
129
- "Ensure you have the correct App_ID set",
130
- )}.`,
125
+ `Public key not found. Ensure you have the correct App_ID set`,
131
126
  );
132
127
  }
133
128
 
@@ -139,7 +134,7 @@ export function createJwtMiddleware(
139
134
  return sendUnauthorizedResponse(res, "Token expired");
140
135
  }
141
136
 
142
- next(e);
137
+ return next(e);
143
138
  }
144
139
  };
145
140
  }
@@ -30,6 +30,11 @@ export const useAddElement = () => {
30
30
  } else if (features.isSupported(addElementAtCursor)) {
31
31
  return addElementAtCursor(element);
32
32
  }
33
+ // eslint-disable-next-line no-console
34
+ console.warn(
35
+ "Neither addElementAtPoint nor addElementAtCursor are supported",
36
+ );
37
+ return Promise.resolve();
33
38
  };
34
39
  });
35
40
 
@@ -40,6 +45,11 @@ export const useAddElement = () => {
40
45
  } else if (isSupported(addElementAtCursor)) {
41
46
  return addElementAtCursor(element);
42
47
  }
48
+ // eslint-disable-next-line no-console
49
+ console.warn(
50
+ "Neither addElementAtPoint nor addElementAtCursor are supported",
51
+ );
52
+ return Promise.resolve();
43
53
  };
44
54
  setAddElement(() => addElement);
45
55
  }, [isSupported]);
@@ -1,9 +1,9 @@
1
1
  import type { Configuration } from "webpack";
2
2
  import { DefinePlugin, optimize } from "webpack";
3
- import * as path from "path";
4
- import * as TerserPlugin from "terser-webpack-plugin";
3
+ import path from "path";
4
+ import TerserPlugin from "terser-webpack-plugin";
5
5
  import { transform } from "@formatjs/ts-transformer";
6
- import * as chalk from "chalk";
6
+ import chalk from "chalk";
7
7
  import { config } from "dotenv";
8
8
  import { Configuration as DevServerConfiguration } from "webpack-dev-server";
9
9
 
@@ -196,7 +196,7 @@ function buildDevConfig(options?: DevConfig): {
196
196
  return {};
197
197
  }
198
198
 
199
- const { port, enableHmr, appOrigin, appId, enableHttps, certFile, keyFile } =
199
+ const { port, enableHmr, appOrigin, enableHttps, certFile, keyFile } =
200
200
  options;
201
201
  const host = "localhost";
202
202
 
@@ -1,10 +1,10 @@
1
1
  /* eslint-disable no-console */
2
2
  import debug from "debug";
3
- import * as express from "express";
3
+ import express from "express";
4
4
  import type { NextFunction, Request, Response } from "express";
5
- import * as fs from "fs";
6
- import * as http from "http";
7
- import * as https from "https";
5
+ import fs from "fs";
6
+ import http from "http";
7
+ import https from "https";
8
8
 
9
9
  const serverDebug = debug("server");
10
10
 
@@ -41,12 +41,12 @@ export function createBaseServer(router: express.Router): BaseServer {
41
41
  app.disable("x-powered-by");
42
42
 
43
43
  // Health check endpoint
44
- app.get("/healthz", (req, res: Response) => {
44
+ app.get("/healthz", (req, res) => {
45
45
  res.sendStatus(200);
46
46
  });
47
47
 
48
48
  // logging middleware
49
- app.use((req: Request, res: Response, next: NextFunction) => {
49
+ app.use((req, _res, next) => {
50
50
  serverDebug(`${new Date().toISOString()}: ${req.method} ${req.url}`);
51
51
  next();
52
52
  });
@@ -62,7 +62,7 @@ export function createBaseServer(router: express.Router): BaseServer {
62
62
  });
63
63
 
64
64
  // default error handler
65
- app.use((err, req, res, next) => {
65
+ app.use((err: Error, _req: Request, res: Response, _next: NextFunction) => {
66
66
  console.error(err.stack);
67
67
  res.status(500).send({
68
68
  error: "something went wrong",
@@ -1,10 +1,7 @@
1
1
  /* eslint-disable no-console */
2
- import * as chalk from "chalk";
3
- import * as debug from "debug";
2
+ import debug from "debug";
4
3
  import type { NextFunction, Request, Response } from "express";
5
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
6
- import Express from "express-serve-static-core";
7
- import * as jwt from "jsonwebtoken";
4
+ import jwt from "jsonwebtoken";
8
5
  import { JwksClient, SigningKeyNotFoundError } from "jwks-rsa";
9
6
 
10
7
  /**
@@ -116,7 +113,7 @@ export function createJwtMiddleware(
116
113
  userId: payload.userId,
117
114
  };
118
115
 
119
- next();
116
+ return next();
120
117
  } catch (e) {
121
118
  if (e instanceof JWTAuthorizationError) {
122
119
  return sendUnauthorizedResponse(res, e.message);
@@ -125,9 +122,7 @@ export function createJwtMiddleware(
125
122
  if (e instanceof SigningKeyNotFoundError) {
126
123
  return sendUnauthorizedResponse(
127
124
  res,
128
- `Public key not found. ${chalk.bgRedBright(
129
- "Ensure you have the correct App_ID set",
130
- )}.`,
125
+ `Public key not found. Ensure you have the correct App_ID set`,
131
126
  );
132
127
  }
133
128
 
@@ -139,7 +134,7 @@ export function createJwtMiddleware(
139
134
  return sendUnauthorizedResponse(res, "Token expired");
140
135
  }
141
136
 
142
- next(e);
137
+ return next(e);
143
138
  }
144
139
  };
145
140
  }
@@ -19,9 +19,10 @@ export class TableWrapper {
19
19
  }[],
20
20
  ) {
21
21
  this.validateRowColumn();
22
- this.metaCells = Array.from({ length: this.rows.length }, () =>
23
- Array.from({ length: this.rows[0].cells.length }, () => ({})),
24
- );
22
+ this.metaCells = [];
23
+ for (const row of this.rows) {
24
+ this.metaCells.push(Array.from({ length: row.cells.length }, () => ({})));
25
+ }
25
26
  this.syncMergedCellsFromRows();
26
27
  }
27
28
 
@@ -94,16 +95,14 @@ export class TableWrapper {
94
95
 
95
96
  this.validateRowColumn(1, 0);
96
97
 
98
+ const columnLength = this.rows[0]?.cells.length || 0;
97
99
  const newRow = {
98
- cells: Array.from(
99
- { length: this.rows[0].cells.length },
100
- () => ({}) as Cell,
101
- ),
100
+ cells: Array.from({ length: columnLength }, () => ({}) as Cell),
102
101
  };
103
102
  this.rows.splice(afterRowPos, 0, newRow);
104
103
 
105
104
  const newMergeCells: MetaCell[] = Array.from(
106
- { length: this.rows[0].cells.length },
105
+ { length: columnLength },
107
106
  () => ({}),
108
107
  );
109
108
  this.metaCells.splice(afterRowPos, 0, newMergeCells);
@@ -134,22 +133,24 @@ export class TableWrapper {
134
133
  * column is inserted between them, the new column will also have the same background color.
135
134
  */
136
135
  addColumn(afterColumnPos: number) {
137
- if (afterColumnPos < 0 || afterColumnPos > this.rows[0].cells.length) {
136
+ const columnLength = this.rows[0]?.cells.length || 0;
137
+ if (afterColumnPos < 0 || afterColumnPos > columnLength) {
138
138
  throw new TableValidationError(
139
- `New column position must be between 0 and ${this.rows[0].cells.length}.`,
139
+ `New column position must be between 0 and ${columnLength}.`,
140
140
  );
141
141
  }
142
142
 
143
143
  this.validateRowColumn(0, 1);
144
144
 
145
145
  this.rows.forEach((row) => row.cells.splice(afterColumnPos, 0, null));
146
+ const newColumnLength = this.rows[0]?.cells.length || 0;
146
147
 
147
148
  const newMergeCell: MetaCell = {};
148
149
  this.metaCells.forEach((row) =>
149
150
  row.splice(afterColumnPos, 0, newMergeCell),
150
151
  );
151
152
 
152
- if (0 < afterColumnPos && afterColumnPos < this.rows[0].cells.length) {
153
+ if (0 < afterColumnPos && afterColumnPos < newColumnLength) {
153
154
  // Insert in between columns
154
155
  for (let i = 0; i < this.rows.length; i++) {
155
156
  this.mayCopyStyles({
@@ -178,7 +179,7 @@ export class TableWrapper {
178
179
  this.validateCellBoundaries(rowPos, columnPos);
179
180
  const rowIndex = rowPos - 1;
180
181
  const columnIndex = columnPos - 1;
181
- const { mergedInto } = this.metaCells[rowIndex][columnIndex];
182
+ const { mergedInto } = this.getMetaCell(rowIndex, columnIndex);
182
183
  if (!mergedInto) {
183
184
  // Not belongs to any merged cell
184
185
  return false;
@@ -199,7 +200,15 @@ export class TableWrapper {
199
200
  `The cell at ${rowPos},${columnPos} is squashed into another cell`,
200
201
  );
201
202
  }
202
- return this.rows[rowPos - 1].cells[columnPos - 1];
203
+ return this.getCellInternal(rowPos - 1, columnPos - 1);
204
+ }
205
+
206
+ getCellInternal(rowIdx: number, columnIdx: number) {
207
+ const row = this.rows[rowIdx];
208
+ if (!row) {
209
+ throw new Error("validateCellBoundaries should be called first");
210
+ }
211
+ return row.cells[columnIdx];
203
212
  }
204
213
 
205
214
  /**
@@ -222,8 +231,12 @@ export class TableWrapper {
222
231
  const rowIndex = rowPos - 1;
223
232
  const columnIndex = columnPos - 1;
224
233
  const { rowSpan: oldRowSpan, colSpan: oldColSpan } =
225
- this.rows[rowIndex].cells[columnIndex] || {};
226
- this.rows[rowIndex].cells[columnIndex] = details;
234
+ this.getCellInternal(rowIndex, columnIndex) || {};
235
+
236
+ const row = this.rows[rowIndex];
237
+ if (row) {
238
+ row.cells[columnIndex] = details;
239
+ }
227
240
 
228
241
  if (oldRowSpan !== rowSpan || oldColSpan !== colSpan) {
229
242
  this.syncMergedCellsFromRows();
@@ -232,10 +245,11 @@ export class TableWrapper {
232
245
 
233
246
  private validateRowColumn(toBeAddedRow = 0, toBeAddedColumn = 0) {
234
247
  const rowCount = this.rows.length + toBeAddedRow;
235
- const columnCount = this.rows[0].cells.length + toBeAddedColumn;
236
- if (rowCount === 0) {
248
+ const row = this.rows[0];
249
+ if (rowCount === 0 || !row) {
237
250
  throw new TableValidationError("Table must have at least one row.");
238
251
  }
252
+ const columnCount = row.cells.length + toBeAddedColumn;
239
253
  if (columnCount === 0) {
240
254
  throw new TableValidationError("Table must have at least one column.");
241
255
  }
@@ -267,6 +281,9 @@ export class TableWrapper {
267
281
  // Then loop through this.rows to find any merged cells
268
282
  for (let rowIndex = 0; rowIndex < this.rows.length; rowIndex++) {
269
283
  const row = this.rows[rowIndex];
284
+ if (!row) {
285
+ continue;
286
+ }
270
287
  for (let columnIndex = 0; columnIndex < row.cells.length; columnIndex++) {
271
288
  const cell = row.cells[columnIndex] || { colSpan: 1, rowSpan: 1 };
272
289
  const colSpan = cell.colSpan || 1;
@@ -304,7 +321,7 @@ export class TableWrapper {
304
321
  ) {
305
322
  for (let row = fromRow; row <= toRow; row++) {
306
323
  for (let column = fromColumn; column <= toColumn; column++) {
307
- const metaCell = this.metaCells[row][column];
324
+ const metaCell = this.getMetaCell(row, column);
308
325
 
309
326
  if (metaCell.mergedInto) {
310
327
  // This cell may be squashed by another merge cell
@@ -344,39 +361,57 @@ export class TableWrapper {
344
361
  currentColumnIdx: number;
345
362
  }) {
346
363
  // Continue span if both front and behind cells belong to a same merged cell
347
- const frontMergedCell =
348
- this.metaCells[frontRowIdx][frontColumnIdx].mergedInto;
349
- const behindMergedCell =
350
- this.metaCells[behindRowIdx][behindColumnIdx].mergedInto;
364
+ const frontMergedCell = this.getMetaCell(
365
+ frontRowIdx,
366
+ frontColumnIdx,
367
+ ).mergedInto;
368
+ const behindMergedCell = this.getMetaCell(
369
+ behindRowIdx,
370
+ behindColumnIdx,
371
+ ).mergedInto;
351
372
  if (
352
373
  frontMergedCell &&
353
374
  frontMergedCell.row === behindMergedCell?.row &&
354
375
  frontMergedCell.column === behindMergedCell?.column
355
376
  ) {
356
- this.metaCells[currentRowIdx][currentColumnIdx].mergedInto = {
377
+ this.getMetaCell(currentRowIdx, currentColumnIdx).mergedInto = {
357
378
  ...frontMergedCell,
358
379
  };
359
380
  }
360
381
 
361
382
  // Copy attributes if both front and behind cells are the same
362
- const frontCell = this.rows[frontRowIdx].cells[frontColumnIdx];
363
- const behindCell = this.rows[behindRowIdx].cells[behindColumnIdx];
383
+ const frontCell = this.getCellInternal(frontRowIdx, frontColumnIdx);
384
+ const behindCell = this.getCellInternal(behindRowIdx, behindColumnIdx);
364
385
  if (
365
386
  frontCell != null &&
366
387
  behindCell != null &&
367
388
  frontCell.attributes &&
368
389
  behindCell.attributes
369
390
  ) {
370
- let currentCell = this.rows[currentRowIdx].cells[currentColumnIdx];
371
- for (const key of Object.keys(frontCell.attributes)) {
391
+ let currentCell = this.getCellInternal(currentRowIdx, currentColumnIdx);
392
+ const attrs = Object.keys(
393
+ frontCell.attributes,
394
+ ) as (keyof Cell["attributes"])[];
395
+ for (const key of attrs) {
372
396
  if (frontCell.attributes[key] === behindCell.attributes[key]) {
373
397
  currentCell = currentCell || { type: "empty" };
374
398
  currentCell.attributes = currentCell.attributes || {};
375
399
  currentCell.attributes[key] = frontCell.attributes[key];
376
400
  }
377
401
  }
378
- this.rows[currentRowIdx].cells[currentColumnIdx] = currentCell;
402
+ const targetRow = this.rows[currentRowIdx];
403
+ if (currentCell && targetRow) {
404
+ targetRow.cells[currentColumnIdx] = currentCell;
405
+ }
406
+ }
407
+ }
408
+
409
+ private getMetaCell(rowIdx: number, columnIdx: number): MetaCell {
410
+ const metaCell = this.metaCells[rowIdx]?.[columnIdx];
411
+ if (!metaCell) {
412
+ throw new Error("MetaCells does not match the table dimension");
379
413
  }
414
+ return metaCell;
380
415
  }
381
416
 
382
417
  /**
@@ -386,13 +421,17 @@ export class TableWrapper {
386
421
  private syncCellSpansFromMetaCells() {
387
422
  const groups = new Map<string, { row: number; column: number }[]>();
388
423
  for (let row = 0; row < this.metaCells.length; row++) {
389
- for (let column = 0; column < this.metaCells[row].length; column++) {
424
+ const metaRow = this.metaCells[row];
425
+ if (!metaRow) {
426
+ continue;
427
+ }
428
+ for (let column = 0; column < metaRow.length; column++) {
390
429
  // Reset all rowSpans and colSpans
391
- const currentCell = this.rows[row].cells[column];
430
+ const currentCell = this.getCellInternal(row, column);
392
431
  currentCell && delete currentCell.rowSpan;
393
432
  currentCell && delete currentCell.colSpan;
394
433
 
395
- const mergedCell = this.metaCells[row][column];
434
+ const mergedCell = this.getMetaCell(row, column);
396
435
  if (!mergedCell.mergedInto) {
397
436
  continue;
398
437
  }
@@ -429,12 +468,15 @@ export class TableWrapper {
429
468
  const rowSpan = maxRow - minRow + 1;
430
469
  const columnSpan = maxColumn - minColumn + 1;
431
470
  if (rowSpan > 1 || columnSpan > 1) {
432
- const currentCell = this.rows[minRow].cells[minColumn] || {
471
+ const currentCell = this.getCellInternal(minRow, minColumn) || {
433
472
  type: "empty",
434
473
  };
435
474
  currentCell.rowSpan = rowSpan;
436
475
  currentCell.colSpan = columnSpan;
437
- this.rows[minRow].cells[minColumn] = currentCell;
476
+ const targetRow = this.rows[minRow];
477
+ if (targetRow) {
478
+ targetRow.cells[minColumn] = currentCell;
479
+ }
438
480
  }
439
481
  });
440
482
  }
@@ -450,9 +492,10 @@ export class TableWrapper {
450
492
  `Row position must be between 1 and ${this.rows.length} (number of rows).`,
451
493
  );
452
494
  }
453
- if (columnPos < 1 || columnPos > this.rows[0].cells.length) {
495
+ const columnLength = this.rows[0]?.cells.length || 0;
496
+ if (columnPos < 1 || columnPos > columnLength) {
454
497
  throw new TableValidationError(
455
- `Column position must be between 1 and ${this.rows[0].cells.length} (number of columns).`,
498
+ `Column position must be between 1 and ${columnLength} (number of columns).`,
456
499
  );
457
500
  }
458
501
  if (rowSpan < 1) {
@@ -466,9 +509,9 @@ export class TableWrapper {
466
509
  `Cannot expand ${rowSpan} rows from the cell at ${rowPos},${columnPos}. Table has ${this.rows.length} rows.`,
467
510
  );
468
511
  }
469
- if (columnPos + columnSpan - 1 > this.rows[0].cells.length) {
512
+ if (columnPos + columnSpan - 1 > columnLength) {
470
513
  throw new TableValidationError(
471
- `Cannot expand ${columnSpan} columns from the cell at ${rowPos},${columnPos}. Table has ${this.rows[0].cells.length} columns.`,
514
+ `Cannot expand ${columnSpan} columns from the cell at ${rowPos},${columnPos}. Table has ${columnLength} columns.`,
472
515
  );
473
516
  }
474
517
  }
@@ -30,6 +30,11 @@ export const useAddElement = () => {
30
30
  } else if (features.isSupported(addElementAtCursor)) {
31
31
  return addElementAtCursor(element);
32
32
  }
33
+ // eslint-disable-next-line no-console
34
+ console.warn(
35
+ "Neither addElementAtPoint nor addElementAtCursor are supported",
36
+ );
37
+ return Promise.resolve();
33
38
  };
34
39
  });
35
40
 
@@ -40,6 +45,11 @@ export const useAddElement = () => {
40
45
  } else if (isSupported(addElementAtCursor)) {
41
46
  return addElementAtCursor(element);
42
47
  }
48
+ // eslint-disable-next-line no-console
49
+ console.warn(
50
+ "Neither addElementAtPoint nor addElementAtCursor are supported",
51
+ );
52
+ return Promise.resolve();
43
53
  };
44
54
  setAddElement(() => addElement);
45
55
  }, [isSupported]);
@@ -57,11 +57,13 @@ export function useOverlay<
57
57
  const open = async (
58
58
  opts: { launchParameters?: unknown } = {},
59
59
  ): Promise<AppProcessId | undefined> => {
60
- if (overlay && overlay.canOpen) {
61
- const overlayId = await overlay.open(opts);
62
- setOverlayId(overlayId);
63
- return overlayId;
60
+ if (!overlay || !overlay.canOpen) {
61
+ return undefined;
64
62
  }
63
+
64
+ const overlayId = await overlay.open(opts);
65
+ setOverlayId(overlayId);
66
+ return overlayId;
65
67
  };
66
68
 
67
69
  const close = async (opts: C) => {
@@ -1,10 +1,10 @@
1
1
  import type { Container, Resource } from "@canva/app-components";
2
- import * as crypto from "crypto";
3
- import * as express from "express";
2
+ import crypto from "crypto";
3
+ import express from "express";
4
4
 
5
5
  /**
6
- * Generates a unique hash for a url.
7
- * Handy for uniquely identifying an image and creating an image id
6
+ * Generates a unique hash for a URL.
7
+ * Used for creating unique identifiers for digital assets from external URLs.
8
8
  */
9
9
  export async function generateHash(message: string) {
10
10
  const msgUint8 = new TextEncoder().encode(message);
@@ -16,6 +16,7 @@ export async function generateHash(message: string) {
16
16
  return hashHex;
17
17
  }
18
18
 
19
+ // Mock image URLs for demonstration purposes - replace with your actual digital asset sources
19
20
  const imageUrls = [
20
21
  "https://images.pexels.com/photos/1495580/pexels-photo-1495580.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=2",
21
22
  "https://images.pexels.com/photos/3943197/pexels-photo-3943197.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=2",
@@ -23,15 +24,20 @@ const imageUrls = [
23
24
  "https://images.pexels.com/photos/2904142/pexels-photo-2904142.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=2",
24
25
  "https://images.pexels.com/photos/5403478/pexels-photo-5403478.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=2",
25
26
  ];
27
+
26
28
  export const createDamRouter = () => {
27
29
  const router = express.Router();
28
30
 
29
- /**
30
- * This endpoint returns the data for your app.
31
- */
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
+ */
32
35
  router.post("/resources/find", async (req, res) => {
33
- // You should modify these lines to return data from your
34
- // digital asset manager (DAM) based on the findResourcesRequest
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
+ */
35
41
  const {
36
42
  types,
37
43
  continuation,
@@ -48,21 +54,32 @@ export const createDamRouter = () => {
48
54
  } = req.body;
49
55
 
50
56
  let resources: Resource[] = [];
57
+
58
+ // Handle image resource requests
51
59
  if (types.includes("IMAGE")) {
52
60
  resources = await Promise.all(
53
- Array.from({ length: 40 }, async (_, i) => ({
54
- id: await generateHash(i + ""),
55
- mimeType: "image/jpeg",
56
- name: `My new thing in ${locale}`, // Use the `locale` value from the request if your backend supports i18n
57
- type: "IMAGE",
58
- thumbnail: {
59
- url: imageUrls[i % imageUrls.length],
60
- },
61
- url: imageUrls[i % imageUrls.length],
62
- })),
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
+ }),
63
79
  );
64
80
  }
65
81
 
82
+ // Handle container (folder) resource requests
66
83
  if (types.includes("CONTAINER")) {
67
84
  const containers = await Promise.all(
68
85
  Array.from(
@@ -80,6 +97,7 @@ export const createDamRouter = () => {
80
97
  resources = resources.concat(containers);
81
98
  }
82
99
 
100
+ // Send response with resources and pagination token
83
101
  res.send({
84
102
  resources,
85
103
  continuation: +(continuation || 0) + 1,
@@ -1,5 +1,5 @@
1
- import * as cors from "cors";
2
- import * as express from "express";
1
+ import cors from "cors";
2
+ import express from "express";
3
3
  import { createBaseServer } from "../utils/backend/base_backend/create";
4
4
  import { createDamRouter } from "./routers/dam";
5
5
 
@@ -20,7 +20,7 @@
20
20
  "dependencies": {
21
21
  "@canva/app-components": "^2.1.0",
22
22
  "@canva/app-i18n-kit": "^1.1.1",
23
- "@canva/app-ui-kit": "^5.2.0",
23
+ "@canva/app-ui-kit": "^5.2.1",
24
24
  "@canva/asset": "^2.2.1",
25
25
  "@canva/design": "^2.7.3",
26
26
  "@canva/error": "^2.1.0",
@@ -44,7 +44,6 @@
44
44
  "@types/cors": "2.8.19",
45
45
  "@types/debug": "4.1.12",
46
46
  "@types/express": "4.17.21",
47
- "@types/express-serve-static-core": "5.0.7",
48
47
  "@types/jest": "29.5.14",
49
48
  "@types/jsonwebtoken": "9.0.10",
50
49
  "@types/node": "20.19.2",