blixify-ui-web 1.2.112 → 1.2.114

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 (37) 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/driveTemplate/driveUtils.d.ts +71 -0
  14. package/lib/components/data/driveTemplate/driveUtils.d.ts.map +1 -0
  15. package/lib/components/data/driveTemplate/driveUtils.js +298 -0
  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} +552 -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 +0 -43
  32. package/lib/components/data/dataTemplate/driveUtils.d.ts.map +0 -1
  33. package/lib/components/data/dataTemplate/driveUtils.js +0 -153
  34. package/lib/components/data/dataTemplate/driveUtils.js.map +0 -1
  35. package/lib/components/data/dataTemplate/driveView.d.ts +0 -43
  36. package/lib/components/data/dataTemplate/driveView.d.ts.map +0 -1
  37. 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,214 @@ 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)(""), previewSrc = _y[0], setPreviewSrc = _y[1];
147
+ var _z = (0, react_2.useState)(false), uploadOpen = _z[0], setUploadOpen = _z[1];
148
+ var _0 = (0, react_2.useState)({
132
149
  data: [],
133
150
  upload: [],
134
151
  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];
152
+ }), uploadFiles = _0[0], setUploadFiles = _0[1];
153
+ var _1 = (0, react_2.useState)({}), uploadStatus = _1[0], setUploadStatus = _1[1];
154
+ var _2 = (0, react_2.useState)(false), working = _2[0], setWorking = _2[1];
155
+ var _3 = (0, react_2.useState)(false), dragging = _3[0], setDragging = _3[1];
156
+ var _4 = (0, react_2.useState)(""), modalError = _4[0], setModalError = _4[1];
140
157
  // INFO: dragenter and dragleave fire for every child the pointer crosses, so a
141
158
  // plain boolean flickers off over each tile; a depth count only reaches zero
142
159
  // when the drag has really left the drive
143
160
  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);
161
+ // INFO: a slow folder answering after a faster one must not overwrite it
162
+ var fetchSeqRef = (0, react_2.useRef)(0);
163
+ var folderSeqRef = (0, react_2.useRef)(0);
164
+ // INFO: a folder's contents are counted before its delete is confirmed. A
165
+ // count that finishes after the dialog closed or moved to another item must
166
+ // not attach its ids to that item's Delete button
167
+ var deleteSeqRef = (0, react_2.useRef)(0);
168
+ // INFO: what the drive is showing right now. A refresh that fires after a
169
+ // long upload reads this rather than the folder open when the upload began,
170
+ // or it would load the old folder under the new folder's path
171
+ var viewRef = (0, react_2.useRef)({
172
+ folderId: "",
173
+ searchQuery: "",
174
+ additionalQuery: [],
175
+ });
176
+ var devSettings = props.devSettings;
177
+ var serverId = (0, utils_1.parseServerId)(devSettings.server);
178
+ var folderId = (_a = props.folderId) !== null && _a !== void 0 ? _a : innerFolderId;
179
+ var additionalQuery = (_b = props.additionalQuery) !== null && _b !== void 0 ? _b : [];
180
+ var additionalQueryKey = JSON.stringify(additionalQuery);
181
+ var searching = !!searchQuery;
182
+ var dark = !!props.darkMode;
183
+ // INFO: one set of surface and text classes so every tile, menu and message
184
+ // follows darkMode, not only the header
185
+ var textStrong = dark ? "text-white" : "text-gray-900";
186
+ var textBody = dark ? "text-gray-300" : "text-gray-700";
187
+ var surface = dark
188
+ ? "border-white/10 bg-white/5 hover:bg-white/10"
189
+ : "border-gray-200 bg-white hover:bg-gray-50";
190
+ var canCreate = !!((_c = props.permissions) === null || _c === void 0 ? void 0 : _c.create);
191
+ var canUpdate = !!((_d = props.permissions) === null || _d === void 0 ? void 0 : _d.update);
192
+ var canDelete = !!((_e = props.permissions) === null || _e === void 0 ? void 0 : _e.delete);
193
+ var uploadEndpoint = devSettings.uploadEndpoint || "/api/asset";
194
+ var readQuery = (0, react_2.useMemo)(function () {
195
+ return new readQuery_1.ReadQuery({
196
+ dbModule: props.lib.axios,
197
+ dbType: devSettings.server,
198
+ dbCollection: props.collectionId,
199
+ dbEndpoint: devSettings.dbEndpoint,
200
+ dbAPIToken: devSettings.dbAPIToken,
201
+ dbUserToken: devSettings.dbUserToken,
202
+ });
203
+ }, [
204
+ props.lib.axios,
205
+ props.collectionId,
206
+ devSettings.server,
207
+ devSettings.dbEndpoint,
208
+ devSettings.dbAPIToken,
209
+ devSettings.dbUserToken,
210
+ ]);
211
+ var writeQuery = (0, react_2.useMemo)(function () {
212
+ return new writeQuery_1.WriteQuery({
213
+ dbModule: props.lib.axios,
214
+ dbType: devSettings.server,
215
+ dbCollection: props.collectionId,
216
+ dbEndpoint: devSettings.dbEndpoint,
217
+ dbAPIToken: devSettings.dbAPIToken,
218
+ dbUserToken: devSettings.dbUserToken,
219
+ });
220
+ }, [
221
+ props.lib.axios,
222
+ props.collectionId,
223
+ devSettings.server,
224
+ devSettings.dbEndpoint,
225
+ devSettings.dbAPIToken,
226
+ devSettings.dbUserToken,
227
+ ]);
228
+ var sortedRows = (0, driveUtils_1.handleSortDriveRows)(rows);
229
+ var path = (0, driveUtils_1.handleBuildFolderPath)(folders, folderId);
230
+ viewRef.current = { folderId: folderId, searchQuery: searchQuery, additionalQuery: additionalQuery };
231
+ (0, react_2.useEffect)(function () {
232
+ var timer = setTimeout(function () { return setSearchQuery(searchText.trim()); }, SEARCH_DEBOUNCE_MS);
233
+ return function () { return clearTimeout(timer); };
234
+ }, [searchText]);
235
+ (0, react_2.useEffect)(function () {
236
+ handleFetchRows();
237
+ // eslint-disable-next-line react-hooks/exhaustive-deps
238
+ }, [props.collectionId, folderId, searchQuery, additionalQueryKey]);
151
239
  (0, react_2.useEffect)(function () {
152
240
  handleLoadFolders();
153
241
  // 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;
242
+ }, [props.collectionId, additionalQueryKey]);
243
+ (0, react_2.useEffect)(function () {
244
+ var file = previewRow ? handleResolveFile(previewRow) : null;
245
+ setPreviewSrc("");
246
+ if (!file || (0, filePreview_1.handleResolveFileKind)(file) !== "pdf")
247
+ return;
248
+ var active = true;
249
+ var revoke;
250
+ (0, driveUtils_1.handleResolvePreviewSource)(file.fileName, file.mimeType).then(function (result) {
251
+ var _a;
252
+ if (active) {
253
+ revoke = result.revoke;
254
+ setPreviewSrc(result.src);
255
+ }
256
+ else
257
+ (_a = result.revoke) === null || _a === void 0 ? void 0 : _a.call(result);
258
+ });
259
+ return function () {
260
+ active = false;
261
+ revoke === null || revoke === void 0 ? void 0 : revoke();
262
+ };
263
+ // eslint-disable-next-line react-hooks/exhaustive-deps
264
+ }, [previewRow]);
265
+ /**
266
+ * INFO: the whole folder in one unpaged read — folders sort first on the
267
+ * client, which only holds if nothing is left on a later page. A search
268
+ * reads across the whole drive instead of the open folder.
269
+ */
270
+ var handleFetchRows = function () { return __awaiter(void 0, void 0, void 0, function () {
271
+ var seq, view, res, err_1;
157
272
  var _a;
158
273
  return __generator(this, function (_b) {
159
274
  switch (_b.label) {
160
275
  case 0:
161
- _b.trys.push([0, 2, , 3]);
162
- return [4 /*yield*/, props.readQuery.call({
276
+ seq = ++fetchSeqRef.current;
277
+ view = viewRef.current;
278
+ setLoading(true);
279
+ _b.label = 1;
280
+ case 1:
281
+ _b.trys.push([1, 3, , 4]);
282
+ return [4 /*yield*/, readQuery.call({
163
283
  type: "list",
164
- query: __spreadArray(__spreadArray([], props.baseQuery, true), [
165
- { type: "=", queryId: settings.kindColumnId, value: driveUtils_1.DRIVE_FOLDER },
284
+ query: __spreadArray(__spreadArray([], view.additionalQuery, true), [
285
+ view.searchQuery
286
+ ? (0, driveUtils_1.handleBuildDriveSearchQuery)(view.searchQuery)
287
+ : (0, driveUtils_1.handleBuildDriveFolderQuery)(view.folderId),
166
288
  ], false),
167
289
  stopLimit: true,
168
290
  })];
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
291
  case 2:
292
+ res = _b.sent();
293
+ if (seq !== fetchSeqRef.current)
294
+ return [2 /*return*/];
295
+ setRows((_a = res === null || res === void 0 ? void 0 : res.data) !== null && _a !== void 0 ? _a : []);
296
+ return [3 /*break*/, 4];
297
+ case 3:
174
298
  err_1 = _b.sent();
175
- return [3 /*break*/, 3];
176
- case 3: return [2 /*return*/];
299
+ if (seq !== fetchSeqRef.current)
300
+ return [2 /*return*/];
301
+ setRows([]);
302
+ return [3 /*break*/, 4];
303
+ case 4:
304
+ setLoading(false);
305
+ return [2 /*return*/];
306
+ }
307
+ });
308
+ }); };
309
+ var handleLoadFolders = function () { return __awaiter(void 0, void 0, void 0, function () {
310
+ var seq, res, err_2;
311
+ var _a;
312
+ return __generator(this, function (_b) {
313
+ switch (_b.label) {
314
+ case 0:
315
+ seq = ++folderSeqRef.current;
316
+ _b.label = 1;
317
+ case 1:
318
+ _b.trys.push([1, 3, , 4]);
319
+ return [4 /*yield*/, readQuery.call({
320
+ type: "list",
321
+ query: __spreadArray(__spreadArray([], viewRef.current.additionalQuery, true), [
322
+ { type: "=", queryId: driveUtils_1.DRIVE_COLUMNS.kind, value: driveUtils_1.DRIVE_FOLDER },
323
+ ], false),
324
+ stopLimit: true,
325
+ })];
326
+ case 2:
327
+ res = _b.sent();
328
+ if (seq !== folderSeqRef.current)
329
+ return [2 /*return*/];
330
+ setFolders((0, driveUtils_1.handleMapDriveFolders)((_a = res === null || res === void 0 ? void 0 : res.data) !== null && _a !== void 0 ? _a : []));
331
+ return [3 /*break*/, 4];
332
+ case 3:
333
+ err_2 = _b.sent();
334
+ return [3 /*break*/, 4];
335
+ case 4: return [2 /*return*/];
177
336
  }
178
337
  });
179
338
  }); };
339
+ var handleChangeFolder = function (nextFolderId) {
340
+ if (props.onChangeFolder)
341
+ props.onChangeFolder(nextFolderId);
342
+ else
343
+ setInnerFolderId(nextFolderId);
344
+ };
180
345
  var handleChangeLayout = function (next) {
181
346
  setLayout(next);
182
347
  try {
@@ -187,39 +352,49 @@ var DriveView = function (props) {
187
352
  var handleAfterWrite = function () { return __awaiter(void 0, void 0, void 0, function () {
188
353
  return __generator(this, function (_a) {
189
354
  switch (_a.label) {
190
- case 0:
191
- props.handleRefresh();
192
- return [4 /*yield*/, handleLoadFolders()];
355
+ case 0: return [4 /*yield*/, Promise.all([handleFetchRows(), handleLoadFolders()])];
193
356
  case 1:
194
357
  _a.sent();
195
358
  return [2 /*return*/];
196
359
  }
197
360
  });
198
361
  }); };
362
+ var handleBuildPrefill = function () {
363
+ var _a;
364
+ var prefill = {};
365
+ ((_a = props.createPrefill) !== null && _a !== void 0 ? _a : []).forEach(function (eachPrefill) {
366
+ prefill[eachPrefill.key] = eachPrefill.value;
367
+ });
368
+ return prefill;
369
+ };
199
370
  var handleResolveFile = function (row) {
200
- var _a, _b;
201
- var value = row === null || row === void 0 ? void 0 : row[settings.fileColumnId];
371
+ var _a;
372
+ var value = row === null || row === void 0 ? void 0 : row[driveUtils_1.DRIVE_COLUMNS.file];
202
373
  var entry = Array.isArray(value) ? value[0] : value;
203
374
  if (!(entry === null || entry === void 0 ? void 0 : entry.token) || !(entry === null || entry === void 0 ? void 0 : entry.fileName))
204
375
  return null;
205
376
  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,
377
+ fileName: (0, blobModule_1.renderImageUrlFromPath)(devSettings.imageEndpoint, props.collectionId, (0, driveUtils_1.handleGetRowId)(row), entry.fileName, entry.token, devSettings.bucketName),
378
+ label: String((_a = row === null || row === void 0 ? void 0 : row[driveUtils_1.DRIVE_COLUMNS.name]) !== null && _a !== void 0 ? _a : "") || undefined,
208
379
  token: entry.token,
209
- mimeType: (_b = entry.mimeType) !== null && _b !== void 0 ? _b : (0, filePreview_1.handleGuessMimeType)(entry.fileName),
380
+ mimeType: (row === null || row === void 0 ? void 0 : row[driveUtils_1.DRIVE_COLUMNS.mimeType]) ||
381
+ entry.mimeType ||
382
+ (0, filePreview_1.handleGuessMimeType)(entry.fileName),
210
383
  };
211
384
  };
212
385
  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));
386
+ if ((0, driveUtils_1.handleIsDriveFolder)(row)) {
387
+ // INFO: a search reads the whole drive, so opening a folder from its
388
+ // results has to end the search or the folder shows the results again
389
+ setSearchText("");
390
+ setSearchQuery("");
391
+ handleChangeFolder((0, driveUtils_1.handleGetRowId)(row));
218
392
  }
219
393
  else
220
394
  setPreviewRow(row);
221
395
  };
222
396
  var handleCloseModals = function () {
397
+ deleteSeqRef.current += 1;
223
398
  setNewFolderOpen(false);
224
399
  setFolderName("");
225
400
  setRenameRow(null);
@@ -230,27 +405,25 @@ var DriveView = function (props) {
230
405
  setModalError("");
231
406
  };
232
407
  var handleCreateFolder = function () { return __awaiter(void 0, void 0, void 0, function () {
233
- var name, err_2;
408
+ var name, err_3;
234
409
  var _a;
235
410
  return __generator(this, function (_b) {
236
411
  switch (_b.label) {
237
412
  case 0:
413
+ // INFO: Enter and a phone's Done still submit while the button is disabled
414
+ if (working)
415
+ return [2 /*return*/];
238
416
  name = folderName.trim();
239
417
  if (!name)
240
418
  return [2 /*return*/, setModalError("Name the folder.")];
241
- if ((0, driveUtils_1.handleIsDuplicateFolderName)(folders, props.folderId, name))
419
+ if ((0, driveUtils_1.handleIsDuplicateFolderName)(folders, folderId, name))
242
420
  return [2 /*return*/, setModalError("A folder named \"".concat(name, "\" is already here."))];
243
421
  setWorking(true);
244
422
  _b.label = 1;
245
423
  case 1:
246
424
  _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),
425
+ return [4 /*yield*/, writeQuery.call("create", {
426
+ 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
427
  })];
255
428
  case 2:
256
429
  _b.sent();
@@ -260,7 +433,7 @@ var DriveView = function (props) {
260
433
  _b.sent();
261
434
  return [3 /*break*/, 5];
262
435
  case 4:
263
- err_2 = _b.sent();
436
+ err_3 = _b.sent();
264
437
  setModalError("The folder could not be created.");
265
438
  return [3 /*break*/, 5];
266
439
  case 5:
@@ -272,30 +445,32 @@ var DriveView = function (props) {
272
445
  var handleOpenRename = function (row) {
273
446
  var _a;
274
447
  setRenameRow(row);
275
- setRenameValue(String((_a = row === null || row === void 0 ? void 0 : row[settings.nameColumnId]) !== null && _a !== void 0 ? _a : ""));
448
+ setRenameValue(String((_a = row === null || row === void 0 ? void 0 : row[driveUtils_1.DRIVE_COLUMNS.name]) !== null && _a !== void 0 ? _a : ""));
276
449
  setModalError("");
277
450
  };
278
451
  var handleRename = function () { return __awaiter(void 0, void 0, void 0, function () {
279
- var name, rowId, err_3;
452
+ var name, rowId, err_4;
280
453
  var _a;
281
454
  var _b;
282
455
  return __generator(this, function (_c) {
283
456
  switch (_c.label) {
284
457
  case 0:
458
+ if (working)
459
+ return [2 /*return*/];
285
460
  name = renameValue.trim();
286
461
  if (!name)
287
462
  return [2 /*return*/, setModalError("A name is required.")];
288
463
  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))
464
+ if ((0, driveUtils_1.handleIsDriveFolder)(renameRow) &&
465
+ (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
466
  return [2 /*return*/, setModalError("A folder named \"".concat(name, "\" is already here."))];
292
467
  setWorking(true);
293
468
  _c.label = 1;
294
469
  case 1:
295
470
  _c.trys.push([1, 4, , 5]);
296
- return [4 /*yield*/, props.writeQuery.call("update", {
471
+ return [4 /*yield*/, writeQuery.call("update", {
297
472
  id: rowId,
298
- data: (_a = {}, _a[settings.nameColumnId] = name, _a),
473
+ data: (_a = {}, _a[driveUtils_1.DRIVE_COLUMNS.name] = name, _a),
299
474
  })];
300
475
  case 2:
301
476
  _c.sent();
@@ -305,7 +480,7 @@ var DriveView = function (props) {
305
480
  _c.sent();
306
481
  return [3 /*break*/, 5];
307
482
  case 4:
308
- err_3 = _c.sent();
483
+ err_4 = _c.sent();
309
484
  setModalError("The name could not be saved.");
310
485
  return [3 /*break*/, 5];
311
486
  case 5:
@@ -315,23 +490,25 @@ var DriveView = function (props) {
315
490
  });
316
491
  }); };
317
492
  var handleMove = function (targetFolderId) { return __awaiter(void 0, void 0, void 0, function () {
318
- var rowId, err_4;
493
+ var rowId, err_5;
319
494
  var _a;
320
495
  var _b;
321
496
  return __generator(this, function (_c) {
322
497
  switch (_c.label) {
323
498
  case 0:
499
+ if (working)
500
+ return [2 /*return*/];
324
501
  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))
502
+ if ((0, driveUtils_1.handleIsDriveFolder)(moveRow) &&
503
+ (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
504
  return [2 /*return*/, setModalError("A folder with this name is already there.")];
328
505
  setWorking(true);
329
506
  _c.label = 1;
330
507
  case 1:
331
508
  _c.trys.push([1, 4, , 5]);
332
- return [4 /*yield*/, props.writeQuery.call("update", {
509
+ return [4 /*yield*/, writeQuery.call("update", {
333
510
  id: rowId,
334
- data: (_a = {}, _a[settings.parentColumnId] = targetFolderId, _a),
511
+ data: (_a = {}, _a[driveUtils_1.DRIVE_COLUMNS.parentId] = targetFolderId, _a),
335
512
  })];
336
513
  case 2:
337
514
  _c.sent();
@@ -341,7 +518,7 @@ var DriveView = function (props) {
341
518
  _c.sent();
342
519
  return [3 /*break*/, 5];
343
520
  case 4:
344
- err_4 = _c.sent();
521
+ err_5 = _c.sent();
345
522
  setModalError("The item could not be moved.");
346
523
  return [3 /*break*/, 5];
347
524
  case 5:
@@ -351,49 +528,128 @@ var DriveView = function (props) {
351
528
  });
352
529
  }); };
353
530
  /**
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.
531
+ * Every row below a folder, read one level at a time.
532
+ *
533
+ * INFO: only the folder's own subtree is read — by parentId, one request per
534
+ * level, keeping just the id and folder columns — so counting a folder costs
535
+ * its size, not the size of the whole drive.
358
536
  */
359
- var handleOpenDelete = function (row) { return __awaiter(void 0, void 0, void 0, function () {
360
- var res, rowId, err_5;
537
+ var handleReadSubtreeIds = function (rootId, query) { return __awaiter(void 0, void 0, void 0, function () {
538
+ var found, seen, frontier, _loop_1, depth;
361
539
  var _a;
362
540
  return __generator(this, function (_b) {
363
541
  switch (_b.label) {
364
542
  case 0:
543
+ found = [];
544
+ seen = new Set([rootId]);
545
+ frontier = [rootId];
546
+ _loop_1 = function (depth) {
547
+ var res, next;
548
+ return __generator(this, function (_c) {
549
+ switch (_c.label) {
550
+ case 0: return [4 /*yield*/, readQuery.call({
551
+ type: "list",
552
+ query: __spreadArray(__spreadArray([], query, true), [
553
+ { type: "in", queryId: driveUtils_1.DRIVE_COLUMNS.parentId, value: frontier },
554
+ ], false),
555
+ attributeIds: [serverId, driveUtils_1.DRIVE_COLUMNS.parentId],
556
+ stopLimit: true,
557
+ })];
558
+ case 1:
559
+ res = _c.sent();
560
+ next = [];
561
+ ((_a = res === null || res === void 0 ? void 0 : res.data) !== null && _a !== void 0 ? _a : []).forEach(function (eachRow) {
562
+ var id = (0, driveUtils_1.handleGetRowId)(eachRow);
563
+ if (!id || seen.has(id))
564
+ return;
565
+ seen.add(id);
566
+ found.push(id);
567
+ next.push(id);
568
+ });
569
+ frontier = next;
570
+ return [2 /*return*/];
571
+ }
572
+ });
573
+ };
574
+ depth = 0;
575
+ _b.label = 1;
576
+ case 1:
577
+ if (!(frontier.length > 0 && depth < MAX_DELETE_DEPTH)) return [3 /*break*/, 4];
578
+ return [5 /*yield**/, _loop_1(depth)];
579
+ case 2:
580
+ _b.sent();
581
+ _b.label = 3;
582
+ case 3:
583
+ depth += 1;
584
+ return [3 /*break*/, 1];
585
+ case 4: return [2 /*return*/, found];
586
+ }
587
+ });
588
+ }); };
589
+ /**
590
+ * INFO: a folder's contents are counted before anything is deleted, so the
591
+ * confirmation says what will go. A scoped member's folder can hold rows
592
+ * outside their slice; deleting the folder would strand those rows under a
593
+ * parent that no longer exists, where no view can reach them, so such a
594
+ * folder is refused. When the unscoped count cannot be read the delete is
595
+ * refused too, rather than guessed.
596
+ */
597
+ var handleOpenDelete = function (row) { return __awaiter(void 0, void 0, void 0, function () {
598
+ var seq, rowId, visibleIds, err_6, allIds, err_7;
599
+ return __generator(this, function (_a) {
600
+ switch (_a.label) {
601
+ case 0:
602
+ seq = ++deleteSeqRef.current;
365
603
  setDeleteRow(row);
366
604
  setModalError("");
367
- if (!(0, driveUtils_1.handleIsDriveFolder)(row, settings)) {
605
+ if (!(0, driveUtils_1.handleIsDriveFolder)(row)) {
368
606
  setDeleteIds([(0, driveUtils_1.handleGetRowId)(row)]);
369
607
  return [2 /*return*/];
370
608
  }
371
609
  setDeleteIds(null);
372
- _b.label = 1;
610
+ rowId = (0, driveUtils_1.handleGetRowId)(row);
611
+ visibleIds = [];
612
+ _a.label = 1;
373
613
  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
- })];
614
+ _a.trys.push([1, 3, , 4]);
615
+ return [4 /*yield*/, handleReadSubtreeIds(rowId, additionalQuery)];
380
616
  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));
617
+ visibleIds = _a.sent();
386
618
  return [3 /*break*/, 4];
387
619
  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*/];
620
+ err_6 = _a.sent();
621
+ if (seq === deleteSeqRef.current)
622
+ setModalError("The folder's contents could not be read.");
623
+ return [2 /*return*/];
624
+ case 4:
625
+ if (!(additionalQuery.length > 0)) return [3 /*break*/, 8];
626
+ _a.label = 5;
627
+ case 5:
628
+ _a.trys.push([5, 7, , 8]);
629
+ return [4 /*yield*/, handleReadSubtreeIds(rowId, [])];
630
+ case 6:
631
+ allIds = _a.sent();
632
+ if (allIds.length > visibleIds.length) {
633
+ if (seq === deleteSeqRef.current)
634
+ setModalError("This folder holds items you do not have access to, so it cannot be deleted.");
635
+ return [2 /*return*/];
636
+ }
637
+ return [3 /*break*/, 8];
638
+ case 7:
639
+ err_7 = _a.sent();
640
+ if (seq === deleteSeqRef.current)
641
+ setModalError("Whether this folder holds items you cannot see could not be checked, so it cannot be deleted.");
642
+ return [2 /*return*/];
643
+ case 8:
644
+ if (seq !== deleteSeqRef.current)
645
+ return [2 /*return*/];
646
+ setDeleteIds(__spreadArray([rowId], visibleIds, true));
647
+ return [2 /*return*/];
392
648
  }
393
649
  });
394
650
  }); };
395
651
  var handleDelete = function () { return __awaiter(void 0, void 0, void 0, function () {
396
- var form, err_6;
652
+ var form, err_8;
397
653
  return __generator(this, function (_a) {
398
654
  switch (_a.label) {
399
655
  case 0:
@@ -403,19 +659,19 @@ var DriveView = function (props) {
403
659
  _a.label = 1;
404
660
  case 1:
405
661
  _a.trys.push([1, 5, , 6]);
406
- return [4 /*yield*/, props.writeQuery.call("batchDelete", { id: deleteIds })];
662
+ return [4 /*yield*/, writeQuery.call("batchDelete", { id: deleteIds })];
407
663
  case 2:
408
664
  _a.sent();
409
665
  form = new FormData();
410
- form.append("data[assetCollectionName]", storageCollection);
666
+ form.append("data[assetCollectionName]", props.collectionId);
411
667
  form.append("data[assetParentId]", JSON.stringify(deleteIds));
412
- if (props.bucketName)
413
- form.append("data[bucketName]", props.bucketName);
668
+ if (devSettings.bucketName)
669
+ form.append("data[bucketName]", devSettings.bucketName);
414
670
  return [4 /*yield*/, props.lib
415
671
  .axios({
416
672
  data: form,
417
673
  method: "post",
418
- url: "".concat(props.uploadEndpoint, "/batchDeleteDirectory"),
674
+ url: "".concat(uploadEndpoint, "/batchDeleteDirectory"),
419
675
  headers: { "Content-Type": "multipart/form-data" },
420
676
  })
421
677
  .catch(function () { })];
@@ -427,7 +683,7 @@ var DriveView = function (props) {
427
683
  _a.sent();
428
684
  return [3 /*break*/, 6];
429
685
  case 5:
430
- err_6 = _a.sent();
686
+ err_8 = _a.sent();
431
687
  setModalError("The item could not be deleted.");
432
688
  return [3 /*break*/, 6];
433
689
  case 6:
@@ -436,60 +692,79 @@ var DriveView = function (props) {
436
692
  }
437
693
  });
438
694
  }); };
695
+ var handleUploadToStorage = function (rowId, storedName, file) { return __awaiter(void 0, void 0, void 0, function () {
696
+ var dataForm, res;
697
+ var _a;
698
+ return __generator(this, function (_b) {
699
+ switch (_b.label) {
700
+ case 0:
701
+ dataForm = new FormData();
702
+ dataForm.append("file", file);
703
+ dataForm.append("data[assetCollectionName]", props.collectionId);
704
+ dataForm.append("data[assetParentId]", rowId);
705
+ dataForm.append("data[assetFileName]", storedName);
706
+ if (devSettings.bucketName)
707
+ dataForm.append("data[bucketName]", devSettings.bucketName);
708
+ return [4 /*yield*/, props.lib.axios({
709
+ method: "post",
710
+ url: "".concat(uploadEndpoint, "/upload"),
711
+ data: dataForm,
712
+ headers: { "Content-Type": "multipart/form-data" },
713
+ })];
714
+ case 1:
715
+ res = _b.sent();
716
+ return [2 /*return*/, (_a = res === null || res === void 0 ? void 0 : res.data) === null || _a === void 0 ? void 0 : _a.data];
717
+ }
718
+ });
719
+ }); };
439
720
  /**
440
721
  * INFO: one row per file, each uploaded into its own row's storage folder
441
722
  * first — a row is only created for a file that stored, and one failure does
442
723
  * not stop the rest.
443
724
  */
444
725
  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) {
726
+ var failed, _loop_2, _i, queued_1, eachFile;
727
+ return __generator(this, function (_a) {
728
+ switch (_a.label) {
449
729
  case 0:
450
730
  if (!queued.length)
451
731
  return [2 /*return*/];
452
732
  setWorking(true);
453
733
  setModalError("");
454
734
  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) {
735
+ _loop_2 = function (eachFile) {
736
+ var file, rowId, storedName, token, err_9;
737
+ var _b;
738
+ return __generator(this, function (_c) {
739
+ switch (_c.label) {
460
740
  case 0:
461
741
  setUploadStatus(function (current) {
462
742
  var _a;
463
743
  return (__assign(__assign({}, current), (_a = {}, _a[eachFile.token] = "uploading", _a)));
464
744
  });
465
- _f.label = 1;
745
+ _c.label = 1;
466
746
  case 1:
467
- _f.trys.push([1, 4, , 5]);
747
+ _c.trys.push([1, 4, , 5]);
468
748
  file = eachFile.fileName;
469
749
  rowId = (0, uuid_1.v4)();
470
750
  storedName = "".concat((0, uuid_1.v4)(), "-").concat(file.name);
471
- return [4 /*yield*/, props.handleUpload(props.uploadEndpoint, rowId, storageCollection, storedName, file, props.bucketName)];
751
+ return [4 /*yield*/, handleUploadToStorage(rowId, storedName, file)];
472
752
  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;
753
+ token = _c.sent();
475
754
  if (!token)
476
755
  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) : {})),
756
+ return [4 /*yield*/, writeQuery.call("create", {
757
+ 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
758
  })];
484
759
  case 3:
485
- _f.sent();
760
+ _c.sent();
486
761
  setUploadStatus(function (current) {
487
762
  var _a;
488
763
  return (__assign(__assign({}, current), (_a = {}, _a[eachFile.token] = "done", _a)));
489
764
  });
490
765
  return [3 /*break*/, 5];
491
766
  case 4:
492
- err_7 = _f.sent();
767
+ err_9 = _c.sent();
493
768
  failed += 1;
494
769
  setUploadStatus(function (current) {
495
770
  var _a;
@@ -501,20 +776,20 @@ var DriveView = function (props) {
501
776
  });
502
777
  };
503
778
  _i = 0, queued_1 = queued;
504
- _b.label = 1;
779
+ _a.label = 1;
505
780
  case 1:
506
781
  if (!(_i < queued_1.length)) return [3 /*break*/, 4];
507
782
  eachFile = queued_1[_i];
508
- return [5 /*yield**/, _loop_1(eachFile)];
783
+ return [5 /*yield**/, _loop_2(eachFile)];
509
784
  case 2:
510
- _b.sent();
511
- _b.label = 3;
785
+ _a.sent();
786
+ _a.label = 3;
512
787
  case 3:
513
788
  _i++;
514
789
  return [3 /*break*/, 1];
515
790
  case 4:
516
791
  setWorking(false);
517
- props.handleRefresh();
792
+ handleFetchRows();
518
793
  if (failed === 0)
519
794
  handleCloseUpload();
520
795
  else
@@ -564,10 +839,18 @@ var DriveView = function (props) {
564
839
  // while the upload dialog is open — it has its own drop zone
565
840
  var handleIsFileDrag = function (event) {
566
841
  var _a, _b;
567
- return props.canCreate &&
842
+ return canCreate &&
568
843
  !uploadOpen &&
569
844
  Array.from((_b = (_a = event.dataTransfer) === null || _a === void 0 ? void 0 : _a.types) !== null && _b !== void 0 ? _b : []).includes("Files");
570
845
  };
846
+ var handleFormatRowPath = function (row) {
847
+ var _a;
848
+ if (!searching)
849
+ return "";
850
+ var parentId = String((_a = row === null || row === void 0 ? void 0 : row[driveUtils_1.DRIVE_COLUMNS.parentId]) !== null && _a !== void 0 ? _a : "");
851
+ var rowPath = (0, driveUtils_1.handleFormatFolderPath)((0, driveUtils_1.handleBuildFolderPath)(folders, parentId));
852
+ return rowPath ? "In ".concat(rowPath) : "At the top level";
853
+ };
571
854
  var renderDropOverlay = function () {
572
855
  if (!dragging)
573
856
  return null;
@@ -578,12 +861,8 @@ var DriveView = function (props) {
578
861
  "Drop to upload to ",
579
862
  target)));
580
863
  };
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
864
+ var renderMenu = function (row) {
865
+ var items = __spreadArray(__spreadArray([], (canUpdate
587
866
  ? [
588
867
  { text: "Rename", onClick: function () { return handleOpenRename(row); } },
589
868
  {
@@ -594,102 +873,117 @@ var DriveView = function (props) {
594
873
  },
595
874
  },
596
875
  ]
597
- : []), true), (props.canDelete
876
+ : []), true), (canDelete
598
877
  ? [{ 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";
878
+ : []), true);
879
+ if (!items.length)
880
+ return null;
881
+ return (react_2.default.createElement(DriveItemMenu, { darkMode: dark, testId: "drive-menu-".concat((0, driveUtils_1.handleGetRowId)(row)), items: items }));
607
882
  };
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);
883
+ var renderIconLabel = function (label) { return (react_2.default.createElement("span", { className: "sr-only" }, label)); };
884
+ // INFO: the same row DataTemplate draws above a list — title on the left, a
885
+ // w-64 labelled search and icon actions on the right — so a drive page and a
886
+ // table page line up
887
+ 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" },
888
+ (props.title || props.description) && (react_2.default.createElement("div", { className: "flex flex-col" },
889
+ props.title && (react_2.default.createElement("div", { className: "flex items-center gap-2" },
890
+ react_2.default.createElement("p", { className: "text-xl font-medium ".concat(props.darkMode ? "text-white" : "") }, props.title))),
891
+ props.description && (react_2.default.createElement("span", { className: "text-sm text-gray-500" }, props.description)))),
892
+ react_2.default.createElement("div", { className: "hidden sm:block grow" }),
893
+ react_2.default.createElement("div", { className: "ml-auto flex flex-row items-end" },
894
+ react_2.default.createElement("div", { className: "flex-1 sm:flex-none sm:w-64 min-w-0 mr-3" },
895
+ 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 })),
896
+ react_2.default.createElement("div", { className: "flex flex-row items-end gap-x-2" },
897
+ react_2.default.createElement(iconButton_1.IconButton, { testId: "drive-layout", icon: layout === "grid" ? (react_2.default.createElement(react_2.default.Fragment, null,
898
+ react_2.default.createElement(HeroIcons.ListBulletIcon, { className: "h-7 w-7 text-primary-500" }),
899
+ renderIconLabel("List view"))) : (react_2.default.createElement(react_2.default.Fragment, null,
900
+ react_2.default.createElement(HeroIcons.Squares2X2Icon, { className: "h-7 w-7 text-primary-500" }),
901
+ renderIconLabel("Grid view"))), bgColor: props.darkMode ? "bg-white/10" : "bg-white", onClick: function () {
902
+ return handleChangeLayout(layout === "grid" ? "list" : "grid");
633
903
  } }),
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 })))))); };
904
+ canCreate && (react_2.default.createElement(react_2.default.Fragment, null,
905
+ react_2.default.createElement(iconButton_1.IconButton, { testId: "drive-new-folder", icon: react_2.default.createElement(react_2.default.Fragment, null,
906
+ react_2.default.createElement(HeroIcons.FolderPlusIcon, { className: "h-7 w-7 text-primary-500" }),
907
+ renderIconLabel("New folder")), bgColor: props.darkMode ? "bg-white/10" : "bg-white", onClick: function () {
908
+ setFolderName("");
909
+ setModalError("");
910
+ setNewFolderOpen(true);
911
+ } }),
912
+ react_2.default.createElement(iconButton_1.IconButton, { testId: "drive-upload-open", icon: react_2.default.createElement(react_2.default.Fragment, null,
913
+ react_2.default.createElement(HeroIcons.ArrowUpTrayIcon, { className: "h-7 w-7 text-white" }),
914
+ renderIconLabel("Upload")), background: "bg-primary-500 hover:bg-primary-600", onClick: handleOpenUpload }))))))); };
915
+ var renderPath = function () {
916
+ var currentClass = props.darkMode ? "text-white" : "text-gray-900";
917
+ 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" },
918
+ react_2.default.createElement("button", { type: "button", className: "font-medium ".concat(folderId && !searching
919
+ ? "text-primary-600 hover:underline"
920
+ : currentClass), onClick: function () {
921
+ setSearchText("");
922
+ setSearchQuery("");
923
+ handleChangeFolder("");
924
+ } }, "Drive"),
925
+ !searching &&
926
+ path.map(function (eachFolder, eachIndex) { return (react_2.default.createElement(react_2.default.Fragment, { key: eachFolder.id },
927
+ react_2.default.createElement(HeroIcons.ChevronRightIcon, { className: "h-4 w-4 shrink-0 text-gray-400" }),
928
+ react_2.default.createElement("button", { type: "button", className: "truncate font-medium ".concat(eachIndex === path.length - 1
929
+ ? currentClass
930
+ : "text-primary-600 hover:underline"), onClick: function () { return handleChangeFolder(eachFolder.id); } }, eachFolder.name))); }),
931
+ searching && (react_2.default.createElement("span", { className: "ml-2 text-gray-500" }, "\u00B7 Search results"))));
932
+ };
635
933
  var renderFolderIcon = function (sizeClass) { return (react_2.default.createElement(HeroIcons.FolderIcon, { className: "".concat(sizeClass, " shrink-0 text-primary-400") })); };
636
934
  // INFO: folders as compact rows above the file tiles, the way a file browser
637
935
  // lays them out — a folder has no picture, so a tile-sized card for it is
638
936
  // mostly empty space
639
937
  var renderGrid = function () {
640
938
  var folderRows = sortedRows.filter(function (eachRow) {
641
- return (0, driveUtils_1.handleIsDriveFolder)(eachRow, settings);
939
+ return (0, driveUtils_1.handleIsDriveFolder)(eachRow);
642
940
  });
643
- var fileRows = sortedRows.filter(function (eachRow) { return !(0, driveUtils_1.handleIsDriveFolder)(eachRow, settings); });
941
+ var fileRows = sortedRows.filter(function (eachRow) { return !(0, driveUtils_1.handleIsDriveFolder)(eachRow); });
644
942
  return (react_2.default.createElement("div", { className: "flex flex-col gap-y-4" },
645
943
  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
944
  var _a;
647
945
  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" },
946
+ 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
947
  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
948
  react_2.default.createElement("span", { className: "flex w-full min-w-0 flex-row items-center gap-x-2" },
651
949
  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)))),
950
+ 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 : ""))),
951
+ searching && (react_2.default.createElement("span", { className: "w-full truncate pl-7 text-xs text-gray-500" }, handleFormatRowPath(eachRow)))),
654
952
  renderMenu(eachRow)));
655
953
  }))),
656
954
  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
955
  var _a;
658
956
  var rowId = (0, driveUtils_1.handleGetRowId)(eachRow);
659
957
  var file = handleResolveFile(eachRow);
958
+ var menu = renderMenu(eachRow);
660
959
  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" },
960
+ 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
961
  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))));
962
+ 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 : "")))),
963
+ searching && (react_2.default.createElement("p", { className: "mt-1 truncate text-xs text-gray-500" }, handleFormatRowPath(eachRow))),
964
+ 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
965
  })))));
667
966
  };
668
967
  var renderList = function () { return (react_2.default.createElement(newList_1.NewList, { list: sortedRows.map(function (eachRow) {
669
968
  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;
969
+ var isFolder = (0, driveUtils_1.handleIsDriveFolder)(eachRow);
675
970
  var description = [
676
- rowPath,
677
- typeof size === "number" ? "".concat(Math.max(1, Math.round(size / 1024)), " KB") : "",
971
+ handleFormatRowPath(eachRow),
972
+ isFolder ? "" : (0, driveUtils_1.handleFormatFileSize)(eachRow === null || eachRow === void 0 ? void 0 : eachRow[driveUtils_1.DRIVE_COLUMNS.size]),
678
973
  ]
679
974
  .filter(function (eachPart) { return !!eachPart; })
680
975
  .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) });
976
+ var menu = renderMenu(eachRow);
977
+ 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
978
  }), hidePagination: true, onClickItem: function (item) {
685
979
  return handleOpenRow(sortedRows.find(function (eachRow) { return (0, driveUtils_1.handleGetRowId)(eachRow) === item.id; }));
686
980
  }, darkMode: props.darkMode })); };
687
981
  var renderContent = function () {
688
- if (props.loading)
982
+ if (loading)
689
983
  return (react_2.default.createElement("div", { className: "flex flex-col items-center py-10" },
690
984
  react_2.default.createElement(loading_1.Loading, null)));
691
985
  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") }));
986
+ 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
987
  return layout === "grid" ? renderGrid() : renderList();
694
988
  };
695
989
  var renderNameForm = function (value, onChange, onSubmit, submitText, placeholder) { return (
@@ -701,7 +995,7 @@ var DriveView = function (props) {
701
995
  event.stopPropagation();
702
996
  onSubmit();
703
997
  }, 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); } }),
998
+ 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
999
  modalError && react_2.default.createElement("p", { className: "text-sm text-red-600" }, modalError),
706
1000
  react_2.default.createElement("div", { className: "flex flex-row justify-end gap-x-3" },
707
1001
  react_2.default.createElement(button_1.Button, { text: "Cancel", type: "light", size: "small", onClick: handleCloseModals }),
@@ -711,14 +1005,17 @@ var DriveView = function (props) {
711
1005
  if (!moveRow)
712
1006
  return null;
713
1007
  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))
1008
+ var currentParent = String((_a = moveRow === null || moveRow === void 0 ? void 0 : moveRow[driveUtils_1.DRIVE_COLUMNS.parentId]) !== null && _a !== void 0 ? _a : "");
1009
+ var targets = (0, driveUtils_1.handleGetMoveTargets)(folders, rowId, (0, driveUtils_1.handleIsDriveFolder)(moveRow))
716
1010
  .map(function (eachFolder) { return ({
717
1011
  folder: eachFolder,
718
1012
  label: (0, driveUtils_1.handleFormatFolderPath)((0, driveUtils_1.handleBuildFolderPath)(folders, eachFolder.id)),
719
1013
  }); })
720
1014
  .sort(function (a, b) {
721
- return a.label.localeCompare(b.label, undefined, { numeric: true, sensitivity: "base" });
1015
+ return a.label.localeCompare(b.label, undefined, {
1016
+ numeric: true,
1017
+ sensitivity: "base",
1018
+ });
722
1019
  });
723
1020
  var options = __spreadArray([
724
1021
  { id: "", label: "Drive (top level)" }
@@ -729,7 +1026,7 @@ var DriveView = function (props) {
729
1026
  return (react_2.default.createElement("div", { className: "flex flex-col gap-y-3" },
730
1027
  modalError && react_2.default.createElement("p", { className: "text-sm text-red-600" }, modalError),
731
1028
  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 ({
1029
+ react_2.default.createElement(newList_1.NewList, { darkMode: dark, list: options.map(function (eachOption) { return ({
733
1030
  id: eachOption.id || "__root__",
734
1031
  title: eachOption.label,
735
1032
  icon: renderFolderIcon("h-5 w-5"),
@@ -741,14 +1038,16 @@ var DriveView = function (props) {
741
1038
  var _a, _b;
742
1039
  if (!deleteRow)
743
1040
  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 : "");
1041
+ var isFolder = (0, driveUtils_1.handleIsDriveFolder)(deleteRow);
1042
+ var name = String((_a = deleteRow === null || deleteRow === void 0 ? void 0 : deleteRow[driveUtils_1.DRIVE_COLUMNS.name]) !== null && _a !== void 0 ? _a : "");
746
1043
  var inside = ((_b = deleteIds === null || deleteIds === void 0 ? void 0 : deleteIds.length) !== null && _b !== void 0 ? _b : 1) - 1;
747
1044
  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
1045
+ react_2.default.createElement("p", { className: "text-sm ".concat(textBody) }, !isFolder
749
1046
  ? "Delete \"".concat(name, "\"? This cannot be undone.")
750
1047
  : deleteIds === null
751
- ? "Counting what is inside…"
1048
+ ? modalError
1049
+ ? "\"".concat(name, "\" was not deleted.")
1050
+ : "Counting what is inside…"
752
1051
  : inside > 0
753
1052
  ? "Delete \"".concat(name, "\" and the ").concat(inside, " item(s) inside it? This cannot be undone.")
754
1053
  : "Delete the empty folder \"".concat(name, "\"?")),
@@ -761,7 +1060,7 @@ var DriveView = function (props) {
761
1060
  var _a;
762
1061
  var queued = (_a = uploadFiles.upload) !== null && _a !== void 0 ? _a : [];
763
1062
  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) {
1063
+ 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
1064
  var value = _a.value, action = _a.action;
766
1065
  if (action === "upload")
767
1066
  setUploadFiles(function (current) {
@@ -777,7 +1076,7 @@ var DriveView = function (props) {
777
1076
  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
1077
  var _a, _b;
779
1078
  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 : ""),
1079
+ 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
1080
  react_2.default.createElement("span", { className: "grow" }),
782
1081
  react_2.default.createElement("span", { className: uploadStatus[eachFile.token] === "failed"
783
1082
  ? "text-red-600"
@@ -791,34 +1090,36 @@ var DriveView = function (props) {
791
1090
  }))),
792
1091
  modalError && react_2.default.createElement("p", { className: "text-sm text-red-600" }, modalError),
793
1092
  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 }),
1093
+ react_2.default.createElement(button_1.Button, { text: "Close", type: "light", size: "small", disable: working, onClick: handleCloseUpload }),
795
1094
  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
1095
  };
797
1096
  var renderPreviewContent = function () {
798
- var _a, _b;
1097
+ var _a, _b, _c;
799
1098
  var file = previewRow ? handleResolveFile(previewRow) : null;
800
1099
  if (!file)
801
1100
  return null;
802
1101
  var kind = (0, filePreview_1.handleResolveFileKind)(file);
1102
+ var details = [
1103
+ (0, driveUtils_1.handleFormatFileSize)(previewRow === null || previewRow === void 0 ? void 0 : previewRow[driveUtils_1.DRIVE_COLUMNS.size]),
1104
+ (_a = file.mimeType) !== null && _a !== void 0 ? _a : "",
1105
+ ]
1106
+ .filter(function (eachPart) { return !!eachPart; })
1107
+ .join(" · ");
803
1108
  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" })),
1109
+ 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" })),
1110
+ kind === "pdf" &&
1111
+ (previewSrc ? (react_2.default.createElement("iframe", { title: (_c = file.label) !== null && _c !== void 0 ? _c : "Preview", src: previewSrc, className: "h-[70vh] w-full" })) : (react_2.default.createElement("div", { className: "flex h-[70vh] w-full items-center justify-center" },
1112
+ react_2.default.createElement(loading_1.Loading, null)))),
808
1113
  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
- } }),
1114
+ kind !== "image" && kind !== "pdf" && kind !== "video" && (react_2.default.createElement("p", { className: "text-sm ".concat(textBody) }, "This file has no preview.")),
1115
+ react_2.default.createElement("div", { className: "flex flex-row items-center justify-end gap-x-3" },
1116
+ details && (react_2.default.createElement("p", { className: "mr-auto text-sm text-gray-500" }, details)),
816
1117
  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
1118
  };
818
1119
  return (react_2.default.createElement("div", {
819
1120
  // INFO: a minimum height so an empty or near-empty folder is still a
820
1121
  // 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) {
1122
+ className: "relative flex min-h-[240px] flex-col", "data-testid": "drive-view", onDragEnter: function (event) {
822
1123
  if (!handleIsFileDrag(event))
823
1124
  return;
824
1125
  event.preventDefault();
@@ -842,21 +1143,25 @@ var DriveView = function (props) {
842
1143
  event.preventDefault();
843
1144
  handleDropFiles(Array.from(event.dataTransfer.files));
844
1145
  } },
845
- renderToolbar(),
846
- renderContent(),
1146
+ renderHeader(),
1147
+ renderPath(),
1148
+ react_2.default.createElement("div", { className: "mt-2" }, renderContent()),
847
1149
  renderDropOverlay(),
848
- react_2.default.createElement(modal_1.Modal, { open: newFolderOpen, title: "New folder", onClose: handleCloseModals, renderContent: function () {
1150
+ react_2.default.createElement(modal_1.Modal, { darkMode: dark, open: newFolderOpen, title: "New folder", onClose: handleCloseModals, renderContent: function () {
849
1151
  return renderNameForm(folderName, setFolderName, handleCreateFolder, "Create", "Folder name");
850
1152
  } }),
851
- react_2.default.createElement(modal_1.Modal, { open: !!renameRow, title: "Rename", onClose: handleCloseModals, renderContent: function () {
1153
+ react_2.default.createElement(modal_1.Modal, { darkMode: dark, open: !!renameRow, title: "Rename", onClose: handleCloseModals, renderContent: function () {
852
1154
  return renderNameForm(renameValue, setRenameValue, handleRename, "Save", "Name");
853
1155
  } }),
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 })));
1156
+ 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 }),
1157
+ react_2.default.createElement(modal_1.Modal, { darkMode: dark, open: !!deleteRow, title: "Delete", onClose: handleCloseModals, renderContent: renderDeleteContent }),
1158
+ react_2.default.createElement(modal_1.Modal, { darkMode: dark, open: uploadOpen, title: "Upload files", maxWidth: "2xl",
1159
+ // INFO: no closing mid-upload — the rows still being written belong to
1160
+ // the folder the upload started in
1161
+ onClose: working ? undefined : handleCloseUpload, renderContent: renderUploadContent }),
1162
+ 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
1163
  };
859
- exports.DriveView = DriveView;
1164
+ exports.DriveTemplate = DriveTemplate;
860
1165
  /**
861
1166
  * The per-item action menu.
862
1167
  *
@@ -867,11 +1172,17 @@ exports.DriveView = DriveView;
867
1172
  var DriveItemMenu = function (props) {
868
1173
  var popUpRef = (0, react_2.useRef)(null);
869
1174
  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 () {
1175
+ 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
1176
+ ? "text-gray-300 hover:bg-white/10 hover:text-white"
1177
+ : "text-gray-500 hover:bg-gray-100 hover:text-gray-700") },
1178
+ 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"
1179
+ ? "text-red-600"
1180
+ : props.darkMode
1181
+ ? "text-white"
1182
+ : "text-gray-900"), onClick: function () {
872
1183
  var _a;
873
1184
  (_a = popUpRef.current) === null || _a === void 0 ? void 0 : _a.handleOnClose();
874
1185
  eachItem.onClick();
875
1186
  } }, eachItem.text)); })) })));
876
1187
  };
877
- //# sourceMappingURL=driveView.js.map
1188
+ //# sourceMappingURL=index.js.map