@putkoff/abstract-securefiles 0.0.19 → 0.0.23
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.
- package/dist/App/SecureFiles.d.ts +4 -0
- package/dist/App/index.d.ts +1 -1
- package/dist/FileHandlers/FileRow/imports.d.ts +2 -0
- package/dist/FileHandlers/FileRow/index.d.ts +1 -0
- package/dist/FileHandlers/FileRow/src/index.d.ts +1 -0
- package/dist/FileHandlers/FileRow/src/row_utils.d.ts +2 -0
- package/dist/FileHandlers/FileTable/imports.d.ts +4 -0
- package/dist/FileHandlers/FileTable/index.d.ts +1 -0
- package/dist/FileHandlers/FileTable/src/index.d.ts +1 -0
- package/dist/FileHandlers/FileTable/src/table_utils.d.ts +2 -0
- package/dist/FileHandlers/ShareModal/imports.d.ts +2 -0
- package/dist/FileHandlers/ShareModal/index.d.ts +1 -0
- package/dist/FileHandlers/ShareModal/src/index.d.ts +1 -0
- package/dist/FileHandlers/ShareModal/src/share_utils.d.ts +2 -0
- package/dist/FileHandlers/Upload/imports.d.ts +2 -0
- package/dist/FileHandlers/Upload/index.d.ts +1 -0
- package/dist/FileHandlers/Upload/src/index.d.ts +2 -0
- package/dist/FileHandlers/Upload/src/upload_utils.d.ts +2 -0
- package/dist/FileHandlers/imports.d.ts +4 -0
- package/dist/FileHandlers/index.d.ts +4 -0
- package/dist/UI/index.d.ts +19 -0
- package/dist/file_utils/imports.d.ts +2 -0
- package/dist/file_utils/index.d.ts +1 -0
- package/dist/file_utils/src/delete_utils/imports.d.ts +1 -0
- package/dist/file_utils/src/delete_utils/index.d.ts +1 -0
- package/dist/file_utils/src/delete_utils/src/delete_row.d.ts +2 -0
- package/dist/file_utils/src/delete_utils/src/delete_select.d.ts +1 -0
- package/dist/file_utils/src/delete_utils/src/delete_selected.d.ts +2 -0
- package/dist/file_utils/src/delete_utils/src/index.d.ts +3 -0
- package/dist/file_utils/src/download_utils/imports.d.ts +2 -0
- package/dist/file_utils/src/download_utils/index.d.ts +1 -0
- package/dist/file_utils/src/download_utils/src/direct_download.d.ts +3 -0
- package/dist/file_utils/src/download_utils/src/download_file.d.ts +2 -0
- package/dist/file_utils/src/download_utils/src/download_select.d.ts +2 -0
- package/dist/file_utils/src/download_utils/src/index.d.ts +5 -0
- package/dist/file_utils/src/download_utils/src/target_downloads.d.ts +4 -0
- package/dist/file_utils/src/download_utils/src/trigger_download.d.ts +2 -0
- package/dist/file_utils/src/imports.d.ts +2 -0
- package/dist/file_utils/src/index.d.ts +6 -0
- package/dist/file_utils/src/list_utils/imports.d.ts +2 -0
- package/dist/file_utils/src/list_utils/index.d.ts +1 -0
- package/dist/file_utils/src/list_utils/src/index.d.ts +1 -0
- package/dist/file_utils/src/list_utils/src/list_utils.d.ts +2 -0
- package/dist/file_utils/src/select_utils/index.d.ts +1 -0
- package/dist/file_utils/src/select_utils/src/index.d.ts +3 -0
- package/dist/file_utils/src/select_utils/src/init_select_all.d.ts +2 -0
- package/dist/file_utils/src/select_utils/src/sync_header_checkbox.d.ts +5 -0
- package/dist/file_utils/src/select_utils/src/toggle_checkboxes.d.ts +2 -0
- package/dist/file_utils/src/share_utils/imports.d.ts +2 -0
- package/dist/file_utils/src/share_utils/index.d.ts +1 -0
- package/dist/file_utils/src/share_utils/src/index.d.ts +1 -0
- package/dist/file_utils/src/share_utils/src/share_utils.d.ts +7 -0
- package/dist/file_utils/src/upload_utils/imports.d.ts +1 -0
- package/dist/file_utils/src/upload_utils/index.d.ts +1 -0
- package/dist/file_utils/src/upload_utils/src/index.d.ts +3 -0
- package/dist/file_utils/src/upload_utils/src/patch_file_settings.d.ts +2 -0
- package/dist/file_utils/src/upload_utils/src/upload_file.d.ts +2 -0
- package/dist/file_utils/src/upload_utils/src/upload_files.d.ts +5 -0
- package/dist/hooks/imports.d.ts +3 -0
- package/dist/hooks/index.d.ts +1 -0
- package/dist/hooks/useFiles/imports.d.ts +2 -0
- package/dist/hooks/useFiles/index.d.ts +1 -0
- package/dist/hooks/useFiles/src/deleteFile.d.ts +6 -0
- package/dist/hooks/useFiles/src/index.d.ts +6 -0
- package/dist/hooks/useFiles/src/load.d.ts +6 -0
- package/dist/hooks/useFiles/src/patchShareSettings.d.ts +7 -0
- package/dist/hooks/useFiles/src/toggleShare.d.ts +6 -0
- package/dist/hooks/useFiles/src/useFiles.d.ts +13 -0
- package/dist/hooks/useFiles/src/useSaveShareSettings.d.ts +3 -0
- package/dist/imports.d.ts +5 -0
- package/dist/index.cjs +933 -3
- package/dist/index.cjs.map +1 -1
- package/dist/index.css +1 -1
- package/dist/index.d.ts +7 -0
- package/dist/index.js +884 -5
- package/dist/index.js.map +1 -1
- package/dist/styles/index.d.ts +1 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/src/index.d.ts +1 -0
- package/dist/types/src/types.d.ts +75 -0
- package/package.json +4 -4
- package/src/App/{secure_files.tsx → SecureFiles.tsx} +4 -5
- package/src/App/index.ts +1 -1
- package/src/FileHandlers/FileRow/imports.ts +2 -0
- package/src/FileHandlers/FileRow/index.ts +1 -0
- package/src/FileHandlers/FileRow/src/index.ts +1 -0
- package/src/FileHandlers/FileRow/src/row_utils.tsx +143 -0
- package/src/FileHandlers/FileTable/imports.ts +16 -0
- package/src/FileHandlers/FileTable/index.ts +1 -0
- package/src/FileHandlers/FileTable/src/index.ts +1 -0
- package/src/FileHandlers/FileTable/src/table_utils.tsx +251 -0
- package/src/FileHandlers/ShareModal/imports.ts +2 -0
- package/src/FileHandlers/ShareModal/index.ts +1 -0
- package/src/FileHandlers/ShareModal/src/index.ts +1 -0
- package/src/FileHandlers/ShareModal/src/share_utils.tsx +158 -0
- package/src/FileHandlers/Upload/imports.ts +3 -0
- package/src/FileHandlers/Upload/index.ts +1 -0
- package/src/FileHandlers/Upload/src/index.ts +2 -0
- package/src/FileHandlers/Upload/src/upload_utils.tsx +267 -0
- package/src/FileHandlers/imports.ts +20 -0
- package/src/FileHandlers/index.ts +4 -0
- package/src/UI/index.tsx +113 -0
- package/src/custom.d.ts +7 -0
- package/src/file_utils/imports.ts +2 -0
- package/src/file_utils/index.ts +1 -0
- package/src/file_utils/src/delete_utils/imports.ts +1 -0
- package/src/file_utils/src/delete_utils/index.ts +1 -0
- package/src/file_utils/src/delete_utils/src/delete_row.ts +11 -0
- package/src/file_utils/src/delete_utils/src/delete_select.ts +9 -0
- package/src/file_utils/src/delete_utils/src/delete_selected.ts +15 -0
- package/src/file_utils/src/delete_utils/src/index.ts +3 -0
- package/src/file_utils/src/download_utils/imports.ts +3 -0
- package/src/file_utils/src/download_utils/index.ts +1 -0
- package/src/file_utils/src/download_utils/src/direct_download.ts +22 -0
- package/src/file_utils/src/download_utils/src/download_file.ts +24 -0
- package/src/file_utils/src/download_utils/src/download_select.ts +20 -0
- package/src/file_utils/src/download_utils/src/index.ts +5 -0
- package/src/file_utils/src/download_utils/src/target_downloads.ts +21 -0
- package/src/file_utils/src/download_utils/src/trigger_download.ts +14 -0
- package/src/file_utils/src/imports.ts +3 -0
- package/src/file_utils/src/index.ts +6 -0
- package/src/file_utils/src/list_utils/imports.ts +2 -0
- package/src/file_utils/src/list_utils/index.ts +1 -0
- package/src/file_utils/src/list_utils/src/index.ts +1 -0
- package/src/file_utils/src/list_utils/src/list_utils.ts +24 -0
- package/src/file_utils/src/select_utils/index.ts +1 -0
- package/src/file_utils/src/select_utils/src/index.ts +3 -0
- package/src/file_utils/src/select_utils/src/init_select_all.ts +37 -0
- package/src/file_utils/src/select_utils/src/sync_header_checkbox.ts +18 -0
- package/src/file_utils/src/select_utils/src/toggle_checkboxes.ts +11 -0
- package/src/file_utils/src/share_utils/imports.ts +2 -0
- package/src/file_utils/src/share_utils/index.ts +1 -0
- package/src/file_utils/src/share_utils/src/index.ts +1 -0
- package/src/file_utils/src/share_utils/src/share_utils.ts +29 -0
- package/src/file_utils/src/upload_utils/imports.ts +1 -0
- package/src/file_utils/src/upload_utils/index.ts +1 -0
- package/src/file_utils/src/upload_utils/src/index.ts +3 -0
- package/src/file_utils/src/upload_utils/src/patch_file_settings.ts +18 -0
- package/src/file_utils/src/upload_utils/src/upload_file.ts +28 -0
- package/src/file_utils/src/upload_utils/src/upload_files.ts +34 -0
- package/src/global.d.ts +1 -0
- package/src/hooks/imports.ts +3 -0
- package/src/hooks/index.ts +1 -0
- package/src/hooks/useFiles/imports.ts +3 -0
- package/src/hooks/useFiles/index.ts +1 -0
- package/src/hooks/useFiles/src/deleteFile.ts +22 -0
- package/src/hooks/useFiles/src/index.ts +6 -0
- package/src/hooks/useFiles/src/load.ts +52 -0
- package/src/hooks/useFiles/src/patchShareSettings.ts +37 -0
- package/src/hooks/useFiles/src/toggleShare.ts +28 -0
- package/src/hooks/useFiles/src/useFiles.ts +35 -0
- package/src/hooks/useFiles/src/useSaveShareSettings.ts +25 -0
- package/src/imports.ts +20 -0
- package/src/index.ts +8 -1
- package/src/styles/SecureFiles/index.css +10 -0
- package/src/styles/SecureFilesStyles.module.css +287 -0
- package/src/styles/index.ts +1 -0
- package/src/types/index.ts +1 -0
- package/src/types/src/index.ts +1 -0
- package/src/types/src/types.ts +88 -0
- package/dist/App/secure_files.d.ts +0 -4
- package/src/App/index.d.ts +0 -1
- package/src/App/secure_files.d.ts +0 -4
- package/src/App/styles/index.css +0 -15
- package/src/index.d.ts +0 -1
- package/src/logs/abstract_flask.log +0 -0
- package/src/logs/clipit_logs.log +0 -10
- package/src/logs/fetch_utils.log +0 -0
- package/src/logs/pair_data.log +0 -0
- package/src/logs/query_utils.log +0 -0
- package/src/logs/routes_bp.log +0 -2
- /package/src/{App/styles → hooks/useFiles/src}/logs/clipit_logs.log +0 -0
- /package/src/{App/styles → hooks/useFiles/src}/logs/routes_bp.log +0 -0
- /package/src/{App/styles → styles/SecureFiles}/App.css +0 -0
- /package/{package.tmp.json → src/styles/SecureFiles/logs/clipit_logs.log} +0 -0
- /package/src/{logs/abstract_clipit.clipit.gui_frontend.drag_and_drop_component.imports.robust_readers.directory_reader.directory_reader.log → styles/SecureFiles/logs/routes_bp.log} +0 -0
- /package/src/{App/styles → styles/SecureFiles}/tailwind.css +0 -0
- /package/src/{App/styles → styles/SecureFiles}/window-manager.css +0 -0
package/dist/index.cjs
CHANGED
|
@@ -1,8 +1,905 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
var jsxRuntime = require('react/jsx-runtime');
|
|
4
|
-
var
|
|
4
|
+
var reactDom = require('react-dom');
|
|
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
|
+
}
|
|
6
903
|
|
|
7
904
|
function checkTokenValid() {
|
|
8
905
|
const tok = abstractUtilities.getToken();
|
|
@@ -10,9 +907,42 @@ function checkTokenValid() {
|
|
|
10
907
|
}
|
|
11
908
|
const SecureFiles = () => {
|
|
12
909
|
const [loggedIn, setLoggedIn] = abstractUtilities.useState(checkTokenValid());
|
|
13
|
-
const filesHook =
|
|
14
|
-
return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx(
|
|
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 })] }));
|
|
15
912
|
};
|
|
16
913
|
|
|
914
|
+
exports.Button = Button;
|
|
915
|
+
exports.Checkbox = Checkbox;
|
|
916
|
+
exports.FileRow = FileRow;
|
|
917
|
+
exports.FileTable = FileTable;
|
|
918
|
+
exports.Input = Input;
|
|
17
919
|
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;
|
|
18
948
|
//# sourceMappingURL=index.cjs.map
|