@putkoff/abstract-securefiles 0.0.24 → 0.0.27

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 (177) hide show
  1. package/css.d.ts +0 -0
  2. package/dist/index.cjs +3 -933
  3. package/dist/index.cjs.map +1 -1
  4. package/dist/index.css +1 -1
  5. package/dist/index.d.ts +1 -8
  6. package/dist/index.js +5 -884
  7. package/dist/index.js.map +1 -1
  8. package/dist/{App/SecureFiles.d.ts → secure_files.d.ts} +1 -1
  9. package/index.ts +0 -0
  10. package/package.json +4 -4
  11. package/postcss.config.cjs +0 -0
  12. package/rollup.config.mjs +0 -0
  13. package/src/index.ts +1 -8
  14. package/src/{App/SecureFiles.tsx → secure_files.tsx} +4 -3
  15. package/tailwind.config.cjs +0 -0
  16. package/tsconfig.json +0 -0
  17. package/dist/App/index.d.ts +0 -1
  18. package/dist/FileHandlers/FileRow/imports.d.ts +0 -2
  19. package/dist/FileHandlers/FileRow/index.d.ts +0 -1
  20. package/dist/FileHandlers/FileRow/src/index.d.ts +0 -1
  21. package/dist/FileHandlers/FileRow/src/row_utils.d.ts +0 -2
  22. package/dist/FileHandlers/FileTable/imports.d.ts +0 -4
  23. package/dist/FileHandlers/FileTable/index.d.ts +0 -1
  24. package/dist/FileHandlers/FileTable/src/index.d.ts +0 -1
  25. package/dist/FileHandlers/FileTable/src/table_utils.d.ts +0 -2
  26. package/dist/FileHandlers/ShareModal/imports.d.ts +0 -2
  27. package/dist/FileHandlers/ShareModal/index.d.ts +0 -1
  28. package/dist/FileHandlers/ShareModal/src/index.d.ts +0 -1
  29. package/dist/FileHandlers/ShareModal/src/share_utils.d.ts +0 -2
  30. package/dist/FileHandlers/Upload/imports.d.ts +0 -2
  31. package/dist/FileHandlers/Upload/index.d.ts +0 -1
  32. package/dist/FileHandlers/Upload/src/index.d.ts +0 -2
  33. package/dist/FileHandlers/Upload/src/upload_utils.d.ts +0 -2
  34. package/dist/FileHandlers/imports.d.ts +0 -4
  35. package/dist/FileHandlers/index.d.ts +0 -4
  36. package/dist/UI/index.d.ts +0 -19
  37. package/dist/file_utils/imports.d.ts +0 -2
  38. package/dist/file_utils/index.d.ts +0 -1
  39. package/dist/file_utils/src/delete_utils/imports.d.ts +0 -1
  40. package/dist/file_utils/src/delete_utils/index.d.ts +0 -1
  41. package/dist/file_utils/src/delete_utils/src/delete_row.d.ts +0 -2
  42. package/dist/file_utils/src/delete_utils/src/delete_select.d.ts +0 -1
  43. package/dist/file_utils/src/delete_utils/src/delete_selected.d.ts +0 -2
  44. package/dist/file_utils/src/delete_utils/src/index.d.ts +0 -3
  45. package/dist/file_utils/src/download_utils/imports.d.ts +0 -2
  46. package/dist/file_utils/src/download_utils/index.d.ts +0 -1
  47. package/dist/file_utils/src/download_utils/src/direct_download.d.ts +0 -3
  48. package/dist/file_utils/src/download_utils/src/download_file.d.ts +0 -2
  49. package/dist/file_utils/src/download_utils/src/download_select.d.ts +0 -2
  50. package/dist/file_utils/src/download_utils/src/index.d.ts +0 -5
  51. package/dist/file_utils/src/download_utils/src/target_downloads.d.ts +0 -4
  52. package/dist/file_utils/src/download_utils/src/trigger_download.d.ts +0 -2
  53. package/dist/file_utils/src/imports.d.ts +0 -2
  54. package/dist/file_utils/src/index.d.ts +0 -6
  55. package/dist/file_utils/src/list_utils/imports.d.ts +0 -2
  56. package/dist/file_utils/src/list_utils/index.d.ts +0 -1
  57. package/dist/file_utils/src/list_utils/src/index.d.ts +0 -1
  58. package/dist/file_utils/src/list_utils/src/list_utils.d.ts +0 -2
  59. package/dist/file_utils/src/select_utils/index.d.ts +0 -1
  60. package/dist/file_utils/src/select_utils/src/index.d.ts +0 -3
  61. package/dist/file_utils/src/select_utils/src/init_select_all.d.ts +0 -2
  62. package/dist/file_utils/src/select_utils/src/sync_header_checkbox.d.ts +0 -5
  63. package/dist/file_utils/src/select_utils/src/toggle_checkboxes.d.ts +0 -2
  64. package/dist/file_utils/src/share_utils/imports.d.ts +0 -2
  65. package/dist/file_utils/src/share_utils/index.d.ts +0 -1
  66. package/dist/file_utils/src/share_utils/src/index.d.ts +0 -1
  67. package/dist/file_utils/src/share_utils/src/share_utils.d.ts +0 -7
  68. package/dist/file_utils/src/upload_utils/imports.d.ts +0 -1
  69. package/dist/file_utils/src/upload_utils/index.d.ts +0 -1
  70. package/dist/file_utils/src/upload_utils/src/index.d.ts +0 -3
  71. package/dist/file_utils/src/upload_utils/src/patch_file_settings.d.ts +0 -2
  72. package/dist/file_utils/src/upload_utils/src/upload_file.d.ts +0 -2
  73. package/dist/file_utils/src/upload_utils/src/upload_files.d.ts +0 -5
  74. package/dist/hooks/imports.d.ts +0 -3
  75. package/dist/hooks/index.d.ts +0 -1
  76. package/dist/hooks/useFiles/imports.d.ts +0 -2
  77. package/dist/hooks/useFiles/index.d.ts +0 -1
  78. package/dist/hooks/useFiles/src/deleteFile.d.ts +0 -6
  79. package/dist/hooks/useFiles/src/index.d.ts +0 -6
  80. package/dist/hooks/useFiles/src/load.d.ts +0 -6
  81. package/dist/hooks/useFiles/src/patchShareSettings.d.ts +0 -7
  82. package/dist/hooks/useFiles/src/toggleShare.d.ts +0 -6
  83. package/dist/hooks/useFiles/src/useFiles.d.ts +0 -13
  84. package/dist/hooks/useFiles/src/useSaveShareSettings.d.ts +0 -3
  85. package/dist/imports.d.ts +0 -5
  86. package/dist/styles/index.d.ts +0 -1
  87. package/dist/types/index.d.ts +0 -1
  88. package/dist/types/src/index.d.ts +0 -1
  89. package/dist/types/src/types.d.ts +0 -75
  90. package/src/App/index.ts +0 -1
  91. package/src/FileHandlers/FileRow/imports.ts +0 -2
  92. package/src/FileHandlers/FileRow/index.ts +0 -1
  93. package/src/FileHandlers/FileRow/src/index.ts +0 -1
  94. package/src/FileHandlers/FileRow/src/row_utils.tsx +0 -143
  95. package/src/FileHandlers/FileTable/imports.ts +0 -16
  96. package/src/FileHandlers/FileTable/index.ts +0 -1
  97. package/src/FileHandlers/FileTable/src/index.ts +0 -1
  98. package/src/FileHandlers/FileTable/src/table_utils.tsx +0 -251
  99. package/src/FileHandlers/ShareModal/imports.ts +0 -2
  100. package/src/FileHandlers/ShareModal/index.ts +0 -1
  101. package/src/FileHandlers/ShareModal/src/index.ts +0 -1
  102. package/src/FileHandlers/ShareModal/src/share_utils.tsx +0 -158
  103. package/src/FileHandlers/Upload/imports.ts +0 -3
  104. package/src/FileHandlers/Upload/index.ts +0 -1
  105. package/src/FileHandlers/Upload/src/index.ts +0 -2
  106. package/src/FileHandlers/Upload/src/upload_utils.tsx +0 -267
  107. package/src/FileHandlers/imports.ts +0 -20
  108. package/src/FileHandlers/index.ts +0 -4
  109. package/src/UI/index.tsx +0 -113
  110. package/src/custom.d.ts +0 -7
  111. package/src/file_utils/imports.ts +0 -2
  112. package/src/file_utils/index.ts +0 -1
  113. package/src/file_utils/src/delete_utils/imports.ts +0 -1
  114. package/src/file_utils/src/delete_utils/index.ts +0 -1
  115. package/src/file_utils/src/delete_utils/src/delete_row.ts +0 -11
  116. package/src/file_utils/src/delete_utils/src/delete_select.ts +0 -9
  117. package/src/file_utils/src/delete_utils/src/delete_selected.ts +0 -15
  118. package/src/file_utils/src/delete_utils/src/index.ts +0 -3
  119. package/src/file_utils/src/download_utils/imports.ts +0 -3
  120. package/src/file_utils/src/download_utils/index.ts +0 -1
  121. package/src/file_utils/src/download_utils/src/direct_download.ts +0 -22
  122. package/src/file_utils/src/download_utils/src/download_file.ts +0 -24
  123. package/src/file_utils/src/download_utils/src/download_select.ts +0 -20
  124. package/src/file_utils/src/download_utils/src/index.ts +0 -5
  125. package/src/file_utils/src/download_utils/src/target_downloads.ts +0 -21
  126. package/src/file_utils/src/download_utils/src/trigger_download.ts +0 -14
  127. package/src/file_utils/src/imports.ts +0 -3
  128. package/src/file_utils/src/index.ts +0 -6
  129. package/src/file_utils/src/list_utils/imports.ts +0 -2
  130. package/src/file_utils/src/list_utils/index.ts +0 -1
  131. package/src/file_utils/src/list_utils/src/index.ts +0 -1
  132. package/src/file_utils/src/list_utils/src/list_utils.ts +0 -24
  133. package/src/file_utils/src/select_utils/index.ts +0 -1
  134. package/src/file_utils/src/select_utils/src/index.ts +0 -3
  135. package/src/file_utils/src/select_utils/src/init_select_all.ts +0 -37
  136. package/src/file_utils/src/select_utils/src/sync_header_checkbox.ts +0 -18
  137. package/src/file_utils/src/select_utils/src/toggle_checkboxes.ts +0 -11
  138. package/src/file_utils/src/share_utils/imports.ts +0 -2
  139. package/src/file_utils/src/share_utils/index.ts +0 -1
  140. package/src/file_utils/src/share_utils/src/index.ts +0 -1
  141. package/src/file_utils/src/share_utils/src/share_utils.ts +0 -29
  142. package/src/file_utils/src/upload_utils/imports.ts +0 -1
  143. package/src/file_utils/src/upload_utils/index.ts +0 -1
  144. package/src/file_utils/src/upload_utils/src/index.ts +0 -3
  145. package/src/file_utils/src/upload_utils/src/patch_file_settings.ts +0 -18
  146. package/src/file_utils/src/upload_utils/src/upload_file.ts +0 -28
  147. package/src/file_utils/src/upload_utils/src/upload_files.ts +0 -34
  148. package/src/global.d.ts +0 -1
  149. package/src/hooks/imports.ts +0 -3
  150. package/src/hooks/index.ts +0 -1
  151. package/src/hooks/useFiles/imports.ts +0 -3
  152. package/src/hooks/useFiles/index.ts +0 -1
  153. package/src/hooks/useFiles/src/deleteFile.ts +0 -22
  154. package/src/hooks/useFiles/src/index.ts +0 -6
  155. package/src/hooks/useFiles/src/load.ts +0 -52
  156. package/src/hooks/useFiles/src/logs/routes_bp.log +0 -0
  157. package/src/hooks/useFiles/src/patchShareSettings.ts +0 -37
  158. package/src/hooks/useFiles/src/toggleShare.ts +0 -28
  159. package/src/hooks/useFiles/src/useFiles.ts +0 -35
  160. package/src/hooks/useFiles/src/useSaveShareSettings.ts +0 -25
  161. package/src/imports.ts +0 -20
  162. package/src/styles/SecureFiles/logs/clipit_logs.log +0 -0
  163. package/src/styles/SecureFiles/logs/routes_bp.log +0 -0
  164. package/src/styles/SecureFilesStyles.module.css +0 -287
  165. package/src/styles/index.ts +0 -1
  166. package/src/types/index.ts +0 -1
  167. package/src/types/src/index.ts +0 -1
  168. package/src/types/src/types.ts +0 -88
  169. package/test/package-lock.json +0 -979
  170. package/test/package.json +0 -23
  171. package/test/test_entry.ts +0 -8
  172. package/test/tsconfig.json +0 -12
  173. /package/{src/hooks/useFiles/src/logs/clipit_logs.log → package.tmp.json} +0 -0
  174. /package/src/styles/{SecureFiles/App.css → App.css} +0 -0
  175. /package/src/styles/{SecureFiles/index.css → index.css} +0 -0
  176. /package/src/styles/{SecureFiles/tailwind.css → tailwind.css} +0 -0
  177. /package/src/styles/{SecureFiles/window-manager.css → window-manager.css} +0 -0
package/dist/index.cjs CHANGED
@@ -1,905 +1,8 @@
1
1
  'use strict';
2
2
 
3
3
  var jsxRuntime = require('react/jsx-runtime');
4
- var reactDom = require('react-dom');
4
+ var abstractFiles = require('@putkoff/abstract-files');
5
5
  var abstractUtilities = require('@putkoff/abstract-utilities');
6
- var abstractApis = require('@putkoff/abstract-apis');
7
- var React = require('react');
8
-
9
- function _interopNamespaceDefault(e) {
10
- var n = Object.create(null);
11
- if (e) {
12
- Object.keys(e).forEach(function (k) {
13
- if (k !== 'default') {
14
- var d = Object.getOwnPropertyDescriptor(e, k);
15
- Object.defineProperty(n, k, d.get ? d : {
16
- enumerable: true,
17
- get: function () { return e[k]; }
18
- });
19
- }
20
- });
21
- }
22
- n.default = e;
23
- return Object.freeze(n);
24
- }
25
-
26
- var React__namespace = /*#__PURE__*/_interopNamespaceDefault(React);
27
-
28
- function ClipboardIcon({
29
- title,
30
- titleId,
31
- ...props
32
- }, svgRef) {
33
- return /*#__PURE__*/React__namespace.createElement("svg", Object.assign({
34
- xmlns: "http://www.w3.org/2000/svg",
35
- fill: "none",
36
- viewBox: "0 0 24 24",
37
- strokeWidth: 1.5,
38
- stroke: "currentColor",
39
- "aria-hidden": "true",
40
- "data-slot": "icon",
41
- ref: svgRef,
42
- "aria-labelledby": titleId
43
- }, props), title ? /*#__PURE__*/React__namespace.createElement("title", {
44
- id: titleId
45
- }, title) : null, /*#__PURE__*/React__namespace.createElement("path", {
46
- strokeLinecap: "round",
47
- strokeLinejoin: "round",
48
- d: "M15.666 3.888A2.25 2.25 0 0 0 13.5 2.25h-3c-1.03 0-1.9.693-2.166 1.638m7.332 0c.055.194.084.4.084.612v0a.75.75 0 0 1-.75.75H9a.75.75 0 0 1-.75-.75v0c0-.212.03-.418.084-.612m7.332 0c.646.049 1.288.11 1.927.184 1.1.128 1.907 1.077 1.907 2.185V19.5a2.25 2.25 0 0 1-2.25 2.25H6.75A2.25 2.25 0 0 1 4.5 19.5V6.257c0-1.108.806-2.057 1.907-2.185a48.208 48.208 0 0 1 1.927-.184"
49
- }));
50
- }
51
- const ForwardRef$1 = /*#__PURE__*/ React__namespace.forwardRef(ClipboardIcon);
52
-
53
- function CloudArrowUpIcon({
54
- title,
55
- titleId,
56
- ...props
57
- }, svgRef) {
58
- return /*#__PURE__*/React__namespace.createElement("svg", Object.assign({
59
- xmlns: "http://www.w3.org/2000/svg",
60
- fill: "none",
61
- viewBox: "0 0 24 24",
62
- strokeWidth: 1.5,
63
- stroke: "currentColor",
64
- "aria-hidden": "true",
65
- "data-slot": "icon",
66
- ref: svgRef,
67
- "aria-labelledby": titleId
68
- }, props), title ? /*#__PURE__*/React__namespace.createElement("title", {
69
- id: titleId
70
- }, title) : null, /*#__PURE__*/React__namespace.createElement("path", {
71
- strokeLinecap: "round",
72
- strokeLinejoin: "round",
73
- d: "M12 16.5V9.75m0 0 3 3m-3-3-3 3M6.75 19.5a4.5 4.5 0 0 1-1.41-8.775 5.25 5.25 0 0 1 10.233-2.33 3 3 0 0 1 3.758 3.848A3.752 3.752 0 0 1 18 19.5H6.75Z"
74
- }));
75
- }
76
- const ForwardRef = /*#__PURE__*/ React__namespace.forwardRef(CloudArrowUpIcon);
77
-
78
- /** Helper function to trigger file download */
79
- function triggerDownload(blob, filename) {
80
- const url = URL.createObjectURL(blob);
81
- const a = Object.assign(document.createElement("a"), {
82
- href: url,
83
- download: filename,
84
- });
85
- document.body.appendChild(a);
86
- a.click();
87
- a.remove();
88
- URL.revokeObjectURL(url);
89
- console.log(`✅ Successfully downloaded: ${filename}`);
90
- }
91
-
92
- /** Non-React “direct download” used by some old links (kept for parity) */
93
- async function directDownload(item) {
94
- const token = localStorage.getItem("token");
95
- if (!token)
96
- throw new Error("No auth token found");
97
- const res = await fetch("https://abstractendeavors.com/api/secure-files/secure-download/", {
98
- method: "POST",
99
- headers: {
100
- Authorization: `Bearer ${token}`,
101
- "Content-Type": "application/json",
102
- Accept: "application/octet-stream",
103
- },
104
- body: JSON.stringify(item),
105
- });
106
- if (!res.ok)
107
- throw new Error(`Download failed: ${res.status}`);
108
- const blob = await res.blob();
109
- triggerDownload(blob, item.filename);
110
- }
111
-
112
- async function downloadFile(item) {
113
- const token = localStorage.getItem("token");
114
- if (!token)
115
- throw new Error("No auth token found");
116
- const res = await fetch("https://abstractendeavors.com/api/secure-files/secure-download/", {
117
- method: "POST",
118
- headers: {
119
- Authorization: `Bearer ${token}`,
120
- "Content-Type": "application/json",
121
- // Don’t over-constrain Accept; let server pick proper content-type
122
- // Accept: "application/octet-stream",
123
- },
124
- body: JSON.stringify(item),
125
- });
126
- if (!res.ok)
127
- throw new Error(`Download failed: ${res.status}`);
128
- // Stream-friendly: if your triggerDownload revokes URL immediately, wait a tick
129
- const blob = await res.blob();
130
- const name = item.filename || "download";
131
- triggerDownload(blob, name); // ensure this waits ~0-100ms before URL.revokeObjectURL
132
- }
133
-
134
- async function downloadSelect(item) {
135
- const token = abstractUtilities.requireToken();
136
- const body = item;
137
- const headers = {
138
- Authorization: `Bearer ${token}`,
139
- Accept: "application/octet-stream",
140
- "Content-Type": "application/json"
141
- };
142
- const blob = await abstractApis.secureFetchIt("download", body, "POST", headers, true, true);
143
- if (!blob || blob.size === 0) {
144
- throw new Error(`Received empty file data for “${item.filename}”`);
145
- }
146
- triggerDownload(blob, item.filename);
147
- }
148
-
149
- /** Given a checkbox inside a table-row → download that row’s file. */
150
- async function downloadRow(cb) {
151
- const row = cb.closest("tr");
152
- const filepath = cb.value;
153
- const filename = row?.children[1]?.textContent?.trim() || "downloaded_file";
154
- if (filepath)
155
- await downloadSelect({ filepath, filename });
156
- }
157
- /** Bulk download: pass a NodeList or array of checkboxes. */
158
- async function downloadSelected(checkboxes) {
159
- const list = Array.from(checkboxes);
160
- if (!list.length) {
161
- alert("No files selected.");
162
- return;
163
- }
164
- for (const cb of list) {
165
- await downloadRow(cb);
166
- }
167
- }
168
-
169
- /**
170
- * Classic “select all rows” logic (still handy in non-React views
171
- * such as the admin dashboard).
172
- */
173
- function syncHeaderCheckbox() {
174
- const all = document.querySelectorAll(".file-checkbox");
175
- const checked = document.querySelectorAll(".file-checkbox:checked");
176
- const header = document.getElementById("select-all-checkbox");
177
- header.indeterminate =
178
- checked.length > 0 && checked.length < all.length;
179
- header.checked = checked.length === all.length;
180
- }
181
-
182
- /** Wire up header + row checkboxes once the table is in the DOM. */
183
- function initSelectAll() {
184
- const header = document.getElementById("select-all-checkbox");
185
- const btn = document.getElementById("select-all");
186
- // 1) header → toggle all
187
- header.addEventListener("change", (e) => {
188
- const on = e.target.checked;
189
- document
190
- .querySelectorAll(".file-checkbox")
191
- .forEach((cb) => (cb.checked = on));
192
- });
193
- // 2) button → toggle all
194
- btn.addEventListener("click", () => {
195
- const cbs = Array.from(document.querySelectorAll(".file-checkbox"));
196
- if (!cbs.length) {
197
- alert("No files to select.");
198
- return;
199
- }
200
- const anyUnchecked = cbs.some((cb) => !cb.checked);
201
- cbs.forEach((cb) => (cb.checked = anyUnchecked));
202
- syncHeaderCheckbox();
203
- });
204
- // 3) row boxes → keep header state
205
- document
206
- .querySelectorAll(".file-checkbox")
207
- .forEach((cb) => cb.addEventListener("change", syncHeaderCheckbox));
208
- syncHeaderCheckbox();
209
- }
210
-
211
- /** Utility for other screens: toggle a list of checkboxes on/off. */
212
- function toggleCheckboxes(cbs) {
213
- if (!cbs.length) {
214
- alert("No files to select.");
215
- return;
216
- }
217
- const anyUnchecked = cbs.some((cb) => !cb.checked);
218
- cbs.forEach((cb) => (cb.checked = anyUnchecked));
219
- syncHeaderCheckbox();
220
- }
221
-
222
- /** Generic PATCH helper kept for other admin forms */
223
- async function patchFileSetting(fileId, field, value) {
224
- const res = await fetch(`/files/${fileId}/settings/${field}`, {
225
- method: "PATCH",
226
- headers: { "Content-Type": "application/json" },
227
- body: JSON.stringify({ value }),
228
- });
229
- if (!res.ok) {
230
- const err = await res.json();
231
- throw new Error(err.error || res.statusText);
232
- }
233
- return (await res.json());
234
- }
235
-
236
- /** Classic file-upload for the fallback HTML page. */
237
- async function uploadFile(file, subdir) {
238
- const fileInput = file || document.getElementById("file-browser");
239
- const subdirInput = subdir || document.getElementById("subdir");
240
- const banner = document.getElementById("banner");
241
- if (!fileInput.files?.length) {
242
- alert("No file selected.");
243
- return;
244
- }
245
- const formData = new FormData();
246
- formData.append("file", fileInput.files[0]);
247
- formData.append("subdir", subdirInput.value.trim());
248
- try {
249
- const json = await abstractApis.secureFetchIt("upload", formData, "POST", null, false, false);
250
- banner.textContent = `Uploaded: ${json.filepath}`;
251
- fileInput.value = "";
252
- subdirInput.value = "";
253
- window.location.reload(); // no-op in React view
254
- }
255
- catch (err) {
256
- console.error("Upload failed", err);
257
- }
258
- }
259
-
260
- async function uploadFiles(file, subdir = '') {
261
- if (!(file instanceof File)) {
262
- throw new Error('Invalid file: Must be a File object.');
263
- }
264
- if (typeof subdir !== 'string') {
265
- throw new Error('Invalid subdir: Must be a string.');
266
- }
267
- const formData = new FormData();
268
- formData.append('file', file);
269
- formData.append('subdir', subdir.trim());
270
- try {
271
- const json = await abstractApis.secureFetchIt('upload', formData, 'POST', null, false, false);
272
- if (!json || typeof json !== 'object') {
273
- throw new Error('Invalid response from server.');
274
- }
275
- if (!('filepath' in json) || typeof json.filepath !== 'string' || !json.filepath) {
276
- throw new Error('Server response missing or invalid filepath.');
277
- }
278
- return { filepath: json.filepath };
279
- }
280
- catch (err) {
281
- throw new Error(`Upload failed: ${err?.message ?? 'Unknown error'}`);
282
- }
283
- }
284
-
285
- async function removeFile(item) {
286
- const resp = await abstractApis.secureFetchIt("remove", item, "POST", null, false, false, true);
287
- if (!resp || resp === false || resp?.result === false) {
288
- throw new Error(`could not delete data: “${item.filename}”`);
289
- }
290
- }
291
-
292
- /** Bulk download: pass a NodeList or array of checkboxes. */
293
- async function removeSelected(checkboxes) {
294
- const list = Array.from(checkboxes);
295
- if (!list.length) {
296
- alert("No files selected.");
297
- return;
298
- }
299
- for (const cb of list) {
300
- alert(cb);
301
- await removeFile(cb);
302
- }
303
- }
304
-
305
- /** Given a checkbox inside a table-row → download that row’s file. */
306
- async function removeRow(cb) {
307
- const row = cb.closest("tr");
308
- abstractUtilities.alertit(row);
309
- if (row && row?.id)
310
- await removeFile(row);
311
- }
312
-
313
- /** GET /secure-list */
314
- async function listFiles(setFiles = null) {
315
- /** ← your request already returns the raw array */
316
- const apiRows = await abstractApis.secureFetchIt("/list", null, "GET", null, false, true);
317
- /** map API → UI ------------------------------------------------ */
318
- const rows = apiRows.map((r) => ({
319
- id: r.id,
320
- filename: r.filename,
321
- uploader_id: r.uploader_id,
322
- filepath: r.filepath,
323
- created_at: r.created_at,
324
- download_count: r.download_count,
325
- download_limit: r.download_limit,
326
- shareable: Boolean(r.shareable),
327
- needsPassword: !!r.share_password,
328
- share_password: r.share_password,
329
- password_str: r.password_str,
330
- }));
331
- setFiles(rows);
332
- }
333
-
334
- function getToggleShare(file) {
335
- if (file == true || file?.shareable == true) {
336
- return false;
337
- }
338
- return false;
339
- }
340
- function updateFileShare(file) {
341
- let updated = file;
342
- const boolIt = getToggleShare(updated);
343
- updated = { ...file, shareable: boolIt };
344
- return updated;
345
- }
346
- /**
347
- * Custom hook to toggle share settings on a file item
348
- */
349
- async function toggleShare(file) {
350
- const updated = updateFileShare(file);
351
- try {
352
- const resp = await abstractApis.fetchSharePatch(updated);
353
- }
354
- catch (err) {
355
- console.error('Failed to toggle share settings', err);
356
- // Optionally revert UI state or show an error message
357
- }
358
- }
359
-
360
- /**
361
- * Custom hook that returns a memoized loadFiles callback
362
- */
363
- const useLoadFiles = (setLoading, setError, setFiles) => {
364
- return abstractUtilities.useCallback(async () => {
365
- setLoading(true);
366
- setError(null);
367
- try {
368
- /** ← your request already returns the raw array */
369
- let apiRows = await abstractApis.secureFetchIt("list", null, "GET");
370
- console.log(`all apiRows == ${JSON.stringify(apiRows)}`);
371
- if (apiRows?.result) {
372
- apiRows = apiRows.result;
373
- }
374
- apiRows = apiRows;
375
- /** map API → UI ------------------------------------------------ */
376
- const rows = apiRows.map((r) => ({
377
- id: r.id,
378
- filename: r.filename,
379
- uploader_id: r.uploader_id,
380
- filepath: r.filepath,
381
- created_at: r.created_at,
382
- download_count: r.download_count,
383
- download_limit: r.download_limit,
384
- shareable: Boolean(r.shareable),
385
- needsPassword: !!r.share_password,
386
- share_password: r.share_password,
387
- password_str: r.password_str,
388
- }));
389
- setFiles(rows);
390
- }
391
- catch (err) {
392
- setError(err instanceof Error ? err
393
- : new Error("Unknown error loading files"));
394
- }
395
- finally {
396
- setLoading(false);
397
- }
398
- }, [setLoading, setError, setFiles]);
399
- };
400
-
401
- /**
402
- * Custom hook to toggle share settings on a file item
403
- */
404
- const useToggleShare = (setFiles) => {
405
- return abstractUtilities.useCallback(async (file) => {
406
- const updated = { ...file, shareable: !file.shareable };
407
- if (!updated.shareable) {
408
- updated.needsPassword = false;
409
- }
410
- try {
411
- await abstractApis.fetchSharePatch(updated);
412
- setFiles((curr) => curr.map((f) => (f.id === file.id ? updated : f)));
413
- }
414
- catch (err) {
415
- console.error('Failed to toggle share settings', err);
416
- // Optionally revert UI state or show an error message
417
- }
418
- }, [setFiles]);
419
- };
420
-
421
- /**
422
- * Custom hook to toggle share settings on a file item
423
- */
424
- const usePatchShareSettings = (setFiles) => {
425
- return abstractUtilities.useCallback(async (file) => {
426
- const body = {
427
- id: file.id, // ← always send id
428
- };
429
- // now send whichever settings changed
430
- body.shareable = file.shareable;
431
- body.share_password = file.share_password ?? null;
432
- body.password_str = file.password_str ?? null;
433
- body.download_limit = file.download_limit ?? null;
434
- console.log(`file contents == ${JSON.stringify(body)}`);
435
- try {
436
- await abstractApis.fetchSharePatch(body);
437
- setFiles((curr) => curr.map((f) => (f.id === body.id ? body : f)));
438
- }
439
- catch (err) {
440
- console.error('Failed to toggle share settings', err);
441
- // Optionally revert UI state or show an error message
442
- }
443
- }, [setFiles]);
444
- };
445
-
446
- const useSaveShareSettings = (setFiles) => {
447
- return abstractUtilities.useCallback(async (updated) => {
448
- await abstractApis.fetchSharePatch({
449
- id: updated.id,
450
- shareable: updated.shareable,
451
- share_password: updated.share_password,
452
- password_str: updated.password_str,
453
- download_limit: updated.download_limit,
454
- });
455
- // optimistic UI update – identical to toggleShare
456
- setFiles(curr => curr.map(f => (f.id === updated.id ? updated : f)));
457
- }, [setFiles]);
458
- };
459
-
460
- /**
461
- * Custom hook to toggle share settings on a file item
462
- */
463
- const useDeleteFile = (setFiles) => {
464
- return abstractUtilities.useCallback(async (file) => {
465
- try {
466
- await removeFile(file);
467
- window.location.reload();
468
- }
469
- catch (err) {
470
- console.error('Failed to toggle share settings', err);
471
- // Optionally revert UI state or show an error message
472
- }
473
- }, [setFiles]);
474
- };
475
-
476
- function useFiles() {
477
- const [files, setFiles] = React.useState([]);
478
- const [loading, setLoading] = React.useState(false);
479
- const [error, setError] = React.useState(null);
480
- const loadCallback = useLoadFiles(setLoading, setError, setFiles);
481
- const toggleShareCallback = useToggleShare(setFiles);
482
- const patchShareSettingsCallback = useSaveShareSettings(setFiles);
483
- const deleteFileCallback = useDeleteFile(setFiles);
484
- React.useEffect(() => {
485
- loadCallback();
486
- }, [loadCallback]);
487
- return {
488
- files,
489
- loading,
490
- error,
491
- load: loadCallback,
492
- toggleShare: toggleShareCallback,
493
- patchShareSettings: patchShareSettingsCallback,
494
- deleteFile: deleteFileCallback,
495
- setFiles,
496
- setLoading,
497
- setError,
498
- };
499
- }
500
-
501
- function FileRow({ file, onDownload, onSettings, onToggleShare, onDelete, }) {
502
- const isOwner = file.uploader_id?.toLowerCase() === (abstractUtilities.currentUsername() ?? '').toLowerCase();
503
- const downloadAllowed = isOwner || file.shareable;
504
- const [isDownloading, setIsDownloading] = abstractUtilities.useState(false);
505
- const [isTogglingShare, setIsTogglingShare] = abstractUtilities.useState(false);
506
- const [isDeleting, setIsDeleting] = abstractUtilities.useState(false);
507
- // Mimic WindowManagerApp's unsaved heuristic
508
- const looksUnsaved = file.filename.toLowerCase().startsWith('untitled') ||
509
- file.filename.includes('*');
510
- const handleDownload = async () => {
511
- setIsDownloading(true);
512
- try {
513
- await onDownload();
514
- }
515
- finally {
516
- setIsDownloading(false);
517
- }
518
- };
519
- const handleToggleShare = async () => {
520
- setIsTogglingShare(true);
521
- try {
522
- await onToggleShare();
523
- }
524
- finally {
525
- setIsTogglingShare(false);
526
- }
527
- };
528
- const handleDeleteFile = async () => {
529
- setIsDeleting(true);
530
- try {
531
- await onDelete();
532
- }
533
- finally {
534
- setIsDeleting(false);
535
- }
536
- };
537
- return (jsxRuntime.jsxs("tr", { className: "wm-row transition-colors duration-75", children: [jsxRuntime.jsx("td", { className: "wm-cell w-12 align-middle", children: jsxRuntime.jsx("input", { type: "checkbox", className: "wm-checkbox", "aria-label": `Select ${file.filename}` }) }), jsxRuntime.jsx("td", { className: `wm-cell ${looksUnsaved ? 'unsaved' : ''}`, children: file.filename }), jsxRuntime.jsx("td", { className: "wm-cell w-12 align-middle", children: downloadAllowed ? (jsxRuntime.jsx(abstractUtilities.Button, { color: "blue", variant: "secondary", onClick: handleDownload, disabled: isDownloading, "aria-label": `Download ${file.filename}`, className: "wm-btn-link", children: isDownloading ? 'Downloading...' : 'Download' })) : (jsxRuntime.jsx("span", { className: "text-gray-500 text-lg", "aria-label": "Download restricted", children: "\uD83D\uDD12" })) }), jsxRuntime.jsx("td", { className: "wm-cell w-12 align-middle", children: isOwner && (jsxRuntime.jsx(abstractUtilities.Button, { color: "gray", variant: "icon", onClick: () => {
538
- console.log('⚙︎ clicked', file.id);
539
- onSettings();
540
- }, "aria-label": "Open share settings", className: "wm-btn-icon text-lg", children: "\u2699\uFE0F" })) }), jsxRuntime.jsx("td", { className: "wm-cell w-12 align-middle", children: isOwner && (jsxRuntime.jsx(abstractUtilities.Button, { color: file.shareable ? 'blue' : 'gray', variant: file.shareable ? 'primary' : 'secondary', onClick: handleToggleShare, disabled: isTogglingShare, className: "btn", children: isTogglingShare
541
- ? 'Toggling...'
542
- : file.shareable
543
- ? 'Sharing'
544
- : 'Share' })) }), jsxRuntime.jsx("td", { className: "wm-cell w-12 align-middle", children: isOwner && (jsxRuntime.jsx(abstractUtilities.Button, { color: "gray", variant: "icon", onClick: handleDeleteFile, disabled: isDeleting, className: "wm-btn-icon text-lg", children: "\uD83D\uDDD1" })) }), jsxRuntime.jsxs("td", { className: "wm-cell", children: [file.download_count, " / ", file.download_limit ?? "∞"] })] }));
545
- }
546
-
547
- function ShareModal({ file, onClose, onSave }) {
548
- const [fileId, setFileId] = abstractUtilities.useState("");
549
- const [enabled, setEnabled] = abstractUtilities.useState(false);
550
- const [password, setPassword] = abstractUtilities.useState(undefined);
551
- const [strPassword, setStrPassword] = abstractUtilities.useState("");
552
- const [download_limit, setDownloadLimit] = abstractUtilities.useState(0);
553
- const [error, setError] = abstractUtilities.useState("");
554
- const [busy, setBusy] = abstractUtilities.useState(false);
555
- const modalRef = abstractUtilities.useRef(null);
556
- const shareLink = file && enabled
557
- ? `https://abstractendeavors.com/api/secure-files/secure-download/${file.id}`
558
- : "";
559
- abstractUtilities.useEffect(() => {
560
- if (!file)
561
- return;
562
- setFileId(file.id);
563
- setEnabled(file.shareable);
564
- // load both values from the `file` prop
565
- setPassword(file.share_password);
566
- setStrPassword(file.password_str ?? "");
567
- setDownloadLimit(file.download_limit ?? 0);
568
- setError("");
569
- modalRef.current?.focus();
570
- }, [file]);
571
- abstractUtilities.useEffect(() => {
572
- const h = (e) => e.key === "Escape" && onClose();
573
- window.addEventListener("keydown", h);
574
- return () => window.removeEventListener("keydown", h);
575
- }, [onClose]);
576
- if (!file)
577
- return null;
578
- async function handleSave() {
579
- if (strPassword && strPassword.length < 4) {
580
- setError("Password must be at least 4 characters");
581
- return;
582
- }
583
- setBusy(true);
584
- const save_dict = {
585
- id: fileId,
586
- shareable: enabled,
587
- needsPassword: !!strPassword,
588
- share_password: strPassword || null,
589
- password_str: strPassword || null,
590
- download_limit: download_limit === "" ? null : Number(download_limit),
591
- };
592
- try {
593
- const resp = await onSave(save_dict);
594
- onClose();
595
- }
596
- catch (err) {
597
- setError(`${err} Failed to save settings. Please try again.`);
598
- }
599
- finally {
600
- setBusy(false);
601
- }
602
- }
603
- return reactDom.createPortal(jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx("div", { className: "modal-backdrop z-100", onClick: (e) => e.target === e.currentTarget && onClose() }), jsxRuntime.jsx("div", { className: "modal-wrapper z-110", children: jsxRuntime.jsxs("div", { ref: modalRef, role: "dialog", "aria-modal": "true", tabIndex: -1, className: "modal-panel", children: [jsxRuntime.jsxs("h3", { className: "form-title", children: ["Share settings for ", jsxRuntime.jsx("span", { className: "font-mono", children: file.filename })] }), error && jsxRuntime.jsx("p", { className: "form-error", children: error }), jsxRuntime.jsx(abstractUtilities.Checkbox, { label: "Enable sharing", checked: enabled, onChange: (e) => setEnabled(e.target.checked), className: "checkbox" }), jsxRuntime.jsx(abstractUtilities.Input, { label: "Password (optional)", type: "text", value: strPassword, onChange: (e) => setStrPassword(e.target.value), disabled: !enabled, className: "form-input" }), jsxRuntime.jsx(abstractUtilities.Input, { label: "Download limit (0 = \u221E)", type: "number", min: 0, value: download_limit, onChange: (e) => setDownloadLimit(e.target.value === "" ? "" : Number(e.target.value)), disabled: !enabled, className: "form-input" }), enabled && shareLink && (jsxRuntime.jsx(abstractUtilities.Input, { label: "Share link", readOnly: true, value: shareLink, className: "form-input", trailing: jsxRuntime.jsx(abstractUtilities.Button, { variant: "icon", className: "btn-icon", onClick: () => navigator.clipboard.writeText(shareLink), children: jsxRuntime.jsx(ForwardRef$1, { className: "h-5 w-5" }) }) })), jsxRuntime.jsxs("div", { className: "mt-6 flex justify-end gap-2", children: [jsxRuntime.jsx(abstractUtilities.Button, { color: "green", variant: "primary", onClick: handleSave, disabled: busy, className: "btn-primary", children: busy ? "Saving…" : "Save" }), jsxRuntime.jsx(abstractUtilities.Button, { variant: "secondary", onClick: onClose, disabled: busy, className: "btn-secondary", children: "Cancel" })] })] }) })] }), document.body);
604
- }
605
-
606
- // Build a directory tree from a flat file list
607
- function buildTree(files) {
608
- const root = { name: '', path: '', children: new Map(), files: [] };
609
- files.forEach(file => {
610
- const parts = file.filepath.split('/');
611
- let node = root;
612
- parts.slice(0, -1).forEach((seg, idx) => {
613
- const subPath = parts.slice(0, idx + 1).join('/');
614
- if (!node.children.has(seg)) {
615
- node.children.set(seg, { name: seg, path: subPath, children: new Map(), files: [] });
616
- }
617
- node = node.children.get(seg);
618
- });
619
- node.files.push(file);
620
- });
621
- return root;
622
- }
623
- // Collect all file IDs under a node (recursive)
624
- function collectNodeIds(node) {
625
- let ids = [];
626
- node.files.forEach(f => ids.push(f.id));
627
- node.children.forEach(child => {
628
- ids = ids.concat(collectNodeIds(child));
629
- });
630
- return ids;
631
- }
632
- function FileTable({ files, loading, error, onToggleShare, onPatchShare, onDelete }) {
633
- const { load } = useFiles();
634
- const [search, setSearch] = React.useState('');
635
- const [modalFile, setModalFile] = React.useState(null);
636
- const [selected, setSelected] = React.useState(new Set());
637
- const [expanded, setExpanded] = React.useState(new Set());
638
- // Upload controls
639
- const [uploadDir, setUploadDir] = React.useState('');
640
- const [uploadFile, setUploadFile] = React.useState(null);
641
- // Filter files by search term
642
- const filtered = React.useMemo(() => files.filter(f => f.filename.toLowerCase().includes(search.trim().toLowerCase())), [files, search]);
643
- // Build tree and directory paths list
644
- const tree = React.useMemo(() => buildTree(filtered), [filtered]);
645
- const allDirPaths = React.useMemo(() => {
646
- const paths = [''];
647
- function collect(node) {
648
- node.children.forEach(child => {
649
- paths.push(child.path);
650
- collect(child);
651
- });
652
- }
653
- collect(tree);
654
- return paths;
655
- }, [tree]);
656
- // Selection & expand/collapse logic
657
- const allIds = filtered.map(f => f.id);
658
- const toggleAllSelection = () => selected.size < allIds.length ? setSelected(new Set(allIds)) : setSelected(new Set());
659
- const toggleDirOpen = (path) => setExpanded(prev => {
660
- const next = new Set(prev);
661
- next.has(path) ? next.delete(path) : next.add(path);
662
- return next;
663
- });
664
- const expandAll = () => setExpanded(new Set(allDirPaths));
665
- const collapseAll = () => setExpanded(new Set());
666
- // Recursive renderer: directory row with select-all checkbox, then its files, then subdirectories
667
- const renderNode = (node, depth = 0) => {
668
- const rows = [];
669
- if (node.path) {
670
- const isOpen = expanded.has(node.path);
671
- const nodeIds = collectNodeIds(node);
672
- const allSelected = nodeIds.every(id => selected.has(id));
673
- const someSelected = nodeIds.some(id => selected.has(id));
674
- rows.push(jsxRuntime.jsxs("tr", { children: [jsxRuntime.jsx("td", { className: "px-4 py-2 bg-gray-100", children: jsxRuntime.jsx("input", { type: "checkbox", checked: allSelected, ref: el => { if (el)
675
- el.indeterminate = !allSelected && someSelected; }, onChange: () => {
676
- setSelected(prev => {
677
- const next = new Set(prev);
678
- if (allSelected) {
679
- nodeIds.forEach(id => next.delete(id));
680
- }
681
- else {
682
- nodeIds.forEach(id => next.add(id));
683
- }
684
- return next;
685
- });
686
- } }) }), jsxRuntime.jsx("td", { className: "px-4 py-2 bg-gray-100 font-bold", colSpan: 5, style: { textAlign: 'left' }, children: node.path }), jsxRuntime.jsx("td", { className: "px-4 py-2 bg-gray-100 text-right", children: jsxRuntime.jsx("button", { onClick: () => toggleDirOpen(node.path), children: isOpen ? '▾' : '▸' }) })] }, node.path));
687
- if (!isOpen)
688
- return rows;
689
- }
690
- // Render files in this directory
691
- node.files.sort((a, b) => a.filename.localeCompare(b.filename)).forEach(f => {
692
- const isOwner = f.uploader_id.toLowerCase() === (abstractUtilities.currentUsername() ?? '').toLowerCase();
693
- const canDownload = isOwner || f.shareable;
694
- rows.push(jsxRuntime.jsxs("tr", { children: [jsxRuntime.jsx("td", { className: "px-4 py-2", children: jsxRuntime.jsx("input", { type: "checkbox", checked: selected.has(f.id), onChange: () => setSelected(prev => {
695
- const next = new Set(prev);
696
- next.has(f.id) ? next.delete(f.id) : next.add(f.id);
697
- return next;
698
- }) }) }), jsxRuntime.jsx("td", { className: "px-4 py-2", style: { paddingLeft: (depth + 1) * 20 }, children: f.filename }), jsxRuntime.jsx("td", { className: "px-4 py-2", children: canDownload ? jsxRuntime.jsx(abstractUtilities.Button, { variant: "secondary", onClick: () => downloadFile(f), children: "Download" }) : jsxRuntime.jsx("span", { children: "\uD83D\uDD12" }) }), jsxRuntime.jsx("td", { className: "px-4 py-2", children: isOwner && jsxRuntime.jsx(abstractUtilities.Button, { variant: "icon", onClick: () => setModalFile(f), children: "\u2699\uFE0F" }) }), jsxRuntime.jsx("td", { className: "px-4 py-2", children: isOwner && jsxRuntime.jsx(abstractUtilities.Button, { variant: f.shareable ? 'primary' : 'secondary', onClick: () => onToggleShare(f), children: f.shareable ? 'Sharing' : 'Share' }) }), jsxRuntime.jsx("td", { className: "px-4 py-2", children: isOwner && jsxRuntime.jsx(abstractUtilities.Button, { variant: "icon", onClick: () => onDelete(f).then(load), children: "\uD83D\uDDD1" }) }), jsxRuntime.jsxs("td", { className: "px-4 py-2", children: [f.download_count, " / ", f.download_limit ?? '∞'] })] }, f.id));
699
- });
700
- // Render subdirectories
701
- Array.from(node.children.values())
702
- .sort((a, b) => a.name.localeCompare(b.name))
703
- .forEach(child => rows.push(...renderNode(child, depth + 1)));
704
- return rows;
705
- };
706
- // Batch actions
707
- const handleDownloadAll = async () => {
708
- const inputs = filtered.filter(f => selected.has(f.id)).map(f => document.createElement('input'));
709
- inputs.forEach((cb, idx) => cb.value = filtered.filter(f => selected.has(f.id))[idx].filepath);
710
- await downloadSelected(inputs);
711
- };
712
- const handleDeleteAll = async () => { await onDelete(filtered.filter(f => selected.has(f.id))); setSelected(new Set()); await load(); };
713
- const handleShareAll = async () => { for (const f of filtered.filter(f => selected.has(f.id)))
714
- await onToggleShare(f); };
715
- if (loading)
716
- return jsxRuntime.jsx(abstractUtilities.Spinner, {});
717
- if (error)
718
- return jsxRuntime.jsx("p", { className: "text-red-600", children: error.message });
719
- return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsxs("div", { className: "mb-4 flex flex-col md:flex-row md:justify-between gap-4", children: [jsxRuntime.jsx(abstractUtilities.Input, { label: "Search files", type: "text", value: search, onChange: e => setSearch(e.target.value), placeholder: "Enter filename\u2026", className: "form-input flex-1" }), jsxRuntime.jsxs("div", { className: "flex gap-2", children: [jsxRuntime.jsx(abstractUtilities.Button, { onClick: expandAll, disabled: !allDirPaths.length, children: "Expand All" }), jsxRuntime.jsx(abstractUtilities.Button, { onClick: collapseAll, disabled: !allDirPaths.length, children: "Collapse All" }), jsxRuntime.jsx(abstractUtilities.Button, { onClick: handleDownloadAll, disabled: !selected.size, children: "Download Selected" }), jsxRuntime.jsx(abstractUtilities.Button, { color: "gray", onClick: handleDeleteAll, disabled: !selected.size, children: "Delete Selected" }), jsxRuntime.jsx(abstractUtilities.Button, { onClick: handleShareAll, disabled: !selected.size, children: "Share Selected" })] })] }), jsxRuntime.jsx("div", { className: "overflow-x-auto wm-container", children: jsxRuntime.jsxs("table", { className: "file-table w-full", children: [jsxRuntime.jsx("thead", { className: "wm-thead", children: jsxRuntime.jsxs("tr", { children: [jsxRuntime.jsx("th", { className: "px-4 py-2 w-12", children: jsxRuntime.jsx("input", { type: "checkbox", checked: selected.size === filtered.length && filtered.length > 0, onChange: toggleAllSelection }) }), jsxRuntime.jsx("th", { className: "px-4 py-2", children: "Filename" }), jsxRuntime.jsx("th", { className: "px-4 py-2", children: "Download" }), jsxRuntime.jsx("th", { className: "px-4 py-2", children: "Settings" }), jsxRuntime.jsx("th", { className: "px-4 py-2", children: "Share" }), jsxRuntime.jsx("th", { className: "px-4 py-2", children: "Delete" }), jsxRuntime.jsx("th", { className: "px-4 py-2", children: "Downloads" })] }) }), jsxRuntime.jsx("tbody", { children: renderNode(tree) })] }) }), jsxRuntime.jsx(ShareModal, { file: modalFile, onClose: () => setModalFile(null), onSave: async (patch) => {
720
- if (!modalFile)
721
- return;
722
- const updated = { ...modalFile, ...patch };
723
- await onPatchShare(updated);
724
- setModalFile(null);
725
- } })] }));
726
- }
727
-
728
- function Upload({ onUploaded }) {
729
- const [queue, setQueue] = React.useState([]);
730
- const [busy, setBusy] = React.useState(false);
731
- const [message, setMessage] = React.useState(null);
732
- const [drag, setDrag] = React.useState(false);
733
- const inputRef = React.useRef(null);
734
- const [dirs, setDirs] = React.useState([]);
735
- // — existing files → directory list —
736
- const { files, load } = useFiles();
737
- React.useEffect(() => {
738
- const dirSet = new Set();
739
- files.forEach(f => {
740
- const parts = f.filepath.split('/');
741
- if (parts.length > 1) {
742
- parts.pop();
743
- dirSet.add(parts.join('/') + '/');
744
- }
745
- });
746
- setDirs(Array.from(dirSet).sort());
747
- }, [files]);
748
- // — directory selection state —
749
- const [uploadDir, setUploadDir] = React.useState('');
750
- const [creatingNew, setCreatingNew] = React.useState(false);
751
- const [newDirName, setNewDirName] = React.useState('');
752
- // — queue logic —
753
- const enqueue = React.useCallback((files) => {
754
- setQueue(prev => {
755
- const map = new Map(prev.map(q => [`${q.file.name}-${q.file.size}`, q]));
756
- files.forEach(q => map.set(`${q.file.name}-${q.file.size}`, q));
757
- return Array.from(map.values());
758
- });
759
- }, []);
760
- const extractFromDataTransfer = React.useCallback(async (dt) => {
761
- const out = [];
762
- const walk = async (entry, path = "") => {
763
- if (!entry)
764
- return;
765
- if (entry.isFile) {
766
- const file = await new Promise(res => entry.file(res));
767
- out.push({ file, relPath: path, progress: 0 });
768
- }
769
- else if (entry.isDirectory) {
770
- const reader = entry.createReader();
771
- const entries = await new Promise(r => reader.readEntries(r));
772
- await Promise.all(entries.map(e => walk(e, `${path}${entry.name}/`)));
773
- }
774
- };
775
- await Promise.all(Array.from(dt.items).map(i => walk(i.webkitGetAsEntry())));
776
- return out;
777
- }, []);
778
- const handleSelect = (e) => {
779
- const files = Array.from(e.target.files || []);
780
- const queued = files.map(f => ({
781
- file: f,
782
- relPath: f.webkitRelativePath.replace(/[^/]*$/, ""),
783
- progress: 0,
784
- }));
785
- enqueue(queued);
786
- e.target.value = "";
787
- };
788
- const handleDrop = async (e) => {
789
- e.preventDefault();
790
- const files = await extractFromDataTransfer(e.dataTransfer);
791
- enqueue(files.length ? files : []);
792
- setDrag(false);
793
- };
794
- const handleUploadAll = async () => {
795
- if (!queue.length) {
796
- setMessage("No files queued.");
797
- return;
798
- }
799
- setBusy(true);
800
- setMessage(null);
801
- const targetDir = creatingNew
802
- ? (newDirName.trim().replace(/^\/+|\/+$/g, "") + "/")
803
- : uploadDir;
804
- try {
805
- for (const { file, relPath } of queue) {
806
- await uploadFiles(file, targetDir + relPath);
807
- setQueue(cur => cur.map(item => item.file === file ? { ...item, progress: 100 } : item));
808
- }
809
- setQueue([]);
810
- await onUploaded();
811
- setMessage("Upload complete");
812
- load();
813
- }
814
- catch {
815
- setMessage("Upload failed");
816
- }
817
- finally {
818
- setBusy(false);
819
- }
820
- };
821
- // — directory selector handlers —
822
- const handleDirChange = (e) => {
823
- const v = e.target.value;
824
- if (v === "__new__") {
825
- setCreatingNew(true);
826
- setNewDirName("");
827
- }
828
- else {
829
- setCreatingNew(false);
830
- setUploadDir(v);
831
- }
832
- };
833
- const acceptNewDir = () => {
834
- // normalize like before
835
- const cleanName = newDirName.trim().replace(/^\/+|\/+$/g, '');
836
- const normalized = `${uploadDir}${cleanName}/`;
837
- // ① add it to our dirs list if it isn't there already
838
- setDirs(prev => {
839
- if (prev.includes(normalized))
840
- return prev;
841
- return [...prev, normalized].sort();
842
- });
843
- // ② select it & flip back to the <select>
844
- setUploadDir(normalized);
845
- setCreatingNew(false);
846
- setNewDirName('');
847
- };
848
- return (jsxRuntime.jsxs("div", { className: "form-container max-w-2xl mx-auto", children: [jsxRuntime.jsxs("div", { className: `flex items-center border-2 border-dashed rounded-lg p-6 bg-white shadow-md space-x-4 transition-shadow duration-150
849
- ${drag ? 'border-indigo-500 bg-indigo-50 shadow-lg' : 'border-gray-300 hover:shadow-lg'}`, onClick: () => inputRef.current?.click(), onDragEnter: e => { e.preventDefault(); setDrag(true); }, onDragOver: e => e.preventDefault(), onDragLeave: () => setDrag(false), onDrop: handleDrop, role: "region", "aria-label": "File upload area", children: [jsxRuntime.jsx(ForwardRef, { style: { width: 300, height: 300 }, className: "text-gray-500 mb-3", "aria-hidden": "true" }), jsxRuntime.jsxs("div", { className: "flex-1", children: [jsxRuntime.jsx("p", { className: "text-base font-semibold text-gray-700", children: "Drag & drop files or folders here" }), queue.length > 0 && (jsxRuntime.jsxs("p", { className: "mt-1 text-sm text-gray-600", children: [queue.length, " item", queue.length > 1 && 's', " queued"] }))] })] }), jsxRuntime.jsx("input", { ref: inputRef, type: "file", multiple: true, className: "hidden", ...{ webkitdirectory: true, directory: '' }, onChange: handleSelect }), jsxRuntime.jsxs("div", { className: "flex items-center justify-between mt-4", children: [jsxRuntime.jsx("div", { className: "flex-1 mr-4", children: !creatingNew ? (jsxRuntime.jsxs("select", { value: uploadDir, onChange: handleDirChange, className: "form-select w-full", children: [dirs.map(d => (jsxRuntime.jsx("option", { value: d, children: d }, d))), jsxRuntime.jsx("option", { value: "__new__", children: "+ Create new directory\u2026" })] })) : (jsxRuntime.jsxs("div", { className: "flex", children: [jsxRuntime.jsx("input", { type: "text", value: newDirName, placeholder: "new-folder-name", onChange: e => setNewDirName(e.target.value), onBlur: acceptNewDir, onKeyDown: e => e.key === 'Enter' && acceptNewDir(), className: "form-input flex-1" }), jsxRuntime.jsx(abstractUtilities.Button, { onClick: acceptNewDir, className: "ml-2", children: "OK" })] })) }), jsxRuntime.jsx(abstractUtilities.Button, { color: "blue", variant: "primary", onClick: handleUploadAll, disabled: busy || !queue.length, children: busy ? 'Uploading…' : `Upload ${queue.length}` })] }), message && (jsxRuntime.jsx("p", { className: `text-center text-sm ${message.includes('failed') ? 'text-red-600' : 'text-gray-600'}`, role: "alert", children: message })), queue.length > 0 && (jsxRuntime.jsxs("div", { className: "wm-container mt-4", children: [jsxRuntime.jsx("h3", { className: "mb-2 text-sm font-semibold text-gray-700", children: "Queued Files" }), jsxRuntime.jsx("ul", { className: "max-h-64 space-y-4 overflow-y-auto", children: queue.map(({ file, relPath, progress }) => (jsxRuntime.jsxs("li", { className: "space-y-1", children: [jsxRuntime.jsxs("div", { className: "flex justify-between items-center", children: [jsxRuntime.jsxs("span", { className: "truncate text-sm text-gray-800", children: [relPath, file.name] }), jsxRuntime.jsxs("span", { className: "text-xs text-gray-500", children: [progress, "%"] })] }), jsxRuntime.jsx("div", { className: "w-full bg-gray-200 rounded-full h-2 overflow-hidden", children: jsxRuntime.jsx("div", { className: "bg-indigo-600 h-2", style: { width: `${progress}%` } }) })] }, `${file.name}-${file.size}`))) })] }))] }));
850
- }
851
-
852
- // Utility to convert input to array
853
- function ensure_list(obj) {
854
- if (Array.isArray(obj))
855
- return obj.filter(Boolean);
856
- if (typeof obj === 'string')
857
- return obj.split(' ').filter(Boolean);
858
- return [];
859
- }
860
- // Get list and length safely
861
- function getListAndLen(obj) {
862
- const return_obj = ensure_list(obj);
863
- const ob_len = return_obj.length;
864
- return { return_obj, ob_len };
865
- }
866
- // Combine class names with defaults
867
- function combineClassNames(classNames = [], defaultClasses = ['form-group', 'form-label', 'flex', 'gap-2']) {
868
- const { return_obj: inputClasses } = getListAndLen(classNames);
869
- const { return_obj: defaultClassList } = getListAndLen(defaultClasses);
870
- // Combine and remove duplicates
871
- const combined = [...new Set([...defaultClassList, ...inputClasses])];
872
- console.log(`Combined classes (length: ${combined.length}): ${combined.join(' ')}`);
873
- return combined;
874
- }
875
- function Button({ children, color = 'gray', variant = 'default', className = '', ...rest }) {
876
- const variantClasses = {
877
- default: 'btn',
878
- icon: 'btn-icon',
879
- primary: 'btn-primary',
880
- secondary: 'btn-secondary',
881
- };
882
- const colorClasses = {
883
- gray: '',
884
- green: variant === 'primary' ? 'btn-success' : 'btn-secondary',
885
- blue: variant === 'primary' ? 'btn-primary' : 'btn-secondary',
886
- };
887
- const classes = combineClassNames([variantClasses[variant], colorClasses[color], className]);
888
- return (jsxRuntime.jsx("button", { className: classes.join(' '), ...rest, children: children }));
889
- }
890
- function Checkbox({ label, className = '', classNames = [], ...rest }) {
891
- const classes = combineClassNames(classNames, ['checkbox', 'wm-checkbox']);
892
- return (jsxRuntime.jsxs("label", { className: classes.join(' '), children: [jsxRuntime.jsx("input", { type: "checkbox", className: "wm-checkbox", ...rest }), label && jsxRuntime.jsx("span", { children: label })] }));
893
- }
894
- function Input({ label, trailing, className = '', classNames = [], ...rest }) {
895
- const classes = combineClassNames(classNames, ['form-group', 'flex', 'gap-2']);
896
- const inputClasses = combineClassNames(className, ['form-input']);
897
- return (jsxRuntime.jsxs("label", { className: classes.join(' '), children: [label && jsxRuntime.jsx("span", { className: "form-label", children: label }), jsxRuntime.jsxs("div", { className: "flex gap-2", children: [jsxRuntime.jsx("input", { className: inputClasses.join(' '), ...rest }), trailing] })] }));
898
- }
899
- function Spinner() {
900
- const classes = combineClassNames('animate-pulse');
901
- return jsxRuntime.jsx("p", { className: classes.join(' '), children: "Loading\u2026" });
902
- }
903
6
 
904
7
  function checkTokenValid() {
905
8
  const tok = abstractUtilities.getToken();
@@ -907,42 +10,9 @@ function checkTokenValid() {
907
10
  }
908
11
  const SecureFiles = () => {
909
12
  const [loggedIn, setLoggedIn] = abstractUtilities.useState(checkTokenValid());
910
- const filesHook = useFiles(); // ← one single hook instance
911
- return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx(Upload, { onUploaded: filesHook.load }), jsxRuntime.jsx(FileTable, { files: filesHook.files, loading: filesHook.loading, error: filesHook.error, onDownload: filesHook.toggleShare, onToggleShare: filesHook.toggleShare, onPatchShare: filesHook.patchShareSettings, onDelete: filesHook.deleteFile })] }));
13
+ const filesHook = abstractFiles.useFiles(); // ← one single hook instance
14
+ return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx(abstractFiles.Upload, { onUploaded: filesHook.load }), jsxRuntime.jsx(abstractFiles.FileTable, { files: filesHook.files, loading: filesHook.loading, error: filesHook.error, onDownload: filesHook.toggleShare, onToggleShare: filesHook.toggleShare, onPatchShare: filesHook.patchShareSettings, onDelete: filesHook.deleteFile })] }));
912
15
  };
913
16
 
914
- exports.Button = Button;
915
- exports.Checkbox = Checkbox;
916
- exports.FileRow = FileRow;
917
- exports.FileTable = FileTable;
918
- exports.Input = Input;
919
17
  exports.SecureFiles = SecureFiles;
920
- exports.ShareModal = ShareModal;
921
- exports.Spinner = Spinner;
922
- exports.Upload = Upload;
923
- exports.directDownload = directDownload;
924
- exports.downloadFile = downloadFile;
925
- exports.downloadRow = downloadRow;
926
- exports.downloadSelect = downloadSelect;
927
- exports.downloadSelected = downloadSelected;
928
- exports.getToggleShare = getToggleShare;
929
- exports.initSelectAll = initSelectAll;
930
- exports.listFiles = listFiles;
931
- exports.patchFileSetting = patchFileSetting;
932
- exports.removeFile = removeFile;
933
- exports.removeRow = removeRow;
934
- exports.removeSelected = removeSelected;
935
- exports.syncHeaderCheckbox = syncHeaderCheckbox;
936
- exports.toggleCheckboxes = toggleCheckboxes;
937
- exports.toggleShare = toggleShare;
938
- exports.triggerDownload = triggerDownload;
939
- exports.updateFileShare = updateFileShare;
940
- exports.uploadFile = uploadFile;
941
- exports.uploadFiles = uploadFiles;
942
- exports.useDeleteFile = useDeleteFile;
943
- exports.useFiles = useFiles;
944
- exports.useLoadFiles = useLoadFiles;
945
- exports.usePatchShareSettings = usePatchShareSettings;
946
- exports.useSaveShareSettings = useSaveShareSettings;
947
- exports.useToggleShare = useToggleShare;
948
18
  //# sourceMappingURL=index.cjs.map