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