blixify-ui-web 1.2.112 → 1.2.113

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 (35) hide show
  1. package/lib/components/data/dataTemplate/dataStateModel.d.ts +0 -7
  2. package/lib/components/data/dataTemplate/dataStateModel.d.ts.map +1 -1
  3. package/lib/components/data/dataTemplate/dataStateModel.js.map +1 -1
  4. package/lib/components/data/dataTemplate/index.d.ts.map +1 -1
  5. package/lib/components/data/dataTemplate/index.js +179 -216
  6. package/lib/components/data/dataTemplate/index.js.map +1 -1
  7. package/lib/components/data/dataTemplate/settingsModal.d.ts +1 -9
  8. package/lib/components/data/dataTemplate/settingsModal.d.ts.map +1 -1
  9. package/lib/components/data/dataTemplate/settingsModal.js +1 -60
  10. package/lib/components/data/dataTemplate/settingsModal.js.map +1 -1
  11. package/lib/components/data/dataTemplate/spaceModel.d.ts +2 -25
  12. package/lib/components/data/dataTemplate/spaceModel.d.ts.map +1 -1
  13. package/lib/components/data/{dataTemplate → driveTemplate}/driveUtils.d.ts +30 -6
  14. package/lib/components/data/driveTemplate/driveUtils.d.ts.map +1 -0
  15. package/lib/components/data/{dataTemplate → driveTemplate}/driveUtils.js +87 -28
  16. package/lib/components/data/driveTemplate/driveUtils.js.map +1 -0
  17. package/lib/components/data/driveTemplate/index.d.ts +36 -0
  18. package/lib/components/data/driveTemplate/index.d.ts.map +1 -0
  19. package/lib/components/data/{dataTemplate/driveView.js → driveTemplate/index.js} +527 -241
  20. package/lib/components/data/driveTemplate/index.js.map +1 -0
  21. package/lib/components/display/filePreview/index.d.ts +1 -0
  22. package/lib/components/display/filePreview/index.d.ts.map +1 -1
  23. package/lib/components/display/filePreview/index.js +6 -4
  24. package/lib/components/display/filePreview/index.js.map +1 -1
  25. package/lib/components/index.d.ts +2 -0
  26. package/lib/components/index.d.ts.map +1 -1
  27. package/lib/components/index.js +9 -3
  28. package/lib/components/index.js.map +1 -1
  29. package/lib/tail.css +2 -2
  30. package/package.json +1 -1
  31. package/lib/components/data/dataTemplate/driveUtils.d.ts.map +0 -1
  32. package/lib/components/data/dataTemplate/driveUtils.js.map +0 -1
  33. package/lib/components/data/dataTemplate/driveView.d.ts +0 -43
  34. package/lib/components/data/dataTemplate/driveView.d.ts.map +0 -1
  35. package/lib/components/data/dataTemplate/driveView.js.map +0 -1
@@ -33,6 +33,9 @@ var __importStar = (this && this.__importStar) || function (mod) {
33
33
  __setModuleDefault(result, mod);
34
34
  return result;
35
35
  };
36
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
37
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
38
+ };
36
39
  var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
37
40
  function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
38
41
  return new (P || (P = Promise))(function (resolve, reject) {
@@ -79,13 +82,14 @@ var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
79
82
  return to.concat(ar || Array.prototype.slice.call(from));
80
83
  };
81
84
  Object.defineProperty(exports, "__esModule", { value: true });
82
- exports.DriveView = void 0;
85
+ exports.DriveTemplate = void 0;
83
86
  var react_1 = require("@headlessui/react");
84
87
  var HeroIcons = __importStar(require("@heroicons/react/24/outline"));
85
88
  var react_2 = __importStar(require("react"));
86
- var popUp_1 = require("../../advanced/popUp");
87
89
  var uuid_1 = require("uuid");
88
90
  var button_1 = require("../../action/button");
91
+ var iconButton_1 = require("../../action/iconButton");
92
+ var popUp_1 = require("../../advanced/popUp");
89
93
  var emptyState_1 = require("../../display/emptyState");
90
94
  var filePreview_1 = require("../../display/filePreview");
91
95
  var loading_1 = require("../../display/loading");
@@ -94,20 +98,32 @@ var newList_1 = require("../../display/newList");
94
98
  var textInput_1 = require("../../input/textInput");
95
99
  var uploadInput_1 = require("../../input/uploadInput");
96
100
  var blobModule_1 = require("../blobModule");
101
+ var readQuery_1 = require("../readQuery");
102
+ var utils_1 = require("../utils");
103
+ var writeQuery_1 = require("../writeQuery");
97
104
  var driveUtils_1 = require("./driveUtils");
98
- var LAYOUT_STORAGE_PREFIX = "blixify-dtw-drive-layout-";
105
+ __exportStar(require("./driveUtils"), exports);
106
+ var LAYOUT_STORAGE_PREFIX = "blixify-drive-layout-";
107
+ var SEARCH_DEBOUNCE_MS = 400;
108
+ // INFO: the same cap the path walk uses — a deeper chain is treated as a loop
109
+ var MAX_DELETE_DEPTH = 64;
99
110
  /**
100
- * A folder browser over a collection whose rows are files and folders.
111
+ * A Google Drive-style folder browser over a collection whose rows are files
112
+ * and folders.
101
113
  *
102
- * INFO: the rows of the open folder arrive from DataTemplate's own list query,
103
- * so pagination, tab filters and permission filters behave as on any other
104
- * view. This component adds the folder tree it needs for the breadcrumb, the
105
- * move picker and search paths, and performs the drive's own writes.
114
+ * INFO: a standalone template rather than a DataTemplate view — a drive has one
115
+ * fixed data structure, and sitting beside a table view let users add blank
116
+ * records or edit the columns the drive depends on.
106
117
  */
107
- var DriveView = function (props) {
108
- var _a, _b, _c;
109
- var _d = (0, react_2.useState)(new Map()), folders = _d[0], setFolders = _d[1];
110
- var _e = (0, react_2.useState)(function () {
118
+ var DriveTemplate = function (props) {
119
+ var _a, _b, _c, _d, _e, _f, _g;
120
+ var _h = (0, react_2.useState)(""), innerFolderId = _h[0], setInnerFolderId = _h[1];
121
+ var _j = (0, react_2.useState)([]), rows = _j[0], setRows = _j[1];
122
+ var _k = (0, react_2.useState)(true), loading = _k[0], setLoading = _k[1];
123
+ var _l = (0, react_2.useState)(new Map()), folders = _l[0], setFolders = _l[1];
124
+ var _m = (0, react_2.useState)(""), searchText = _m[0], setSearchText = _m[1];
125
+ var _o = (0, react_2.useState)(""), searchQuery = _o[0], setSearchQuery = _o[1];
126
+ var _p = (0, react_2.useState)(function () {
111
127
  try {
112
128
  var stored = window.localStorage.getItem(LAYOUT_STORAGE_PREFIX + props.collectionId);
113
129
  if (stored === "grid" || stored === "list")
@@ -118,65 +134,191 @@ var DriveView = function (props) {
118
134
  return typeof window !== "undefined" && window.innerWidth < 640
119
135
  ? "list"
120
136
  : "grid";
121
- }), layout = _e[0], setLayout = _e[1];
122
- var _f = (0, react_2.useState)(false), newFolderOpen = _f[0], setNewFolderOpen = _f[1];
123
- var _g = (0, react_2.useState)(""), folderName = _g[0], setFolderName = _g[1];
124
- var _h = (0, react_2.useState)(null), renameRow = _h[0], setRenameRow = _h[1];
125
- var _j = (0, react_2.useState)(""), renameValue = _j[0], setRenameValue = _j[1];
126
- var _k = (0, react_2.useState)(null), moveRow = _k[0], setMoveRow = _k[1];
127
- var _l = (0, react_2.useState)(null), deleteRow = _l[0], setDeleteRow = _l[1];
128
- var _m = (0, react_2.useState)(null), deleteIds = _m[0], setDeleteIds = _m[1];
129
- var _o = (0, react_2.useState)(null), previewRow = _o[0], setPreviewRow = _o[1];
130
- var _p = (0, react_2.useState)(false), uploadOpen = _p[0], setUploadOpen = _p[1];
131
- var _q = (0, react_2.useState)({
137
+ }), layout = _p[0], setLayout = _p[1];
138
+ var _q = (0, react_2.useState)(false), newFolderOpen = _q[0], setNewFolderOpen = _q[1];
139
+ var _r = (0, react_2.useState)(""), folderName = _r[0], setFolderName = _r[1];
140
+ var _s = (0, react_2.useState)(null), renameRow = _s[0], setRenameRow = _s[1];
141
+ var _t = (0, react_2.useState)(""), renameValue = _t[0], setRenameValue = _t[1];
142
+ var _u = (0, react_2.useState)(null), moveRow = _u[0], setMoveRow = _u[1];
143
+ var _v = (0, react_2.useState)(null), deleteRow = _v[0], setDeleteRow = _v[1];
144
+ var _w = (0, react_2.useState)(null), deleteIds = _w[0], setDeleteIds = _w[1];
145
+ var _x = (0, react_2.useState)(null), previewRow = _x[0], setPreviewRow = _x[1];
146
+ var _y = (0, react_2.useState)(false), uploadOpen = _y[0], setUploadOpen = _y[1];
147
+ var _z = (0, react_2.useState)({
132
148
  data: [],
133
149
  upload: [],
134
150
  remove: [],
135
- }), uploadFiles = _q[0], setUploadFiles = _q[1];
136
- var _r = (0, react_2.useState)({}), uploadStatus = _r[0], setUploadStatus = _r[1];
137
- var _s = (0, react_2.useState)(false), working = _s[0], setWorking = _s[1];
138
- var _t = (0, react_2.useState)(false), dragging = _t[0], setDragging = _t[1];
139
- var _u = (0, react_2.useState)(""), modalError = _u[0], setModalError = _u[1];
151
+ }), uploadFiles = _z[0], setUploadFiles = _z[1];
152
+ var _0 = (0, react_2.useState)({}), uploadStatus = _0[0], setUploadStatus = _0[1];
153
+ var _1 = (0, react_2.useState)(false), working = _1[0], setWorking = _1[1];
154
+ var _2 = (0, react_2.useState)(false), dragging = _2[0], setDragging = _2[1];
155
+ var _3 = (0, react_2.useState)(""), modalError = _3[0], setModalError = _3[1];
140
156
  // INFO: dragenter and dragleave fire for every child the pointer crosses, so a
141
157
  // plain boolean flickers off over each tile; a depth count only reaches zero
142
158
  // when the drag has really left the drive
143
159
  var dragDepthRef = (0, react_2.useRef)(0);
144
- var settings = props.settings;
145
- var fileColumn = props.structure.find(function (eachColumn) { return eachColumn.id === settings.fileColumnId; });
146
- // INFO: the same folder a file column's uploads use everywhere else, so a
147
- // row deleted from another view removes the same storage folder
148
- var storageCollection = (_a = fileColumn === null || fileColumn === void 0 ? void 0 : fileColumn.imageCollectionName) !== null && _a !== void 0 ? _a : props.collectionId;
149
- var sortedRows = (0, driveUtils_1.handleSortDriveRows)(props.rows, settings);
150
- var path = (0, driveUtils_1.handleBuildFolderPath)(folders, props.folderId);
160
+ // INFO: a slow folder answering after a faster one must not overwrite it
161
+ var fetchSeqRef = (0, react_2.useRef)(0);
162
+ var folderSeqRef = (0, react_2.useRef)(0);
163
+ // INFO: a folder's contents are counted before its delete is confirmed. A
164
+ // count that finishes after the dialog closed or moved to another item must
165
+ // not attach its ids to that item's Delete button
166
+ var deleteSeqRef = (0, react_2.useRef)(0);
167
+ // INFO: what the drive is showing right now. A refresh that fires after a
168
+ // long upload reads this rather than the folder open when the upload began,
169
+ // or it would load the old folder under the new folder's path
170
+ var viewRef = (0, react_2.useRef)({
171
+ folderId: "",
172
+ searchQuery: "",
173
+ additionalQuery: [],
174
+ });
175
+ var devSettings = props.devSettings;
176
+ var serverId = (0, utils_1.parseServerId)(devSettings.server);
177
+ var folderId = (_a = props.folderId) !== null && _a !== void 0 ? _a : innerFolderId;
178
+ var additionalQuery = (_b = props.additionalQuery) !== null && _b !== void 0 ? _b : [];
179
+ var additionalQueryKey = JSON.stringify(additionalQuery);
180
+ var searching = !!searchQuery;
181
+ var dark = !!props.darkMode;
182
+ // INFO: one set of surface and text classes so every tile, menu and message
183
+ // follows darkMode, not only the header
184
+ var textStrong = dark ? "text-white" : "text-gray-900";
185
+ var textBody = dark ? "text-gray-300" : "text-gray-700";
186
+ var surface = dark
187
+ ? "border-white/10 bg-white/5 hover:bg-white/10"
188
+ : "border-gray-200 bg-white hover:bg-gray-50";
189
+ var canCreate = !!((_c = props.permissions) === null || _c === void 0 ? void 0 : _c.create);
190
+ var canUpdate = !!((_d = props.permissions) === null || _d === void 0 ? void 0 : _d.update);
191
+ var canDelete = !!((_e = props.permissions) === null || _e === void 0 ? void 0 : _e.delete);
192
+ var uploadEndpoint = devSettings.uploadEndpoint || "/api/asset";
193
+ var readQuery = (0, react_2.useMemo)(function () {
194
+ return new readQuery_1.ReadQuery({
195
+ dbModule: props.lib.axios,
196
+ dbType: devSettings.server,
197
+ dbCollection: props.collectionId,
198
+ dbEndpoint: devSettings.dbEndpoint,
199
+ dbAPIToken: devSettings.dbAPIToken,
200
+ dbUserToken: devSettings.dbUserToken,
201
+ });
202
+ }, [
203
+ props.lib.axios,
204
+ props.collectionId,
205
+ devSettings.server,
206
+ devSettings.dbEndpoint,
207
+ devSettings.dbAPIToken,
208
+ devSettings.dbUserToken,
209
+ ]);
210
+ var writeQuery = (0, react_2.useMemo)(function () {
211
+ return new writeQuery_1.WriteQuery({
212
+ dbModule: props.lib.axios,
213
+ dbType: devSettings.server,
214
+ dbCollection: props.collectionId,
215
+ dbEndpoint: devSettings.dbEndpoint,
216
+ dbAPIToken: devSettings.dbAPIToken,
217
+ dbUserToken: devSettings.dbUserToken,
218
+ });
219
+ }, [
220
+ props.lib.axios,
221
+ props.collectionId,
222
+ devSettings.server,
223
+ devSettings.dbEndpoint,
224
+ devSettings.dbAPIToken,
225
+ devSettings.dbUserToken,
226
+ ]);
227
+ var sortedRows = (0, driveUtils_1.handleSortDriveRows)(rows);
228
+ var path = (0, driveUtils_1.handleBuildFolderPath)(folders, folderId);
229
+ viewRef.current = { folderId: folderId, searchQuery: searchQuery, additionalQuery: additionalQuery };
230
+ (0, react_2.useEffect)(function () {
231
+ var timer = setTimeout(function () { return setSearchQuery(searchText.trim()); }, SEARCH_DEBOUNCE_MS);
232
+ return function () { return clearTimeout(timer); };
233
+ }, [searchText]);
234
+ (0, react_2.useEffect)(function () {
235
+ handleFetchRows();
236
+ // eslint-disable-next-line react-hooks/exhaustive-deps
237
+ }, [props.collectionId, folderId, searchQuery, additionalQueryKey]);
151
238
  (0, react_2.useEffect)(function () {
152
239
  handleLoadFolders();
153
240
  // eslint-disable-next-line react-hooks/exhaustive-deps
154
- }, [props.collectionId, JSON.stringify(props.baseQuery)]);
155
- var handleLoadFolders = function () { return __awaiter(void 0, void 0, void 0, function () {
156
- var res, err_1;
241
+ }, [props.collectionId, additionalQueryKey]);
242
+ /**
243
+ * INFO: the whole folder in one unpaged read — folders sort first on the
244
+ * client, which only holds if nothing is left on a later page. A search
245
+ * reads across the whole drive instead of the open folder.
246
+ */
247
+ var handleFetchRows = function () { return __awaiter(void 0, void 0, void 0, function () {
248
+ var seq, view, res, err_1;
157
249
  var _a;
158
250
  return __generator(this, function (_b) {
159
251
  switch (_b.label) {
160
252
  case 0:
161
- _b.trys.push([0, 2, , 3]);
162
- return [4 /*yield*/, props.readQuery.call({
253
+ seq = ++fetchSeqRef.current;
254
+ view = viewRef.current;
255
+ setLoading(true);
256
+ _b.label = 1;
257
+ case 1:
258
+ _b.trys.push([1, 3, , 4]);
259
+ return [4 /*yield*/, readQuery.call({
163
260
  type: "list",
164
- query: __spreadArray(__spreadArray([], props.baseQuery, true), [
165
- { type: "=", queryId: settings.kindColumnId, value: driveUtils_1.DRIVE_FOLDER },
261
+ query: __spreadArray(__spreadArray([], view.additionalQuery, true), [
262
+ view.searchQuery
263
+ ? (0, driveUtils_1.handleBuildDriveSearchQuery)(view.searchQuery)
264
+ : (0, driveUtils_1.handleBuildDriveFolderQuery)(view.folderId),
166
265
  ], false),
167
266
  stopLimit: true,
168
267
  })];
169
- case 1:
170
- res = _b.sent();
171
- setFolders((0, driveUtils_1.handleMapDriveFolders)((_a = res === null || res === void 0 ? void 0 : res.data) !== null && _a !== void 0 ? _a : [], settings));
172
- return [3 /*break*/, 3];
173
268
  case 2:
269
+ res = _b.sent();
270
+ if (seq !== fetchSeqRef.current)
271
+ return [2 /*return*/];
272
+ setRows((_a = res === null || res === void 0 ? void 0 : res.data) !== null && _a !== void 0 ? _a : []);
273
+ return [3 /*break*/, 4];
274
+ case 3:
174
275
  err_1 = _b.sent();
175
- return [3 /*break*/, 3];
176
- case 3: return [2 /*return*/];
276
+ if (seq !== fetchSeqRef.current)
277
+ return [2 /*return*/];
278
+ setRows([]);
279
+ return [3 /*break*/, 4];
280
+ case 4:
281
+ setLoading(false);
282
+ return [2 /*return*/];
283
+ }
284
+ });
285
+ }); };
286
+ var handleLoadFolders = function () { return __awaiter(void 0, void 0, void 0, function () {
287
+ var seq, res, err_2;
288
+ var _a;
289
+ return __generator(this, function (_b) {
290
+ switch (_b.label) {
291
+ case 0:
292
+ seq = ++folderSeqRef.current;
293
+ _b.label = 1;
294
+ case 1:
295
+ _b.trys.push([1, 3, , 4]);
296
+ return [4 /*yield*/, readQuery.call({
297
+ type: "list",
298
+ query: __spreadArray(__spreadArray([], viewRef.current.additionalQuery, true), [
299
+ { type: "=", queryId: driveUtils_1.DRIVE_COLUMNS.kind, value: driveUtils_1.DRIVE_FOLDER },
300
+ ], false),
301
+ stopLimit: true,
302
+ })];
303
+ case 2:
304
+ res = _b.sent();
305
+ if (seq !== folderSeqRef.current)
306
+ return [2 /*return*/];
307
+ setFolders((0, driveUtils_1.handleMapDriveFolders)((_a = res === null || res === void 0 ? void 0 : res.data) !== null && _a !== void 0 ? _a : []));
308
+ return [3 /*break*/, 4];
309
+ case 3:
310
+ err_2 = _b.sent();
311
+ return [3 /*break*/, 4];
312
+ case 4: return [2 /*return*/];
177
313
  }
178
314
  });
179
315
  }); };
316
+ var handleChangeFolder = function (nextFolderId) {
317
+ if (props.onChangeFolder)
318
+ props.onChangeFolder(nextFolderId);
319
+ else
320
+ setInnerFolderId(nextFolderId);
321
+ };
180
322
  var handleChangeLayout = function (next) {
181
323
  setLayout(next);
182
324
  try {
@@ -187,39 +329,49 @@ var DriveView = function (props) {
187
329
  var handleAfterWrite = function () { return __awaiter(void 0, void 0, void 0, function () {
188
330
  return __generator(this, function (_a) {
189
331
  switch (_a.label) {
190
- case 0:
191
- props.handleRefresh();
192
- return [4 /*yield*/, handleLoadFolders()];
332
+ case 0: return [4 /*yield*/, Promise.all([handleFetchRows(), handleLoadFolders()])];
193
333
  case 1:
194
334
  _a.sent();
195
335
  return [2 /*return*/];
196
336
  }
197
337
  });
198
338
  }); };
339
+ var handleBuildPrefill = function () {
340
+ var _a;
341
+ var prefill = {};
342
+ ((_a = props.createPrefill) !== null && _a !== void 0 ? _a : []).forEach(function (eachPrefill) {
343
+ prefill[eachPrefill.key] = eachPrefill.value;
344
+ });
345
+ return prefill;
346
+ };
199
347
  var handleResolveFile = function (row) {
200
- var _a, _b;
201
- var value = row === null || row === void 0 ? void 0 : row[settings.fileColumnId];
348
+ var _a;
349
+ var value = row === null || row === void 0 ? void 0 : row[driveUtils_1.DRIVE_COLUMNS.file];
202
350
  var entry = Array.isArray(value) ? value[0] : value;
203
351
  if (!(entry === null || entry === void 0 ? void 0 : entry.token) || !(entry === null || entry === void 0 ? void 0 : entry.fileName))
204
352
  return null;
205
353
  return {
206
- fileName: (0, blobModule_1.renderImageUrlFromPath)(props.imageEndpoint, storageCollection, (0, driveUtils_1.handleGetRowId)(row), entry.fileName, entry.token, props.bucketName),
207
- label: String((_a = row === null || row === void 0 ? void 0 : row[settings.nameColumnId]) !== null && _a !== void 0 ? _a : "") || undefined,
354
+ fileName: (0, blobModule_1.renderImageUrlFromPath)(devSettings.imageEndpoint, props.collectionId, (0, driveUtils_1.handleGetRowId)(row), entry.fileName, entry.token, devSettings.bucketName),
355
+ label: String((_a = row === null || row === void 0 ? void 0 : row[driveUtils_1.DRIVE_COLUMNS.name]) !== null && _a !== void 0 ? _a : "") || undefined,
208
356
  token: entry.token,
209
- mimeType: (_b = entry.mimeType) !== null && _b !== void 0 ? _b : (0, filePreview_1.handleGuessMimeType)(entry.fileName),
357
+ mimeType: (row === null || row === void 0 ? void 0 : row[driveUtils_1.DRIVE_COLUMNS.mimeType]) ||
358
+ entry.mimeType ||
359
+ (0, filePreview_1.handleGuessMimeType)(entry.fileName),
210
360
  };
211
361
  };
212
362
  var handleOpenRow = function (row) {
213
- var _a;
214
- if ((0, driveUtils_1.handleIsDriveFolder)(row, settings)) {
215
- if (props.searching)
216
- (_a = props.handleClearSearch) === null || _a === void 0 ? void 0 : _a.call(props);
217
- props.handleChangeFolder((0, driveUtils_1.handleGetRowId)(row));
363
+ if ((0, driveUtils_1.handleIsDriveFolder)(row)) {
364
+ // INFO: a search reads the whole drive, so opening a folder from its
365
+ // results has to end the search or the folder shows the results again
366
+ setSearchText("");
367
+ setSearchQuery("");
368
+ handleChangeFolder((0, driveUtils_1.handleGetRowId)(row));
218
369
  }
219
370
  else
220
371
  setPreviewRow(row);
221
372
  };
222
373
  var handleCloseModals = function () {
374
+ deleteSeqRef.current += 1;
223
375
  setNewFolderOpen(false);
224
376
  setFolderName("");
225
377
  setRenameRow(null);
@@ -230,27 +382,25 @@ var DriveView = function (props) {
230
382
  setModalError("");
231
383
  };
232
384
  var handleCreateFolder = function () { return __awaiter(void 0, void 0, void 0, function () {
233
- var name, err_2;
385
+ var name, err_3;
234
386
  var _a;
235
387
  return __generator(this, function (_b) {
236
388
  switch (_b.label) {
237
389
  case 0:
390
+ // INFO: Enter and a phone's Done still submit while the button is disabled
391
+ if (working)
392
+ return [2 /*return*/];
238
393
  name = folderName.trim();
239
394
  if (!name)
240
395
  return [2 /*return*/, setModalError("Name the folder.")];
241
- if ((0, driveUtils_1.handleIsDuplicateFolderName)(folders, props.folderId, name))
396
+ if ((0, driveUtils_1.handleIsDuplicateFolderName)(folders, folderId, name))
242
397
  return [2 /*return*/, setModalError("A folder named \"".concat(name, "\" is already here."))];
243
398
  setWorking(true);
244
399
  _b.label = 1;
245
400
  case 1:
246
401
  _b.trys.push([1, 4, , 5]);
247
- return [4 /*yield*/, props.writeQuery.call("create", {
248
- data: (_a = {},
249
- _a[props.serverId] = (0, uuid_1.v4)(),
250
- _a[settings.nameColumnId] = name,
251
- _a[settings.kindColumnId] = driveUtils_1.DRIVE_FOLDER,
252
- _a[settings.parentColumnId] = props.folderId,
253
- _a),
402
+ return [4 /*yield*/, writeQuery.call("create", {
403
+ data: __assign(__assign({}, handleBuildPrefill()), (_a = {}, _a[serverId] = (0, uuid_1.v4)(), _a[driveUtils_1.DRIVE_COLUMNS.name] = name, _a[driveUtils_1.DRIVE_COLUMNS.kind] = driveUtils_1.DRIVE_FOLDER, _a[driveUtils_1.DRIVE_COLUMNS.parentId] = folderId, _a)),
254
404
  })];
255
405
  case 2:
256
406
  _b.sent();
@@ -260,7 +410,7 @@ var DriveView = function (props) {
260
410
  _b.sent();
261
411
  return [3 /*break*/, 5];
262
412
  case 4:
263
- err_2 = _b.sent();
413
+ err_3 = _b.sent();
264
414
  setModalError("The folder could not be created.");
265
415
  return [3 /*break*/, 5];
266
416
  case 5:
@@ -272,30 +422,32 @@ var DriveView = function (props) {
272
422
  var handleOpenRename = function (row) {
273
423
  var _a;
274
424
  setRenameRow(row);
275
- setRenameValue(String((_a = row === null || row === void 0 ? void 0 : row[settings.nameColumnId]) !== null && _a !== void 0 ? _a : ""));
425
+ setRenameValue(String((_a = row === null || row === void 0 ? void 0 : row[driveUtils_1.DRIVE_COLUMNS.name]) !== null && _a !== void 0 ? _a : ""));
276
426
  setModalError("");
277
427
  };
278
428
  var handleRename = function () { return __awaiter(void 0, void 0, void 0, function () {
279
- var name, rowId, err_3;
429
+ var name, rowId, err_4;
280
430
  var _a;
281
431
  var _b;
282
432
  return __generator(this, function (_c) {
283
433
  switch (_c.label) {
284
434
  case 0:
435
+ if (working)
436
+ return [2 /*return*/];
285
437
  name = renameValue.trim();
286
438
  if (!name)
287
439
  return [2 /*return*/, setModalError("A name is required.")];
288
440
  rowId = (0, driveUtils_1.handleGetRowId)(renameRow);
289
- if ((0, driveUtils_1.handleIsDriveFolder)(renameRow, settings) &&
290
- (0, driveUtils_1.handleIsDuplicateFolderName)(folders, String((_b = renameRow === null || renameRow === void 0 ? void 0 : renameRow[settings.parentColumnId]) !== null && _b !== void 0 ? _b : ""), name, rowId))
441
+ if ((0, driveUtils_1.handleIsDriveFolder)(renameRow) &&
442
+ (0, driveUtils_1.handleIsDuplicateFolderName)(folders, String((_b = renameRow === null || renameRow === void 0 ? void 0 : renameRow[driveUtils_1.DRIVE_COLUMNS.parentId]) !== null && _b !== void 0 ? _b : ""), name, rowId))
291
443
  return [2 /*return*/, setModalError("A folder named \"".concat(name, "\" is already here."))];
292
444
  setWorking(true);
293
445
  _c.label = 1;
294
446
  case 1:
295
447
  _c.trys.push([1, 4, , 5]);
296
- return [4 /*yield*/, props.writeQuery.call("update", {
448
+ return [4 /*yield*/, writeQuery.call("update", {
297
449
  id: rowId,
298
- data: (_a = {}, _a[settings.nameColumnId] = name, _a),
450
+ data: (_a = {}, _a[driveUtils_1.DRIVE_COLUMNS.name] = name, _a),
299
451
  })];
300
452
  case 2:
301
453
  _c.sent();
@@ -305,7 +457,7 @@ var DriveView = function (props) {
305
457
  _c.sent();
306
458
  return [3 /*break*/, 5];
307
459
  case 4:
308
- err_3 = _c.sent();
460
+ err_4 = _c.sent();
309
461
  setModalError("The name could not be saved.");
310
462
  return [3 /*break*/, 5];
311
463
  case 5:
@@ -315,23 +467,25 @@ var DriveView = function (props) {
315
467
  });
316
468
  }); };
317
469
  var handleMove = function (targetFolderId) { return __awaiter(void 0, void 0, void 0, function () {
318
- var rowId, err_4;
470
+ var rowId, err_5;
319
471
  var _a;
320
472
  var _b;
321
473
  return __generator(this, function (_c) {
322
474
  switch (_c.label) {
323
475
  case 0:
476
+ if (working)
477
+ return [2 /*return*/];
324
478
  rowId = (0, driveUtils_1.handleGetRowId)(moveRow);
325
- if ((0, driveUtils_1.handleIsDriveFolder)(moveRow, settings) &&
326
- (0, driveUtils_1.handleIsDuplicateFolderName)(folders, targetFolderId, String((_b = moveRow === null || moveRow === void 0 ? void 0 : moveRow[settings.nameColumnId]) !== null && _b !== void 0 ? _b : ""), rowId))
479
+ if ((0, driveUtils_1.handleIsDriveFolder)(moveRow) &&
480
+ (0, driveUtils_1.handleIsDuplicateFolderName)(folders, targetFolderId, String((_b = moveRow === null || moveRow === void 0 ? void 0 : moveRow[driveUtils_1.DRIVE_COLUMNS.name]) !== null && _b !== void 0 ? _b : ""), rowId))
327
481
  return [2 /*return*/, setModalError("A folder with this name is already there.")];
328
482
  setWorking(true);
329
483
  _c.label = 1;
330
484
  case 1:
331
485
  _c.trys.push([1, 4, , 5]);
332
- return [4 /*yield*/, props.writeQuery.call("update", {
486
+ return [4 /*yield*/, writeQuery.call("update", {
333
487
  id: rowId,
334
- data: (_a = {}, _a[settings.parentColumnId] = targetFolderId, _a),
488
+ data: (_a = {}, _a[driveUtils_1.DRIVE_COLUMNS.parentId] = targetFolderId, _a),
335
489
  })];
336
490
  case 2:
337
491
  _c.sent();
@@ -341,7 +495,7 @@ var DriveView = function (props) {
341
495
  _c.sent();
342
496
  return [3 /*break*/, 5];
343
497
  case 4:
344
- err_4 = _c.sent();
498
+ err_5 = _c.sent();
345
499
  setModalError("The item could not be moved.");
346
500
  return [3 /*break*/, 5];
347
501
  case 5:
@@ -351,49 +505,128 @@ var DriveView = function (props) {
351
505
  });
352
506
  }); };
353
507
  /**
354
- * INFO: a folder's contents are counted before anything is deleted, so the
355
- * confirmation says what will go. Read from the whole collection, not the
356
- * filtered list — a file hidden from this view would otherwise be left
357
- * pointing at a folder that no longer exists.
508
+ * Every row below a folder, read one level at a time.
509
+ *
510
+ * INFO: only the folder's own subtree is read — by parentId, one request per
511
+ * level, keeping just the id and folder columns — so counting a folder costs
512
+ * its size, not the size of the whole drive.
358
513
  */
359
- var handleOpenDelete = function (row) { return __awaiter(void 0, void 0, void 0, function () {
360
- var res, rowId, err_5;
514
+ var handleReadSubtreeIds = function (rootId, query) { return __awaiter(void 0, void 0, void 0, function () {
515
+ var found, seen, frontier, _loop_1, depth;
361
516
  var _a;
362
517
  return __generator(this, function (_b) {
363
518
  switch (_b.label) {
364
519
  case 0:
520
+ found = [];
521
+ seen = new Set([rootId]);
522
+ frontier = [rootId];
523
+ _loop_1 = function (depth) {
524
+ var res, next;
525
+ return __generator(this, function (_c) {
526
+ switch (_c.label) {
527
+ case 0: return [4 /*yield*/, readQuery.call({
528
+ type: "list",
529
+ query: __spreadArray(__spreadArray([], query, true), [
530
+ { type: "in", queryId: driveUtils_1.DRIVE_COLUMNS.parentId, value: frontier },
531
+ ], false),
532
+ attributeIds: [serverId, driveUtils_1.DRIVE_COLUMNS.parentId],
533
+ stopLimit: true,
534
+ })];
535
+ case 1:
536
+ res = _c.sent();
537
+ next = [];
538
+ ((_a = res === null || res === void 0 ? void 0 : res.data) !== null && _a !== void 0 ? _a : []).forEach(function (eachRow) {
539
+ var id = (0, driveUtils_1.handleGetRowId)(eachRow);
540
+ if (!id || seen.has(id))
541
+ return;
542
+ seen.add(id);
543
+ found.push(id);
544
+ next.push(id);
545
+ });
546
+ frontier = next;
547
+ return [2 /*return*/];
548
+ }
549
+ });
550
+ };
551
+ depth = 0;
552
+ _b.label = 1;
553
+ case 1:
554
+ if (!(frontier.length > 0 && depth < MAX_DELETE_DEPTH)) return [3 /*break*/, 4];
555
+ return [5 /*yield**/, _loop_1(depth)];
556
+ case 2:
557
+ _b.sent();
558
+ _b.label = 3;
559
+ case 3:
560
+ depth += 1;
561
+ return [3 /*break*/, 1];
562
+ case 4: return [2 /*return*/, found];
563
+ }
564
+ });
565
+ }); };
566
+ /**
567
+ * INFO: a folder's contents are counted before anything is deleted, so the
568
+ * confirmation says what will go. A scoped member's folder can hold rows
569
+ * outside their slice; deleting the folder would strand those rows under a
570
+ * parent that no longer exists, where no view can reach them, so such a
571
+ * folder is refused. When the unscoped count cannot be read the delete is
572
+ * refused too, rather than guessed.
573
+ */
574
+ var handleOpenDelete = function (row) { return __awaiter(void 0, void 0, void 0, function () {
575
+ var seq, rowId, visibleIds, err_6, allIds, err_7;
576
+ return __generator(this, function (_a) {
577
+ switch (_a.label) {
578
+ case 0:
579
+ seq = ++deleteSeqRef.current;
365
580
  setDeleteRow(row);
366
581
  setModalError("");
367
- if (!(0, driveUtils_1.handleIsDriveFolder)(row, settings)) {
582
+ if (!(0, driveUtils_1.handleIsDriveFolder)(row)) {
368
583
  setDeleteIds([(0, driveUtils_1.handleGetRowId)(row)]);
369
584
  return [2 /*return*/];
370
585
  }
371
586
  setDeleteIds(null);
372
- _b.label = 1;
587
+ rowId = (0, driveUtils_1.handleGetRowId)(row);
588
+ visibleIds = [];
589
+ _a.label = 1;
373
590
  case 1:
374
- _b.trys.push([1, 3, , 4]);
375
- return [4 /*yield*/, props.readQuery.call({
376
- type: "list",
377
- query: [],
378
- stopLimit: true,
379
- })];
591
+ _a.trys.push([1, 3, , 4]);
592
+ return [4 /*yield*/, handleReadSubtreeIds(rowId, additionalQuery)];
380
593
  case 2:
381
- res = _b.sent();
382
- rowId = (0, driveUtils_1.handleGetRowId)(row);
383
- setDeleteIds(__spreadArray([
384
- rowId
385
- ], (0, driveUtils_1.handleCollectDescendantIds)((_a = res === null || res === void 0 ? void 0 : res.data) !== null && _a !== void 0 ? _a : [], settings, rowId), true));
594
+ visibleIds = _a.sent();
386
595
  return [3 /*break*/, 4];
387
596
  case 3:
388
- err_5 = _b.sent();
389
- setModalError("The folder's contents could not be read.");
390
- return [3 /*break*/, 4];
391
- case 4: return [2 /*return*/];
597
+ err_6 = _a.sent();
598
+ if (seq === deleteSeqRef.current)
599
+ setModalError("The folder's contents could not be read.");
600
+ return [2 /*return*/];
601
+ case 4:
602
+ if (!(additionalQuery.length > 0)) return [3 /*break*/, 8];
603
+ _a.label = 5;
604
+ case 5:
605
+ _a.trys.push([5, 7, , 8]);
606
+ return [4 /*yield*/, handleReadSubtreeIds(rowId, [])];
607
+ case 6:
608
+ allIds = _a.sent();
609
+ if (allIds.length > visibleIds.length) {
610
+ if (seq === deleteSeqRef.current)
611
+ setModalError("This folder holds items you do not have access to, so it cannot be deleted.");
612
+ return [2 /*return*/];
613
+ }
614
+ return [3 /*break*/, 8];
615
+ case 7:
616
+ err_7 = _a.sent();
617
+ if (seq === deleteSeqRef.current)
618
+ setModalError("Whether this folder holds items you cannot see could not be checked, so it cannot be deleted.");
619
+ return [2 /*return*/];
620
+ case 8:
621
+ if (seq !== deleteSeqRef.current)
622
+ return [2 /*return*/];
623
+ setDeleteIds(__spreadArray([rowId], visibleIds, true));
624
+ return [2 /*return*/];
392
625
  }
393
626
  });
394
627
  }); };
395
628
  var handleDelete = function () { return __awaiter(void 0, void 0, void 0, function () {
396
- var form, err_6;
629
+ var form, err_8;
397
630
  return __generator(this, function (_a) {
398
631
  switch (_a.label) {
399
632
  case 0:
@@ -403,19 +636,19 @@ var DriveView = function (props) {
403
636
  _a.label = 1;
404
637
  case 1:
405
638
  _a.trys.push([1, 5, , 6]);
406
- return [4 /*yield*/, props.writeQuery.call("batchDelete", { id: deleteIds })];
639
+ return [4 /*yield*/, writeQuery.call("batchDelete", { id: deleteIds })];
407
640
  case 2:
408
641
  _a.sent();
409
642
  form = new FormData();
410
- form.append("data[assetCollectionName]", storageCollection);
643
+ form.append("data[assetCollectionName]", props.collectionId);
411
644
  form.append("data[assetParentId]", JSON.stringify(deleteIds));
412
- if (props.bucketName)
413
- form.append("data[bucketName]", props.bucketName);
645
+ if (devSettings.bucketName)
646
+ form.append("data[bucketName]", devSettings.bucketName);
414
647
  return [4 /*yield*/, props.lib
415
648
  .axios({
416
649
  data: form,
417
650
  method: "post",
418
- url: "".concat(props.uploadEndpoint, "/batchDeleteDirectory"),
651
+ url: "".concat(uploadEndpoint, "/batchDeleteDirectory"),
419
652
  headers: { "Content-Type": "multipart/form-data" },
420
653
  })
421
654
  .catch(function () { })];
@@ -427,7 +660,7 @@ var DriveView = function (props) {
427
660
  _a.sent();
428
661
  return [3 /*break*/, 6];
429
662
  case 5:
430
- err_6 = _a.sent();
663
+ err_8 = _a.sent();
431
664
  setModalError("The item could not be deleted.");
432
665
  return [3 /*break*/, 6];
433
666
  case 6:
@@ -436,60 +669,79 @@ var DriveView = function (props) {
436
669
  }
437
670
  });
438
671
  }); };
672
+ var handleUploadToStorage = function (rowId, storedName, file) { return __awaiter(void 0, void 0, void 0, function () {
673
+ var dataForm, res;
674
+ var _a;
675
+ return __generator(this, function (_b) {
676
+ switch (_b.label) {
677
+ case 0:
678
+ dataForm = new FormData();
679
+ dataForm.append("file", file);
680
+ dataForm.append("data[assetCollectionName]", props.collectionId);
681
+ dataForm.append("data[assetParentId]", rowId);
682
+ dataForm.append("data[assetFileName]", storedName);
683
+ if (devSettings.bucketName)
684
+ dataForm.append("data[bucketName]", devSettings.bucketName);
685
+ return [4 /*yield*/, props.lib.axios({
686
+ method: "post",
687
+ url: "".concat(uploadEndpoint, "/upload"),
688
+ data: dataForm,
689
+ headers: { "Content-Type": "multipart/form-data" },
690
+ })];
691
+ case 1:
692
+ res = _b.sent();
693
+ return [2 /*return*/, (_a = res === null || res === void 0 ? void 0 : res.data) === null || _a === void 0 ? void 0 : _a.data];
694
+ }
695
+ });
696
+ }); };
439
697
  /**
440
698
  * INFO: one row per file, each uploaded into its own row's storage folder
441
699
  * first — a row is only created for a file that stored, and one failure does
442
700
  * not stop the rest.
443
701
  */
444
702
  var handleUploadFiles = function (queued) { return __awaiter(void 0, void 0, void 0, function () {
445
- var failed, _loop_1, _i, queued_1, eachFile;
446
- var _a;
447
- return __generator(this, function (_b) {
448
- switch (_b.label) {
703
+ var failed, _loop_2, _i, queued_1, eachFile;
704
+ return __generator(this, function (_a) {
705
+ switch (_a.label) {
449
706
  case 0:
450
707
  if (!queued.length)
451
708
  return [2 /*return*/];
452
709
  setWorking(true);
453
710
  setModalError("");
454
711
  failed = 0;
455
- _loop_1 = function (eachFile) {
456
- var file, rowId, storedName, res, token, err_7;
457
- var _c, _d, _e;
458
- return __generator(this, function (_f) {
459
- switch (_f.label) {
712
+ _loop_2 = function (eachFile) {
713
+ var file, rowId, storedName, token, err_9;
714
+ var _b;
715
+ return __generator(this, function (_c) {
716
+ switch (_c.label) {
460
717
  case 0:
461
718
  setUploadStatus(function (current) {
462
719
  var _a;
463
720
  return (__assign(__assign({}, current), (_a = {}, _a[eachFile.token] = "uploading", _a)));
464
721
  });
465
- _f.label = 1;
722
+ _c.label = 1;
466
723
  case 1:
467
- _f.trys.push([1, 4, , 5]);
724
+ _c.trys.push([1, 4, , 5]);
468
725
  file = eachFile.fileName;
469
726
  rowId = (0, uuid_1.v4)();
470
727
  storedName = "".concat((0, uuid_1.v4)(), "-").concat(file.name);
471
- return [4 /*yield*/, props.handleUpload(props.uploadEndpoint, rowId, storageCollection, storedName, file, props.bucketName)];
728
+ return [4 /*yield*/, handleUploadToStorage(rowId, storedName, file)];
472
729
  case 2:
473
- res = _f.sent();
474
- token = (_a = res === null || res === void 0 ? void 0 : res.data) === null || _a === void 0 ? void 0 : _a.data;
730
+ token = _c.sent();
475
731
  if (!token)
476
732
  throw new Error("No token returned");
477
- return [4 /*yield*/, props.writeQuery.call("create", {
478
- data: __assign(__assign((_c = {}, _c[props.serverId] = rowId, _c[settings.nameColumnId] = file.name, _c[settings.kindColumnId] = driveUtils_1.DRIVE_FILE, _c[settings.parentColumnId] = props.folderId, _c[settings.fileColumnId] = [
479
- { token: token, fileName: storedName, mimeType: file.type || undefined },
480
- ], _c), (settings.sizeColumnId
481
- ? (_d = {}, _d[settings.sizeColumnId] = file.size, _d) : {})), (settings.mimeTypeColumnId
482
- ? (_e = {}, _e[settings.mimeTypeColumnId] = file.type, _e) : {})),
733
+ return [4 /*yield*/, writeQuery.call("create", {
734
+ data: __assign(__assign({}, handleBuildPrefill()), (_b = {}, _b[serverId] = rowId, _b[driveUtils_1.DRIVE_COLUMNS.name] = file.name, _b[driveUtils_1.DRIVE_COLUMNS.kind] = driveUtils_1.DRIVE_FILE, _b[driveUtils_1.DRIVE_COLUMNS.parentId] = folderId, _b[driveUtils_1.DRIVE_COLUMNS.file] = [{ token: token, fileName: storedName }], _b[driveUtils_1.DRIVE_COLUMNS.size] = file.size, _b[driveUtils_1.DRIVE_COLUMNS.mimeType] = file.type, _b)),
483
735
  })];
484
736
  case 3:
485
- _f.sent();
737
+ _c.sent();
486
738
  setUploadStatus(function (current) {
487
739
  var _a;
488
740
  return (__assign(__assign({}, current), (_a = {}, _a[eachFile.token] = "done", _a)));
489
741
  });
490
742
  return [3 /*break*/, 5];
491
743
  case 4:
492
- err_7 = _f.sent();
744
+ err_9 = _c.sent();
493
745
  failed += 1;
494
746
  setUploadStatus(function (current) {
495
747
  var _a;
@@ -501,20 +753,20 @@ var DriveView = function (props) {
501
753
  });
502
754
  };
503
755
  _i = 0, queued_1 = queued;
504
- _b.label = 1;
756
+ _a.label = 1;
505
757
  case 1:
506
758
  if (!(_i < queued_1.length)) return [3 /*break*/, 4];
507
759
  eachFile = queued_1[_i];
508
- return [5 /*yield**/, _loop_1(eachFile)];
760
+ return [5 /*yield**/, _loop_2(eachFile)];
509
761
  case 2:
510
- _b.sent();
511
- _b.label = 3;
762
+ _a.sent();
763
+ _a.label = 3;
512
764
  case 3:
513
765
  _i++;
514
766
  return [3 /*break*/, 1];
515
767
  case 4:
516
768
  setWorking(false);
517
- props.handleRefresh();
769
+ handleFetchRows();
518
770
  if (failed === 0)
519
771
  handleCloseUpload();
520
772
  else
@@ -564,10 +816,18 @@ var DriveView = function (props) {
564
816
  // while the upload dialog is open — it has its own drop zone
565
817
  var handleIsFileDrag = function (event) {
566
818
  var _a, _b;
567
- return props.canCreate &&
819
+ return canCreate &&
568
820
  !uploadOpen &&
569
821
  Array.from((_b = (_a = event.dataTransfer) === null || _a === void 0 ? void 0 : _a.types) !== null && _b !== void 0 ? _b : []).includes("Files");
570
822
  };
823
+ var handleFormatRowPath = function (row) {
824
+ var _a;
825
+ if (!searching)
826
+ return "";
827
+ var parentId = String((_a = row === null || row === void 0 ? void 0 : row[driveUtils_1.DRIVE_COLUMNS.parentId]) !== null && _a !== void 0 ? _a : "");
828
+ var rowPath = (0, driveUtils_1.handleFormatFolderPath)((0, driveUtils_1.handleBuildFolderPath)(folders, parentId));
829
+ return rowPath ? "In ".concat(rowPath) : "At the top level";
830
+ };
571
831
  var renderDropOverlay = function () {
572
832
  if (!dragging)
573
833
  return null;
@@ -578,12 +838,8 @@ var DriveView = function (props) {
578
838
  "Drop to upload to ",
579
839
  target)));
580
840
  };
581
- var renderMenu = function (row) { return (react_2.default.createElement(DriveItemMenu, { testId: "drive-menu-".concat((0, driveUtils_1.handleGetRowId)(row)), items: __spreadArray(__spreadArray([
582
- {
583
- text: "Details",
584
- onClick: function () { return props.handleOpenDetails((0, driveUtils_1.handleGetRowId)(row)); },
585
- }
586
- ], (props.canUpdate
841
+ var renderMenu = function (row) {
842
+ var items = __spreadArray(__spreadArray([], (canUpdate
587
843
  ? [
588
844
  { text: "Rename", onClick: function () { return handleOpenRename(row); } },
589
845
  {
@@ -594,102 +850,117 @@ var DriveView = function (props) {
594
850
  },
595
851
  },
596
852
  ]
597
- : []), true), (props.canDelete
853
+ : []), true), (canDelete
598
854
  ? [{ text: "Delete", onClick: function () { return handleOpenDelete(row); } }]
599
- : []), true) })); };
600
- var renderRowPath = function (row) {
601
- var _a;
602
- if (!props.searching)
603
- return "";
604
- var parentId = String((_a = row === null || row === void 0 ? void 0 : row[settings.parentColumnId]) !== null && _a !== void 0 ? _a : "");
605
- var rowPath = (0, driveUtils_1.handleFormatFolderPath)((0, driveUtils_1.handleBuildFolderPath)(folders, parentId));
606
- return rowPath ? "In ".concat(rowPath) : "At the top level";
855
+ : []), true);
856
+ if (!items.length)
857
+ return null;
858
+ return (react_2.default.createElement(DriveItemMenu, { darkMode: dark, testId: "drive-menu-".concat((0, driveUtils_1.handleGetRowId)(row)), items: items }));
607
859
  };
608
- var renderToolbar = function () { return (react_2.default.createElement("div", { className: "flex flex-col gap-y-3 sm:flex-row sm:items-center sm:gap-x-3" },
609
- react_2.default.createElement("nav", { "aria-label": "Drive path", className: "flex min-w-0 flex-row flex-wrap items-center gap-x-1 text-sm" },
610
- react_2.default.createElement("button", { type: "button", className: "font-medium ".concat(props.folderId ? "text-primary-600 hover:underline" : "text-gray-900"), onClick: function () { return props.handleChangeFolder(""); } }, "Drive"),
611
- path.map(function (eachFolder, eachIndex) { return (react_2.default.createElement(react_2.default.Fragment, { key: eachFolder.id },
612
- react_2.default.createElement(HeroIcons.ChevronRightIcon, { className: "h-4 w-4 shrink-0 text-gray-400" }),
613
- react_2.default.createElement("button", { type: "button", className: "truncate font-medium ".concat(eachIndex === path.length - 1
614
- ? "text-gray-900"
615
- : "text-primary-600 hover:underline"), onClick: function () { return props.handleChangeFolder(eachFolder.id); } }, eachFolder.name))); }),
616
- props.searching && (react_2.default.createElement("span", { className: "ml-2 text-gray-500" }, "\u00B7 Search results"))),
617
- react_2.default.createElement("div", { className: "grow" }),
618
- react_2.default.createElement("div", { className: "flex flex-row flex-wrap items-center gap-2" },
619
- react_2.default.createElement("div", { className: "flex flex-row overflow-hidden rounded-md border border-gray-200" }, ["grid", "list"].map(function (eachLayout) {
620
- var Icon = eachLayout === "grid"
621
- ? HeroIcons.Squares2X2Icon
622
- : HeroIcons.ListBulletIcon;
623
- return (react_2.default.createElement("button", { key: eachLayout, type: "button", "aria-label": eachLayout === "grid" ? "Grid view" : "List view", "aria-pressed": layout === eachLayout, className: "p-1.5 ".concat(layout === eachLayout
624
- ? "bg-primary-50 text-primary-600"
625
- : "bg-white text-gray-500 hover:bg-gray-50"), onClick: function () { return handleChangeLayout(eachLayout); } },
626
- react_2.default.createElement(Icon, { className: "h-4 w-4" })));
627
- })),
628
- props.canCreate && (react_2.default.createElement(react_2.default.Fragment, null,
629
- react_2.default.createElement(button_1.Button, { text: "New folder", type: "light", size: "extraSmall", className: "whitespace-nowrap sm:!px-3", icon: react_2.default.createElement(HeroIcons.FolderPlusIcon, { className: "mr-1 h-4 w-4" }), onClick: function () {
630
- setFolderName("");
631
- setModalError("");
632
- setNewFolderOpen(true);
860
+ var renderIconLabel = function (label) { return (react_2.default.createElement("span", { className: "sr-only" }, label)); };
861
+ // INFO: the same row DataTemplate draws above a list — title on the left, a
862
+ // w-64 labelled search and icon actions on the right — so a drive page and a
863
+ // table page line up
864
+ var renderHeader = function () { return (react_2.default.createElement("div", { className: "flex py-2 w-full flex-row flex-wrap sm:flex-nowrap items-end justify-between sm:justify-start" },
865
+ (props.title || props.description) && (react_2.default.createElement("div", { className: "flex flex-col" },
866
+ props.title && (react_2.default.createElement("div", { className: "flex items-center gap-2" },
867
+ react_2.default.createElement("p", { className: "text-xl font-medium ".concat(props.darkMode ? "text-white" : "") }, props.title))),
868
+ props.description && (react_2.default.createElement("span", { className: "text-sm text-gray-500" }, props.description)))),
869
+ react_2.default.createElement("div", { className: "hidden sm:block grow" }),
870
+ react_2.default.createElement("div", { className: "ml-auto flex flex-row items-end" },
871
+ react_2.default.createElement("div", { className: "flex-1 sm:flex-none sm:w-64 min-w-0 mr-3" },
872
+ react_2.default.createElement(textInput_1.TextInput, { containerClassName: "w-full", label: "Search", placeholder: "Search", id: "drive-search", type: "text", value: searchText, onChange: function (event) { return setSearchText(event.target.value); }, darkMode: props.darkMode, showMagnifyIcon: true })),
873
+ react_2.default.createElement("div", { className: "flex flex-row items-end gap-x-2" },
874
+ react_2.default.createElement(iconButton_1.IconButton, { testId: "drive-layout", icon: layout === "grid" ? (react_2.default.createElement(react_2.default.Fragment, null,
875
+ react_2.default.createElement(HeroIcons.ListBulletIcon, { className: "h-7 w-7 text-primary-500" }),
876
+ renderIconLabel("List view"))) : (react_2.default.createElement(react_2.default.Fragment, null,
877
+ react_2.default.createElement(HeroIcons.Squares2X2Icon, { className: "h-7 w-7 text-primary-500" }),
878
+ renderIconLabel("Grid view"))), bgColor: props.darkMode ? "bg-white/10" : "bg-white", onClick: function () {
879
+ return handleChangeLayout(layout === "grid" ? "list" : "grid");
633
880
  } }),
634
- react_2.default.createElement(button_1.Button, { text: "Upload", type: "normal", size: "extraSmall", className: "whitespace-nowrap sm:!px-3", icon: react_2.default.createElement(HeroIcons.ArrowUpTrayIcon, { className: "mr-1 h-4 w-4" }), onClick: handleOpenUpload })))))); };
881
+ canCreate && (react_2.default.createElement(react_2.default.Fragment, null,
882
+ react_2.default.createElement(iconButton_1.IconButton, { testId: "drive-new-folder", icon: react_2.default.createElement(react_2.default.Fragment, null,
883
+ react_2.default.createElement(HeroIcons.FolderPlusIcon, { className: "h-7 w-7 text-primary-500" }),
884
+ renderIconLabel("New folder")), bgColor: props.darkMode ? "bg-white/10" : "bg-white", onClick: function () {
885
+ setFolderName("");
886
+ setModalError("");
887
+ setNewFolderOpen(true);
888
+ } }),
889
+ react_2.default.createElement(iconButton_1.IconButton, { testId: "drive-upload-open", icon: react_2.default.createElement(react_2.default.Fragment, null,
890
+ react_2.default.createElement(HeroIcons.ArrowUpTrayIcon, { className: "h-7 w-7 text-white" }),
891
+ renderIconLabel("Upload")), background: "bg-primary-500 hover:bg-primary-600", onClick: handleOpenUpload }))))))); };
892
+ var renderPath = function () {
893
+ var currentClass = props.darkMode ? "text-white" : "text-gray-900";
894
+ return (react_2.default.createElement("nav", { "aria-label": "Drive path", className: "flex min-w-0 flex-row flex-wrap items-center gap-x-1 py-2 text-sm" },
895
+ react_2.default.createElement("button", { type: "button", className: "font-medium ".concat(folderId && !searching
896
+ ? "text-primary-600 hover:underline"
897
+ : currentClass), onClick: function () {
898
+ setSearchText("");
899
+ setSearchQuery("");
900
+ handleChangeFolder("");
901
+ } }, "Drive"),
902
+ !searching &&
903
+ path.map(function (eachFolder, eachIndex) { return (react_2.default.createElement(react_2.default.Fragment, { key: eachFolder.id },
904
+ react_2.default.createElement(HeroIcons.ChevronRightIcon, { className: "h-4 w-4 shrink-0 text-gray-400" }),
905
+ react_2.default.createElement("button", { type: "button", className: "truncate font-medium ".concat(eachIndex === path.length - 1
906
+ ? currentClass
907
+ : "text-primary-600 hover:underline"), onClick: function () { return handleChangeFolder(eachFolder.id); } }, eachFolder.name))); }),
908
+ searching && (react_2.default.createElement("span", { className: "ml-2 text-gray-500" }, "\u00B7 Search results"))));
909
+ };
635
910
  var renderFolderIcon = function (sizeClass) { return (react_2.default.createElement(HeroIcons.FolderIcon, { className: "".concat(sizeClass, " shrink-0 text-primary-400") })); };
636
911
  // INFO: folders as compact rows above the file tiles, the way a file browser
637
912
  // lays them out — a folder has no picture, so a tile-sized card for it is
638
913
  // mostly empty space
639
914
  var renderGrid = function () {
640
915
  var folderRows = sortedRows.filter(function (eachRow) {
641
- return (0, driveUtils_1.handleIsDriveFolder)(eachRow, settings);
916
+ return (0, driveUtils_1.handleIsDriveFolder)(eachRow);
642
917
  });
643
- var fileRows = sortedRows.filter(function (eachRow) { return !(0, driveUtils_1.handleIsDriveFolder)(eachRow, settings); });
918
+ var fileRows = sortedRows.filter(function (eachRow) { return !(0, driveUtils_1.handleIsDriveFolder)(eachRow); });
644
919
  return (react_2.default.createElement("div", { className: "flex flex-col gap-y-4" },
645
920
  folderRows.length > 0 && (react_2.default.createElement("div", { className: "grid grid-cols-1 gap-3 sm:grid-cols-2 lg:grid-cols-4" }, folderRows.map(function (eachRow) {
646
921
  var _a;
647
922
  var rowId = (0, driveUtils_1.handleGetRowId)(eachRow);
648
- return (react_2.default.createElement("div", { key: rowId, "data-testid": "drive-item-".concat(rowId), className: "flex min-w-0 flex-row items-center gap-x-2 rounded-lg border border-gray-200 bg-white pr-1 hover:bg-gray-50" },
923
+ return (react_2.default.createElement("div", { key: rowId, "data-testid": "drive-item-".concat(rowId), className: "flex min-w-0 flex-row items-center gap-x-2 rounded-lg border pr-1 ".concat(surface) },
649
924
  react_2.default.createElement("button", { type: "button", onClick: function () { return handleOpenRow(eachRow); }, className: "flex min-w-0 grow flex-col items-start py-2 pl-3 text-left" },
650
925
  react_2.default.createElement("span", { className: "flex w-full min-w-0 flex-row items-center gap-x-2" },
651
926
  renderFolderIcon("h-5 w-5"),
652
- react_2.default.createElement("span", { className: "truncate text-sm font-medium text-gray-900" }, String((_a = eachRow === null || eachRow === void 0 ? void 0 : eachRow[settings.nameColumnId]) !== null && _a !== void 0 ? _a : ""))),
653
- props.searching && (react_2.default.createElement("span", { className: "w-full truncate pl-7 text-xs text-gray-500" }, renderRowPath(eachRow)))),
927
+ react_2.default.createElement("span", { className: "truncate text-sm font-medium ".concat(textStrong) }, String((_a = eachRow === null || eachRow === void 0 ? void 0 : eachRow[driveUtils_1.DRIVE_COLUMNS.name]) !== null && _a !== void 0 ? _a : ""))),
928
+ searching && (react_2.default.createElement("span", { className: "w-full truncate pl-7 text-xs text-gray-500" }, handleFormatRowPath(eachRow)))),
654
929
  renderMenu(eachRow)));
655
930
  }))),
656
931
  fileRows.length > 0 && (react_2.default.createElement("div", { className: "grid grid-cols-2 gap-4 sm:grid-cols-3 lg:grid-cols-4" }, fileRows.map(function (eachRow) {
657
932
  var _a;
658
933
  var rowId = (0, driveUtils_1.handleGetRowId)(eachRow);
659
934
  var file = handleResolveFile(eachRow);
935
+ var menu = renderMenu(eachRow);
660
936
  return (react_2.default.createElement("div", { key: rowId, "data-testid": "drive-item-".concat(rowId), className: "relative min-w-0" },
661
- file ? (react_2.default.createElement(filePreview_1.FilePreview, { variant: "tile", file: file, onClick: function () { return handleOpenRow(eachRow); } })) : (react_2.default.createElement("button", { type: "button", onClick: function () { return handleOpenRow(eachRow); }, className: "flex aspect-video w-full flex-col items-center justify-center gap-y-2 rounded-lg border border-gray-200" },
937
+ file ? (react_2.default.createElement(filePreview_1.FilePreview, { variant: "tile", darkMode: dark, file: file, onClick: function () { return handleOpenRow(eachRow); } })) : (react_2.default.createElement("button", { type: "button", onClick: function () { return handleOpenRow(eachRow); }, className: "flex aspect-video w-full flex-col items-center justify-center gap-y-2 rounded-lg border ".concat(surface) },
662
938
  react_2.default.createElement(HeroIcons.DocumentIcon, { className: "h-8 w-8 text-gray-400" }),
663
- react_2.default.createElement("span", { className: "w-full truncate px-2 text-sm text-gray-900" }, String((_a = eachRow === null || eachRow === void 0 ? void 0 : eachRow[settings.nameColumnId]) !== null && _a !== void 0 ? _a : "")))),
664
- props.searching && (react_2.default.createElement("p", { className: "mt-1 truncate text-xs text-gray-500" }, renderRowPath(eachRow))),
665
- react_2.default.createElement("div", { className: "absolute right-1 top-1 rounded-md bg-white/90 shadow-sm" }, renderMenu(eachRow))));
939
+ react_2.default.createElement("span", { className: "w-full truncate px-2 text-sm ".concat(textStrong) }, String((_a = eachRow === null || eachRow === void 0 ? void 0 : eachRow[driveUtils_1.DRIVE_COLUMNS.name]) !== null && _a !== void 0 ? _a : "")))),
940
+ searching && (react_2.default.createElement("p", { className: "mt-1 truncate text-xs text-gray-500" }, handleFormatRowPath(eachRow))),
941
+ menu && (react_2.default.createElement("div", { className: "absolute right-1 top-1 rounded-md shadow-sm ".concat(dark ? "bg-gray-900/90" : "bg-white/90") }, menu))));
666
942
  })))));
667
943
  };
668
944
  var renderList = function () { return (react_2.default.createElement(newList_1.NewList, { list: sortedRows.map(function (eachRow) {
669
945
  var _a;
670
- var isFolder = (0, driveUtils_1.handleIsDriveFolder)(eachRow, settings);
671
- var rowPath = renderRowPath(eachRow);
672
- var size = !isFolder && settings.sizeColumnId
673
- ? eachRow === null || eachRow === void 0 ? void 0 : eachRow[settings.sizeColumnId]
674
- : undefined;
946
+ var isFolder = (0, driveUtils_1.handleIsDriveFolder)(eachRow);
675
947
  var description = [
676
- rowPath,
677
- typeof size === "number" ? "".concat(Math.max(1, Math.round(size / 1024)), " KB") : "",
948
+ handleFormatRowPath(eachRow),
949
+ isFolder ? "" : (0, driveUtils_1.handleFormatFileSize)(eachRow === null || eachRow === void 0 ? void 0 : eachRow[driveUtils_1.DRIVE_COLUMNS.size]),
678
950
  ]
679
951
  .filter(function (eachPart) { return !!eachPart; })
680
952
  .join(" · ");
681
- return __assign(__assign({ id: (0, driveUtils_1.handleGetRowId)(eachRow), title: String((_a = eachRow === null || eachRow === void 0 ? void 0 : eachRow[settings.nameColumnId]) !== null && _a !== void 0 ? _a : ""), icon: isFolder
682
- ? renderFolderIcon("h-6 w-6")
683
- : react_2.default.createElement(HeroIcons.DocumentIcon, { className: "h-6 w-6 text-gray-400" }) }, (description ? { description: description } : {})), { action: renderMenu(eachRow) });
953
+ var menu = renderMenu(eachRow);
954
+ return __assign(__assign({ id: (0, driveUtils_1.handleGetRowId)(eachRow), title: String((_a = eachRow === null || eachRow === void 0 ? void 0 : eachRow[driveUtils_1.DRIVE_COLUMNS.name]) !== null && _a !== void 0 ? _a : ""), icon: isFolder ? (renderFolderIcon("h-6 w-6")) : (react_2.default.createElement(HeroIcons.DocumentIcon, { className: "h-6 w-6 text-gray-400" })) }, (description ? { description: description } : {})), (menu ? { action: menu } : {}));
684
955
  }), hidePagination: true, onClickItem: function (item) {
685
956
  return handleOpenRow(sortedRows.find(function (eachRow) { return (0, driveUtils_1.handleGetRowId)(eachRow) === item.id; }));
686
957
  }, darkMode: props.darkMode })); };
687
958
  var renderContent = function () {
688
- if (props.loading)
959
+ if (loading)
689
960
  return (react_2.default.createElement("div", { className: "flex flex-col items-center py-10" },
690
961
  react_2.default.createElement(loading_1.Loading, null)));
691
962
  if (!sortedRows.length)
692
- return (react_2.default.createElement(emptyState_1.EmptyState, { text: props.searching ? "Nothing matches the search" : "This folder is empty", icon: renderFolderIcon("h-10 w-10") }));
963
+ return (react_2.default.createElement(emptyState_1.EmptyState, { darkMode: dark, text: searching ? "Nothing matches the search" : "This folder is empty", icon: renderFolderIcon("h-10 w-10") }));
693
964
  return layout === "grid" ? renderGrid() : renderList();
694
965
  };
695
966
  var renderNameForm = function (value, onChange, onSubmit, submitText, placeholder) { return (
@@ -701,7 +972,7 @@ var DriveView = function (props) {
701
972
  event.stopPropagation();
702
973
  onSubmit();
703
974
  }, className: "flex flex-col gap-y-3" },
704
- react_2.default.createElement(textInput_1.TextInput, { id: "drive-name", type: "text", label: "", placeholder: placeholder, value: value, autoFocus: true, onChange: function (event) { return onChange(event.target.value); } }),
975
+ react_2.default.createElement(textInput_1.TextInput, { id: "drive-name", darkMode: dark, type: "text", label: "", placeholder: placeholder, value: value, autoFocus: true, onChange: function (event) { return onChange(event.target.value); } }),
705
976
  modalError && react_2.default.createElement("p", { className: "text-sm text-red-600" }, modalError),
706
977
  react_2.default.createElement("div", { className: "flex flex-row justify-end gap-x-3" },
707
978
  react_2.default.createElement(button_1.Button, { text: "Cancel", type: "light", size: "small", onClick: handleCloseModals }),
@@ -711,14 +982,17 @@ var DriveView = function (props) {
711
982
  if (!moveRow)
712
983
  return null;
713
984
  var rowId = (0, driveUtils_1.handleGetRowId)(moveRow);
714
- var currentParent = String((_a = moveRow === null || moveRow === void 0 ? void 0 : moveRow[settings.parentColumnId]) !== null && _a !== void 0 ? _a : "");
715
- var targets = (0, driveUtils_1.handleGetMoveTargets)(folders, rowId, (0, driveUtils_1.handleIsDriveFolder)(moveRow, settings))
985
+ var currentParent = String((_a = moveRow === null || moveRow === void 0 ? void 0 : moveRow[driveUtils_1.DRIVE_COLUMNS.parentId]) !== null && _a !== void 0 ? _a : "");
986
+ var targets = (0, driveUtils_1.handleGetMoveTargets)(folders, rowId, (0, driveUtils_1.handleIsDriveFolder)(moveRow))
716
987
  .map(function (eachFolder) { return ({
717
988
  folder: eachFolder,
718
989
  label: (0, driveUtils_1.handleFormatFolderPath)((0, driveUtils_1.handleBuildFolderPath)(folders, eachFolder.id)),
719
990
  }); })
720
991
  .sort(function (a, b) {
721
- return a.label.localeCompare(b.label, undefined, { numeric: true, sensitivity: "base" });
992
+ return a.label.localeCompare(b.label, undefined, {
993
+ numeric: true,
994
+ sensitivity: "base",
995
+ });
722
996
  });
723
997
  var options = __spreadArray([
724
998
  { id: "", label: "Drive (top level)" }
@@ -729,7 +1003,7 @@ var DriveView = function (props) {
729
1003
  return (react_2.default.createElement("div", { className: "flex flex-col gap-y-3" },
730
1004
  modalError && react_2.default.createElement("p", { className: "text-sm text-red-600" }, modalError),
731
1005
  react_2.default.createElement("div", { className: "max-h-80 overflow-y-auto" },
732
- react_2.default.createElement(newList_1.NewList, { list: options.map(function (eachOption) { return ({
1006
+ react_2.default.createElement(newList_1.NewList, { darkMode: dark, list: options.map(function (eachOption) { return ({
733
1007
  id: eachOption.id || "__root__",
734
1008
  title: eachOption.label,
735
1009
  icon: renderFolderIcon("h-5 w-5"),
@@ -741,14 +1015,16 @@ var DriveView = function (props) {
741
1015
  var _a, _b;
742
1016
  if (!deleteRow)
743
1017
  return null;
744
- var isFolder = (0, driveUtils_1.handleIsDriveFolder)(deleteRow, settings);
745
- var name = String((_a = deleteRow === null || deleteRow === void 0 ? void 0 : deleteRow[settings.nameColumnId]) !== null && _a !== void 0 ? _a : "");
1018
+ var isFolder = (0, driveUtils_1.handleIsDriveFolder)(deleteRow);
1019
+ var name = String((_a = deleteRow === null || deleteRow === void 0 ? void 0 : deleteRow[driveUtils_1.DRIVE_COLUMNS.name]) !== null && _a !== void 0 ? _a : "");
746
1020
  var inside = ((_b = deleteIds === null || deleteIds === void 0 ? void 0 : deleteIds.length) !== null && _b !== void 0 ? _b : 1) - 1;
747
1021
  return (react_2.default.createElement("div", { className: "flex flex-col gap-y-3" },
748
- react_2.default.createElement("p", { className: "text-sm text-gray-700" }, !isFolder
1022
+ react_2.default.createElement("p", { className: "text-sm ".concat(textBody) }, !isFolder
749
1023
  ? "Delete \"".concat(name, "\"? This cannot be undone.")
750
1024
  : deleteIds === null
751
- ? "Counting what is inside…"
1025
+ ? modalError
1026
+ ? "\"".concat(name, "\" was not deleted.")
1027
+ : "Counting what is inside…"
752
1028
  : inside > 0
753
1029
  ? "Delete \"".concat(name, "\" and the ").concat(inside, " item(s) inside it? This cannot be undone.")
754
1030
  : "Delete the empty folder \"".concat(name, "\"?")),
@@ -761,7 +1037,7 @@ var DriveView = function (props) {
761
1037
  var _a;
762
1038
  var queued = (_a = uploadFiles.upload) !== null && _a !== void 0 ? _a : [];
763
1039
  return (react_2.default.createElement("div", { className: "flex flex-col gap-y-3" },
764
- react_2.default.createElement(uploadInput_1.UploadInput, { lib: props.lib, id: "drive-upload", label: "", type: "multi", files: uploadFiles, disabled: working, onChange: function (_a) {
1040
+ react_2.default.createElement(uploadInput_1.UploadInput, { darkMode: dark, lib: props.lib, id: "drive-upload", label: "", type: "multi", files: uploadFiles, disabled: working, onChange: function (_a) {
765
1041
  var value = _a.value, action = _a.action;
766
1042
  if (action === "upload")
767
1043
  setUploadFiles(function (current) {
@@ -777,7 +1053,7 @@ var DriveView = function (props) {
777
1053
  queued.length > 0 && Object.keys(uploadStatus).length > 0 && (react_2.default.createElement("ul", { className: "flex flex-col gap-y-1 text-sm" }, queued.map(function (eachFile) {
778
1054
  var _a, _b;
779
1055
  return (react_2.default.createElement("li", { key: eachFile.token, className: "flex flex-row gap-x-2" },
780
- react_2.default.createElement("span", { className: "truncate text-gray-700" }, (_b = (_a = eachFile.fileName) === null || _a === void 0 ? void 0 : _a.name) !== null && _b !== void 0 ? _b : ""),
1056
+ react_2.default.createElement("span", { className: "truncate ".concat(textBody) }, (_b = (_a = eachFile.fileName) === null || _a === void 0 ? void 0 : _a.name) !== null && _b !== void 0 ? _b : ""),
781
1057
  react_2.default.createElement("span", { className: "grow" }),
782
1058
  react_2.default.createElement("span", { className: uploadStatus[eachFile.token] === "failed"
783
1059
  ? "text-red-600"
@@ -791,34 +1067,34 @@ var DriveView = function (props) {
791
1067
  }))),
792
1068
  modalError && react_2.default.createElement("p", { className: "text-sm text-red-600" }, modalError),
793
1069
  react_2.default.createElement("div", { className: "flex flex-row justify-end gap-x-3" },
794
- react_2.default.createElement(button_1.Button, { text: "Close", type: "light", size: "small", onClick: handleCloseUpload }),
1070
+ react_2.default.createElement(button_1.Button, { text: "Close", type: "light", size: "small", disable: working, onClick: handleCloseUpload }),
795
1071
  react_2.default.createElement(button_1.Button, { text: working ? "Uploading…" : "Upload ".concat(queued.length || "").trim(), type: "normal", size: "small", disable: working || queued.length === 0, onClick: handleUploadAll }))));
796
1072
  };
797
1073
  var renderPreviewContent = function () {
798
- var _a, _b;
1074
+ var _a, _b, _c;
799
1075
  var file = previewRow ? handleResolveFile(previewRow) : null;
800
1076
  if (!file)
801
1077
  return null;
802
1078
  var kind = (0, filePreview_1.handleResolveFileKind)(file);
1079
+ var details = [
1080
+ (0, driveUtils_1.handleFormatFileSize)(previewRow === null || previewRow === void 0 ? void 0 : previewRow[driveUtils_1.DRIVE_COLUMNS.size]),
1081
+ (_a = file.mimeType) !== null && _a !== void 0 ? _a : "",
1082
+ ]
1083
+ .filter(function (eachPart) { return !!eachPart; })
1084
+ .join(" · ");
803
1085
  return (react_2.default.createElement("div", { className: "flex flex-col gap-y-3" },
804
- kind === "image" && (
805
- // eslint-disable-next-line jsx-a11y/img-redundant-alt
806
- react_2.default.createElement("img", { src: file.fileName, alt: (_a = file.label) !== null && _a !== void 0 ? _a : "", className: "max-h-[70vh] w-full object-contain" })),
807
- kind === "pdf" && (react_2.default.createElement("iframe", { title: (_b = file.label) !== null && _b !== void 0 ? _b : "Preview", src: file.fileName, className: "h-[70vh] w-full" })),
1086
+ kind === "image" && (react_2.default.createElement("img", { src: file.fileName, alt: (_b = file.label) !== null && _b !== void 0 ? _b : "", className: "max-h-[70vh] w-full object-contain" })),
1087
+ kind === "pdf" && (react_2.default.createElement("iframe", { title: (_c = file.label) !== null && _c !== void 0 ? _c : "Preview", src: file.fileName, className: "h-[70vh] w-full" })),
808
1088
  kind === "video" && (react_2.default.createElement("video", { src: file.fileName, controls: true, className: "max-h-[70vh] w-full" })),
809
- kind !== "image" && kind !== "pdf" && kind !== "video" && (react_2.default.createElement("p", { className: "text-sm text-gray-600" }, "This file has no preview.")),
810
- react_2.default.createElement("div", { className: "flex flex-row justify-end gap-x-3" },
811
- react_2.default.createElement(button_1.Button, { text: "Details", type: "light", size: "small", onClick: function () {
812
- var rowId = (0, driveUtils_1.handleGetRowId)(previewRow);
813
- setPreviewRow(null);
814
- props.handleOpenDetails(rowId);
815
- } }),
1089
+ kind !== "image" && kind !== "pdf" && kind !== "video" && (react_2.default.createElement("p", { className: "text-sm ".concat(textBody) }, "This file has no preview.")),
1090
+ react_2.default.createElement("div", { className: "flex flex-row items-center justify-end gap-x-3" },
1091
+ details && (react_2.default.createElement("p", { className: "mr-auto text-sm text-gray-500" }, details)),
816
1092
  react_2.default.createElement("a", { href: file.fileName, target: "_blank", rel: "noopener noreferrer", className: "rounded-md bg-primary-600 px-3 py-1.5 text-sm font-medium text-white hover:bg-primary-700" }, "Download"))));
817
1093
  };
818
1094
  return (react_2.default.createElement("div", {
819
1095
  // INFO: a minimum height so an empty or near-empty folder is still a
820
1096
  // drop target worth aiming at
821
- className: "relative mt-4 flex min-h-[240px] flex-col gap-y-4 rounded-lg p-4 ".concat(props.darkMode ? "bg-white/5" : "bg-white"), "data-testid": "drive-view", onDragEnter: function (event) {
1097
+ className: "relative flex min-h-[240px] flex-col", "data-testid": "drive-view", onDragEnter: function (event) {
822
1098
  if (!handleIsFileDrag(event))
823
1099
  return;
824
1100
  event.preventDefault();
@@ -842,21 +1118,25 @@ var DriveView = function (props) {
842
1118
  event.preventDefault();
843
1119
  handleDropFiles(Array.from(event.dataTransfer.files));
844
1120
  } },
845
- renderToolbar(),
846
- renderContent(),
1121
+ renderHeader(),
1122
+ renderPath(),
1123
+ react_2.default.createElement("div", { className: "mt-2" }, renderContent()),
847
1124
  renderDropOverlay(),
848
- react_2.default.createElement(modal_1.Modal, { open: newFolderOpen, title: "New folder", onClose: handleCloseModals, renderContent: function () {
1125
+ react_2.default.createElement(modal_1.Modal, { darkMode: dark, open: newFolderOpen, title: "New folder", onClose: handleCloseModals, renderContent: function () {
849
1126
  return renderNameForm(folderName, setFolderName, handleCreateFolder, "Create", "Folder name");
850
1127
  } }),
851
- react_2.default.createElement(modal_1.Modal, { open: !!renameRow, title: "Rename", onClose: handleCloseModals, renderContent: function () {
1128
+ react_2.default.createElement(modal_1.Modal, { darkMode: dark, open: !!renameRow, title: "Rename", onClose: handleCloseModals, renderContent: function () {
852
1129
  return renderNameForm(renameValue, setRenameValue, handleRename, "Save", "Name");
853
1130
  } }),
854
- react_2.default.createElement(modal_1.Modal, { open: !!moveRow, title: "Move \"".concat(String((_b = moveRow === null || moveRow === void 0 ? void 0 : moveRow[settings.nameColumnId]) !== null && _b !== void 0 ? _b : ""), "\" to"), onClose: handleCloseModals, renderContent: renderMoveContent }),
855
- react_2.default.createElement(modal_1.Modal, { open: !!deleteRow, title: "Delete", onClose: handleCloseModals, renderContent: renderDeleteContent }),
856
- react_2.default.createElement(modal_1.Modal, { open: uploadOpen, title: "Upload files", maxWidth: "2xl", onClose: handleCloseUpload, renderContent: renderUploadContent }),
857
- react_2.default.createElement(modal_1.Modal, { open: !!previewRow, title: String((_c = previewRow === null || previewRow === void 0 ? void 0 : previewRow[settings.nameColumnId]) !== null && _c !== void 0 ? _c : ""), maxWidth: "4xl", onClose: function () { return setPreviewRow(null); }, renderContent: renderPreviewContent })));
1131
+ react_2.default.createElement(modal_1.Modal, { darkMode: dark, open: !!moveRow, title: "Move \"".concat(String((_f = moveRow === null || moveRow === void 0 ? void 0 : moveRow[driveUtils_1.DRIVE_COLUMNS.name]) !== null && _f !== void 0 ? _f : ""), "\" to"), onClose: handleCloseModals, renderContent: renderMoveContent }),
1132
+ react_2.default.createElement(modal_1.Modal, { darkMode: dark, open: !!deleteRow, title: "Delete", onClose: handleCloseModals, renderContent: renderDeleteContent }),
1133
+ react_2.default.createElement(modal_1.Modal, { darkMode: dark, open: uploadOpen, title: "Upload files", maxWidth: "2xl",
1134
+ // INFO: no closing mid-upload — the rows still being written belong to
1135
+ // the folder the upload started in
1136
+ onClose: working ? undefined : handleCloseUpload, renderContent: renderUploadContent }),
1137
+ react_2.default.createElement(modal_1.Modal, { darkMode: dark, open: !!previewRow, title: String((_g = previewRow === null || previewRow === void 0 ? void 0 : previewRow[driveUtils_1.DRIVE_COLUMNS.name]) !== null && _g !== void 0 ? _g : ""), maxWidth: "4xl", onClose: function () { return setPreviewRow(null); }, renderContent: renderPreviewContent })));
858
1138
  };
859
- exports.DriveView = DriveView;
1139
+ exports.DriveTemplate = DriveTemplate;
860
1140
  /**
861
1141
  * The per-item action menu.
862
1142
  *
@@ -867,11 +1147,17 @@ exports.DriveView = DriveView;
867
1147
  var DriveItemMenu = function (props) {
868
1148
  var popUpRef = (0, react_2.useRef)(null);
869
1149
  return (react_2.default.createElement("div", { "data-testid": props.testId, onClick: function (event) { return event.stopPropagation(); } },
870
- react_2.default.createElement(popUp_1.PopUp, { ref: popUpRef, portal: true, anchor: "right", direction: "bottom", lib: { Transition: react_1.Transition }, ctaComp: react_2.default.createElement("button", { type: "button", "aria-label": "Actions", className: "rounded-md p-1.5 text-gray-500 hover:bg-gray-100 hover:text-gray-700" },
871
- react_2.default.createElement(HeroIcons.EllipsisVerticalIcon, { className: "h-4 w-4" })), renderComp: react_2.default.createElement("div", { className: "flex min-w-[140px] flex-col divide-y overflow-hidden rounded-md bg-white shadow-md" }, props.items.map(function (eachItem) { return (react_2.default.createElement("button", { key: eachItem.text, type: "button", className: "px-4 py-2 text-left text-sm hover:bg-gray-100 ".concat(eachItem.text === "Delete" ? "text-red-600" : "text-gray-900"), onClick: function () {
1150
+ react_2.default.createElement(popUp_1.PopUp, { ref: popUpRef, portal: true, anchor: "right", direction: "bottom", lib: { Transition: react_1.Transition }, ctaComp: react_2.default.createElement("button", { type: "button", "aria-label": "Actions", className: "rounded-md p-1.5 ".concat(props.darkMode
1151
+ ? "text-gray-300 hover:bg-white/10 hover:text-white"
1152
+ : "text-gray-500 hover:bg-gray-100 hover:text-gray-700") },
1153
+ react_2.default.createElement(HeroIcons.EllipsisVerticalIcon, { className: "h-4 w-4" })), renderComp: react_2.default.createElement("div", { className: "flex min-w-[140px] flex-col divide-y overflow-hidden rounded-md shadow-md ".concat(props.darkMode ? "divide-white/10 bg-gray-900" : "bg-white") }, props.items.map(function (eachItem) { return (react_2.default.createElement("button", { key: eachItem.text, type: "button", className: "px-4 py-2 text-left text-sm ".concat(props.darkMode ? "hover:bg-white/10" : "hover:bg-gray-100", " ").concat(eachItem.text === "Delete"
1154
+ ? "text-red-600"
1155
+ : props.darkMode
1156
+ ? "text-white"
1157
+ : "text-gray-900"), onClick: function () {
872
1158
  var _a;
873
1159
  (_a = popUpRef.current) === null || _a === void 0 ? void 0 : _a.handleOnClose();
874
1160
  eachItem.onClick();
875
1161
  } }, eachItem.text)); })) })));
876
1162
  };
877
- //# sourceMappingURL=driveView.js.map
1163
+ //# sourceMappingURL=index.js.map