@putkoff/abstract-securefiles 0.0.48 → 0.0.49
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/ConfirmButton.d.ts +5 -0
- package/dist/CopyButton.d.ts +4 -0
- package/dist/FolderSelect.d.ts +6 -0
- package/dist/SecureFiles.d.ts +3 -0
- package/dist/ServerFiles.d.ts +6 -0
- package/dist/ShareDialog.d.ts +6 -0
- package/dist/UploadPanel.d.ts +6 -0
- package/dist/api.d.ts +46 -0
- package/dist/index.cjs +537 -1004
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +4 -8
- package/dist/index.js +530 -951
- package/dist/index.js.map +1 -1
- package/dist/types.d.ts +54 -0
- package/package.json +15 -11
- package/src/ConfirmButton.tsx +35 -0
- package/src/CopyButton.tsx +24 -0
- package/src/FolderSelect.tsx +68 -0
- package/src/SecureFiles.tsx +344 -0
- package/src/ServerFiles.tsx +324 -0
- package/src/ShareDialog.tsx +148 -0
- package/src/UploadPanel.tsx +217 -0
- package/src/api.ts +119 -0
- package/src/index.ts +4 -8
- package/src/styles.css +432 -0
- package/src/types.ts +54 -0
- package/css.d.ts +0 -2
- package/dist/App/SecureFiles.d.ts +0 -4
- package/dist/App/index.d.ts +0 -1
- package/dist/FileHandlers/FileRow/imports.d.ts +0 -2
- package/dist/FileHandlers/FileRow/index.d.ts +0 -1
- package/dist/FileHandlers/FileRow/src/index.d.ts +0 -1
- package/dist/FileHandlers/FileRow/src/row_utils.d.ts +0 -2
- package/dist/FileHandlers/FileTable/imports.d.ts +0 -4
- package/dist/FileHandlers/FileTable/index.d.ts +0 -1
- package/dist/FileHandlers/FileTable/src/index.d.ts +0 -1
- package/dist/FileHandlers/FileTable/src/table_utils.d.ts +0 -2
- package/dist/FileHandlers/ShareModal/imports.d.ts +0 -2
- package/dist/FileHandlers/ShareModal/index.d.ts +0 -1
- package/dist/FileHandlers/ShareModal/src/index.d.ts +0 -1
- package/dist/FileHandlers/ShareModal/src/share_utils.d.ts +0 -2
- package/dist/FileHandlers/Upload/imports.d.ts +0 -2
- package/dist/FileHandlers/Upload/index.d.ts +0 -1
- package/dist/FileHandlers/Upload/src/index.d.ts +0 -2
- package/dist/FileHandlers/Upload/src/upload_utils.d.ts +0 -2
- package/dist/FileHandlers/imports.d.ts +0 -4
- package/dist/FileHandlers/index.d.ts +0 -4
- package/dist/UI/index.d.ts +0 -28
- package/dist/file_utils/imports.d.ts +0 -2
- package/dist/file_utils/index.d.ts +0 -1
- package/dist/file_utils/src/cookieFetch.d.ts +0 -33
- package/dist/file_utils/src/delete_utils/imports.d.ts +0 -1
- package/dist/file_utils/src/delete_utils/index.d.ts +0 -1
- package/dist/file_utils/src/delete_utils/src/delete_row.d.ts +0 -2
- package/dist/file_utils/src/delete_utils/src/delete_select.d.ts +0 -4
- package/dist/file_utils/src/delete_utils/src/delete_selected.d.ts +0 -2
- package/dist/file_utils/src/delete_utils/src/index.d.ts +0 -3
- package/dist/file_utils/src/download_utils/imports.d.ts +0 -2
- package/dist/file_utils/src/download_utils/index.d.ts +0 -1
- package/dist/file_utils/src/download_utils/src/direct_download.d.ts +0 -3
- package/dist/file_utils/src/download_utils/src/download_file.d.ts +0 -6
- package/dist/file_utils/src/download_utils/src/download_select.d.ts +0 -2
- package/dist/file_utils/src/download_utils/src/index.d.ts +0 -5
- package/dist/file_utils/src/download_utils/src/target_downloads.d.ts +0 -4
- package/dist/file_utils/src/download_utils/src/trigger_download.d.ts +0 -2
- package/dist/file_utils/src/imports.d.ts +0 -2
- package/dist/file_utils/src/index.d.ts +0 -6
- package/dist/file_utils/src/list_utils/imports.d.ts +0 -1
- package/dist/file_utils/src/list_utils/index.d.ts +0 -1
- package/dist/file_utils/src/list_utils/src/index.d.ts +0 -1
- package/dist/file_utils/src/list_utils/src/list_utils.d.ts +0 -2
- package/dist/file_utils/src/select_utils/index.d.ts +0 -1
- package/dist/file_utils/src/select_utils/src/index.d.ts +0 -3
- package/dist/file_utils/src/select_utils/src/init_select_all.d.ts +0 -2
- package/dist/file_utils/src/select_utils/src/sync_header_checkbox.d.ts +0 -5
- package/dist/file_utils/src/select_utils/src/toggle_checkboxes.d.ts +0 -2
- package/dist/file_utils/src/share_utils/imports.d.ts +0 -1
- package/dist/file_utils/src/share_utils/index.d.ts +0 -1
- package/dist/file_utils/src/share_utils/src/index.d.ts +0 -1
- package/dist/file_utils/src/share_utils/src/share_utils.d.ts +0 -7
- package/dist/file_utils/src/upload_utils/imports.d.ts +0 -0
- package/dist/file_utils/src/upload_utils/index.d.ts +0 -1
- package/dist/file_utils/src/upload_utils/src/index.d.ts +0 -3
- package/dist/file_utils/src/upload_utils/src/patch_file_settings.d.ts +0 -2
- package/dist/file_utils/src/upload_utils/src/upload_file.d.ts +0 -2
- package/dist/file_utils/src/upload_utils/src/upload_files.d.ts +0 -5
- package/dist/hooks/imports.d.ts +0 -3
- package/dist/hooks/index.d.ts +0 -1
- package/dist/hooks/useFiles/imports.d.ts +0 -2
- package/dist/hooks/useFiles/index.d.ts +0 -1
- package/dist/hooks/useFiles/src/deleteFile.d.ts +0 -3
- package/dist/hooks/useFiles/src/index.d.ts +0 -6
- package/dist/hooks/useFiles/src/load.d.ts +0 -3
- package/dist/hooks/useFiles/src/patchShareSettings.d.ts +0 -3
- package/dist/hooks/useFiles/src/toggleShare.d.ts +0 -3
- package/dist/hooks/useFiles/src/useFiles.d.ts +0 -13
- package/dist/hooks/useFiles/src/useSaveShareSettings.d.ts +0 -7
- package/dist/imports.d.ts +0 -6
- package/dist/index.css +0 -1
- package/dist/styles/index.d.ts +0 -1
- package/dist/types/index.d.ts +0 -1
- package/dist/types/src/index.d.ts +0 -1
- package/dist/types/src/types.d.ts +0 -75
- package/index.ts +0 -1
- package/postcss.config.cjs +0 -8
- package/rollup.config.js +0 -38
- package/rollup.config.mjs +0 -35
- package/src/App/SecureFiles.tsx +0 -45
- package/src/App/index.ts +0 -1
- package/src/FileHandlers/FileRow/imports.ts +0 -2
- package/src/FileHandlers/FileRow/index.ts +0 -1
- package/src/FileHandlers/FileRow/src/index.ts +0 -1
- package/src/FileHandlers/FileRow/src/row_utils.tsx +0 -143
- package/src/FileHandlers/FileTable/imports.ts +0 -15
- package/src/FileHandlers/FileTable/index.ts +0 -1
- package/src/FileHandlers/FileTable/src/index.ts +0 -1
- package/src/FileHandlers/FileTable/src/table_utils.tsx +0 -251
- package/src/FileHandlers/ShareModal/imports.ts +0 -2
- package/src/FileHandlers/ShareModal/index.ts +0 -1
- package/src/FileHandlers/ShareModal/src/index.ts +0 -1
- package/src/FileHandlers/ShareModal/src/share_utils.tsx +0 -155
- package/src/FileHandlers/Upload/imports.ts +0 -3
- package/src/FileHandlers/Upload/index.ts +0 -1
- package/src/FileHandlers/Upload/src/index.ts +0 -2
- package/src/FileHandlers/Upload/src/upload_utils.tsx +0 -369
- package/src/FileHandlers/imports.ts +0 -17
- package/src/FileHandlers/index.ts +0 -4
- package/src/UI/index.tsx +0 -122
- package/src/custom.d.ts +0 -7
- package/src/file_utils/imports.ts +0 -2
- package/src/file_utils/index.ts +0 -1
- package/src/file_utils/src/cookieFetch.ts +0 -109
- package/src/file_utils/src/delete_utils/imports.ts +0 -1
- package/src/file_utils/src/delete_utils/index.ts +0 -1
- package/src/file_utils/src/delete_utils/src/delete_row.ts +0 -11
- package/src/file_utils/src/delete_utils/src/delete_select.ts +0 -10
- package/src/file_utils/src/delete_utils/src/delete_selected.ts +0 -15
- package/src/file_utils/src/delete_utils/src/index.ts +0 -3
- package/src/file_utils/src/download_utils/imports.ts +0 -3
- package/src/file_utils/src/download_utils/index.ts +0 -1
- package/src/file_utils/src/download_utils/src/direct_download.ts +0 -22
- package/src/file_utils/src/download_utils/src/download_file.ts +0 -25
- package/src/file_utils/src/download_utils/src/download_select.ts +0 -25
- package/src/file_utils/src/download_utils/src/index.ts +0 -5
- package/src/file_utils/src/download_utils/src/target_downloads.ts +0 -21
- package/src/file_utils/src/download_utils/src/trigger_download.ts +0 -14
- package/src/file_utils/src/imports.ts +0 -3
- package/src/file_utils/src/index.ts +0 -6
- package/src/file_utils/src/list_utils/imports.ts +0 -2
- package/src/file_utils/src/list_utils/index.ts +0 -1
- package/src/file_utils/src/list_utils/src/index.ts +0 -1
- package/src/file_utils/src/list_utils/src/list_utils.ts +0 -28
- package/src/file_utils/src/select_utils/index.ts +0 -1
- package/src/file_utils/src/select_utils/src/index.ts +0 -3
- package/src/file_utils/src/select_utils/src/init_select_all.ts +0 -37
- package/src/file_utils/src/select_utils/src/sync_header_checkbox.ts +0 -18
- package/src/file_utils/src/select_utils/src/toggle_checkboxes.ts +0 -11
- package/src/file_utils/src/share_utils/imports.ts +0 -1
- package/src/file_utils/src/share_utils/index.ts +0 -1
- package/src/file_utils/src/share_utils/src/index.ts +0 -1
- package/src/file_utils/src/share_utils/src/share_utils.ts +0 -20
- package/src/file_utils/src/upload_utils/imports.ts +0 -1
- package/src/file_utils/src/upload_utils/index.ts +0 -1
- package/src/file_utils/src/upload_utils/src/index.ts +0 -3
- package/src/file_utils/src/upload_utils/src/patch_file_settings.ts +0 -20
- package/src/file_utils/src/upload_utils/src/upload_file.ts +0 -27
- package/src/file_utils/src/upload_utils/src/upload_files.ts +0 -19
- package/src/hooks/imports.ts +0 -3
- package/src/hooks/index.ts +0 -1
- package/src/hooks/useFiles/imports.ts +0 -3
- package/src/hooks/useFiles/index.ts +0 -1
- package/src/hooks/useFiles/src/deleteFile.ts +0 -23
- package/src/hooks/useFiles/src/index.ts +0 -6
- package/src/hooks/useFiles/src/load.ts +0 -39
- package/src/hooks/useFiles/src/logs/clipit_logs.log +0 -0
- package/src/hooks/useFiles/src/logs/routes_bp.log +0 -0
- package/src/hooks/useFiles/src/patchShareSettings.ts +0 -27
- package/src/hooks/useFiles/src/toggleShare.ts +0 -28
- package/src/hooks/useFiles/src/useFiles.ts +0 -35
- package/src/hooks/useFiles/src/useSaveShareSettings.ts +0 -31
- package/src/imports.ts +0 -18
- package/src/styles/SecureFiles/App.css +0 -42
- package/src/styles/SecureFiles/index.css +0 -10
- package/src/styles/SecureFiles/logs/clipit_logs.log +0 -0
- package/src/styles/SecureFiles/logs/routes_bp.log +0 -0
- package/src/styles/SecureFiles/tailwind.css +0 -3
- package/src/styles/SecureFiles/window-manager.css +0 -283
- package/src/styles/SecureFilesStyles.module.css +0 -287
- package/src/styles/index.ts +0 -1
- package/src/types/index.ts +0 -1
- package/src/types/src/index.ts +0 -1
- package/src/types/src/types.ts +0 -88
- package/tailwind.config.cjs +0 -7
- package/testit.mjs +0 -11
- package/testit.ts +0 -3
- package/tsconfig.json +0 -27
- /package/src/{global.d.ts → css.d.ts} +0 -0
|
@@ -1,369 +0,0 @@
|
|
|
1
|
-
import React, {
|
|
2
|
-
ChangeEvent,
|
|
3
|
-
useRef,
|
|
4
|
-
useState,
|
|
5
|
-
useCallback,
|
|
6
|
-
useEffect,
|
|
7
|
-
} from 'react';
|
|
8
|
-
import {
|
|
9
|
-
CloudArrowUpIcon,
|
|
10
|
-
Button,
|
|
11
|
-
uploadFiles,
|
|
12
|
-
useFiles,
|
|
13
|
-
currentUsername
|
|
14
|
-
} from './../imports';
|
|
15
|
-
import type { QueuedFile, UploadProps } from './../imports';
|
|
16
|
-
|
|
17
|
-
// ── helpers ─────────────────────────────────────────────────────────────────
|
|
18
|
-
|
|
19
|
-
/** Strip filename from a webkitRelativePath, keep the trailing slash. */
|
|
20
|
-
function deriveRelPath(f: File): string {
|
|
21
|
-
const wrp = (f as any).webkitRelativePath as string | undefined;
|
|
22
|
-
if (!wrp) return '';
|
|
23
|
-
const idx = wrp.lastIndexOf('/');
|
|
24
|
-
return idx >= 0 ? wrp.slice(0, idx + 1) : '';
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* Drain a DirectoryReader completely. The browser only returns ~100 entries
|
|
29
|
-
* per readEntries() call; you must call it repeatedly until it returns [].
|
|
30
|
-
*/
|
|
31
|
-
function readAllEntries(reader: any): Promise<any[]> {
|
|
32
|
-
return new Promise((resolve, reject) => {
|
|
33
|
-
const all: any[] = [];
|
|
34
|
-
const pump = () => {
|
|
35
|
-
reader.readEntries(
|
|
36
|
-
(batch: any[]) => {
|
|
37
|
-
if (batch.length === 0) resolve(all);
|
|
38
|
-
else { all.push(...batch); pump(); }
|
|
39
|
-
},
|
|
40
|
-
reject,
|
|
41
|
-
);
|
|
42
|
-
};
|
|
43
|
-
pump();
|
|
44
|
-
});
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
// ── component ───────────────────────────────────────────────────────────────
|
|
48
|
-
|
|
49
|
-
export default function Upload({ onUploaded }: UploadProps) {
|
|
50
|
-
const [queue, setQueue] = useState<QueuedFile[]>([]);
|
|
51
|
-
const [busy, setBusy] = useState(false);
|
|
52
|
-
const [message, setMessage] = useState<string | null>(null);
|
|
53
|
-
const [drag, setDrag] = useState(false);
|
|
54
|
-
|
|
55
|
-
// Two refs: one for plain file picking, one for folder picking.
|
|
56
|
-
const fileInputRef = useRef<HTMLInputElement>(null);
|
|
57
|
-
const dirInputRef = useRef<HTMLInputElement>(null);
|
|
58
|
-
const [pickMode, setPickMode] = useState<'files' | 'folder'>('files');
|
|
59
|
-
|
|
60
|
-
const [dirs, setDirs] = useState<string[]>([]);
|
|
61
|
-
|
|
62
|
-
// — existing files → directory list —
|
|
63
|
-
const { files, load } = useFiles();
|
|
64
|
-
|
|
65
|
-
useEffect(() => {
|
|
66
|
-
const dirSet = new Set<string>();
|
|
67
|
-
files.forEach(f => {
|
|
68
|
-
const parts = f.filepath.split('/');
|
|
69
|
-
if (parts.length > 1) {
|
|
70
|
-
parts.pop();
|
|
71
|
-
dirSet.add(parts.join('/') + '/');
|
|
72
|
-
}
|
|
73
|
-
});
|
|
74
|
-
setDirs(Array.from(dirSet).sort());
|
|
75
|
-
}, [files]);
|
|
76
|
-
|
|
77
|
-
// — directory selection state —
|
|
78
|
-
const [uploadDir, setUploadDir] = useState<string>('');
|
|
79
|
-
const [creatingNew, setCreatingNew] = useState(false);
|
|
80
|
-
const [newDirName, setNewDirName] = useState('');
|
|
81
|
-
|
|
82
|
-
// — queue logic —
|
|
83
|
-
const enqueue = useCallback((incoming: QueuedFile[]) => {
|
|
84
|
-
setQueue(prev => {
|
|
85
|
-
const map = new Map(prev.map(q => [`${q.file.name}-${q.file.size}`, q]));
|
|
86
|
-
incoming.forEach(q => map.set(`${q.file.name}-${q.file.size}`, q));
|
|
87
|
-
return Array.from(map.values());
|
|
88
|
-
});
|
|
89
|
-
}, []);
|
|
90
|
-
|
|
91
|
-
const extractFromDataTransfer = useCallback(
|
|
92
|
-
async (dt: DataTransfer): Promise<QueuedFile[]> => {
|
|
93
|
-
const items = Array.from(dt.items ?? []);
|
|
94
|
-
const hasEntryApi =
|
|
95
|
-
items.length > 0 &&
|
|
96
|
-
typeof (items[0] as any).webkitGetAsEntry === 'function';
|
|
97
|
-
|
|
98
|
-
// Fallback: no entry API → flat files only, no folder traversal.
|
|
99
|
-
if (!hasEntryApi) {
|
|
100
|
-
return Array.from(dt.files ?? []).map(file => ({
|
|
101
|
-
file,
|
|
102
|
-
relPath: '',
|
|
103
|
-
progress: 0,
|
|
104
|
-
}));
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
const out: QueuedFile[] = [];
|
|
108
|
-
|
|
109
|
-
const walk = async (entry: any, path = ''): Promise<void> => {
|
|
110
|
-
if (!entry) return;
|
|
111
|
-
if (entry.isFile) {
|
|
112
|
-
const file: File = await new Promise((res, rej) =>
|
|
113
|
-
entry.file(res, rej),
|
|
114
|
-
);
|
|
115
|
-
out.push({ file, relPath: path, progress: 0 });
|
|
116
|
-
return;
|
|
117
|
-
}
|
|
118
|
-
if (entry.isDirectory) {
|
|
119
|
-
const reader = entry.createReader();
|
|
120
|
-
const entries = await readAllEntries(reader);
|
|
121
|
-
await Promise.all(
|
|
122
|
-
entries.map(e => walk(e, `${path}${entry.name}/`)),
|
|
123
|
-
);
|
|
124
|
-
}
|
|
125
|
-
};
|
|
126
|
-
|
|
127
|
-
await Promise.all(
|
|
128
|
-
items.map(i => walk((i as any).webkitGetAsEntry?.())),
|
|
129
|
-
);
|
|
130
|
-
return out;
|
|
131
|
-
},
|
|
132
|
-
[]
|
|
133
|
-
);
|
|
134
|
-
|
|
135
|
-
const handleSelect = (e: ChangeEvent<HTMLInputElement>) => {
|
|
136
|
-
const picked = Array.from(e.target.files ?? []);
|
|
137
|
-
const queued: QueuedFile[] = picked.map(f => ({
|
|
138
|
-
file: f,
|
|
139
|
-
relPath: deriveRelPath(f),
|
|
140
|
-
progress: 0,
|
|
141
|
-
}));
|
|
142
|
-
enqueue(queued);
|
|
143
|
-
e.target.value = '';
|
|
144
|
-
};
|
|
145
|
-
|
|
146
|
-
const handleDrop = async (e: React.DragEvent) => {
|
|
147
|
-
e.preventDefault();
|
|
148
|
-
e.stopPropagation();
|
|
149
|
-
const dropped = await extractFromDataTransfer(e.dataTransfer);
|
|
150
|
-
if (dropped.length) enqueue(dropped);
|
|
151
|
-
setDrag(false);
|
|
152
|
-
};
|
|
153
|
-
|
|
154
|
-
const openPicker = (e: React.MouseEvent) => {
|
|
155
|
-
// Only fire when the dropzone background itself is clicked, not children.
|
|
156
|
-
if (e.target !== e.currentTarget && !(e.target as HTMLElement).closest('[data-dz-passthrough]')) {
|
|
157
|
-
// children opt in via data-dz-passthrough if they should still open the picker
|
|
158
|
-
}
|
|
159
|
-
const ref = pickMode === 'folder' ? dirInputRef : fileInputRef;
|
|
160
|
-
ref.current?.click();
|
|
161
|
-
};
|
|
162
|
-
|
|
163
|
-
const handleUploadAll = async () => {
|
|
164
|
-
if (!queue.length) {
|
|
165
|
-
setMessage('No files queued.');
|
|
166
|
-
return;
|
|
167
|
-
}
|
|
168
|
-
setBusy(true);
|
|
169
|
-
setMessage(null);
|
|
170
|
-
const targetDir = creatingNew
|
|
171
|
-
? (newDirName.trim().replace(/^\/+|\/+$/g, '') + '/')
|
|
172
|
-
: uploadDir;
|
|
173
|
-
try {
|
|
174
|
-
for (const { file, relPath } of queue) {
|
|
175
|
-
await uploadFiles(file, targetDir + relPath);
|
|
176
|
-
setQueue(cur =>
|
|
177
|
-
cur.map(item =>
|
|
178
|
-
item.file === file ? { ...item, progress: 100 } : item
|
|
179
|
-
)
|
|
180
|
-
);
|
|
181
|
-
}
|
|
182
|
-
setQueue([]);
|
|
183
|
-
await onUploaded(); // SecureFiles passes filesHook.load here
|
|
184
|
-
setMessage('Upload complete');
|
|
185
|
-
// (removed the duplicate load() — onUploaded already does it)
|
|
186
|
-
} catch {
|
|
187
|
-
setMessage('Upload failed');
|
|
188
|
-
} finally {
|
|
189
|
-
setBusy(false);
|
|
190
|
-
}
|
|
191
|
-
};
|
|
192
|
-
|
|
193
|
-
// — directory selector handlers —
|
|
194
|
-
const handleDirChange = (e: ChangeEvent<HTMLSelectElement>) => {
|
|
195
|
-
const v = e.target.value;
|
|
196
|
-
if (v === '__new__') {
|
|
197
|
-
setCreatingNew(true);
|
|
198
|
-
setNewDirName('');
|
|
199
|
-
} else {
|
|
200
|
-
setCreatingNew(false);
|
|
201
|
-
setUploadDir(v);
|
|
202
|
-
}
|
|
203
|
-
};
|
|
204
|
-
const acceptNewDir = () => {
|
|
205
|
-
const cleanName = newDirName.trim().replace(/^\/+|\/+$/g, '');
|
|
206
|
-
if (!cleanName) {
|
|
207
|
-
setCreatingNew(false);
|
|
208
|
-
return;
|
|
209
|
-
}
|
|
210
|
-
const normalized = `${uploadDir}${cleanName}/`;
|
|
211
|
-
setDirs(prev => prev.includes(normalized) ? prev : [...prev, normalized].sort());
|
|
212
|
-
setUploadDir(normalized);
|
|
213
|
-
setCreatingNew(false);
|
|
214
|
-
setNewDirName('');
|
|
215
|
-
};
|
|
216
|
-
|
|
217
|
-
return (
|
|
218
|
-
<div className="form-container max-w-2xl mx-auto">
|
|
219
|
-
{/* pick-mode toggle */}
|
|
220
|
-
<div className="flex gap-4 text-xs text-gray-700 mb-1">
|
|
221
|
-
<label className="flex items-center gap-1">
|
|
222
|
-
<input
|
|
223
|
-
type="radio"
|
|
224
|
-
name="pick-mode"
|
|
225
|
-
checked={pickMode === 'files'}
|
|
226
|
-
onChange={() => setPickMode('files')}
|
|
227
|
-
/>
|
|
228
|
-
Files
|
|
229
|
-
</label>
|
|
230
|
-
<label className="flex items-center gap-1">
|
|
231
|
-
<input
|
|
232
|
-
type="radio"
|
|
233
|
-
name="pick-mode"
|
|
234
|
-
checked={pickMode === 'folder'}
|
|
235
|
-
onChange={() => setPickMode('folder')}
|
|
236
|
-
/>
|
|
237
|
-
Folder
|
|
238
|
-
</label>
|
|
239
|
-
<span className="text-gray-400">
|
|
240
|
-
(drag-and-drop accepts either)
|
|
241
|
-
</span>
|
|
242
|
-
</div>
|
|
243
|
-
|
|
244
|
-
{/* drop area */}
|
|
245
|
-
<div
|
|
246
|
-
className={`flex items-center border-2 border-dashed rounded-lg p-6 bg-white shadow-md space-x-4 transition-shadow duration-150
|
|
247
|
-
${drag ? 'border-indigo-500 bg-indigo-50 shadow-lg' : 'border-gray-300 hover:shadow-lg'}`}
|
|
248
|
-
onClick={openPicker}
|
|
249
|
-
onDragEnter={e => { e.preventDefault(); e.stopPropagation(); setDrag(true); }}
|
|
250
|
-
onDragOver={e => { e.preventDefault(); e.stopPropagation(); }}
|
|
251
|
-
onDragLeave={e => { e.preventDefault(); e.stopPropagation(); setDrag(false); }}
|
|
252
|
-
onDrop={handleDrop}
|
|
253
|
-
role="region"
|
|
254
|
-
aria-label="File upload area"
|
|
255
|
-
>
|
|
256
|
-
<CloudArrowUpIcon
|
|
257
|
-
style={{ width: 300, height: 300 }}
|
|
258
|
-
className="text-gray-500 mb-3"
|
|
259
|
-
aria-hidden="true"
|
|
260
|
-
/>
|
|
261
|
-
<div className="flex-1">
|
|
262
|
-
<p className="text-base font-semibold text-gray-700">
|
|
263
|
-
Drag & drop files or folders here
|
|
264
|
-
</p>
|
|
265
|
-
<p className="text-xs text-gray-500 mt-1">
|
|
266
|
-
Click to {pickMode === 'folder' ? 'choose a folder' : 'choose files'}
|
|
267
|
-
</p>
|
|
268
|
-
{queue.length > 0 && (
|
|
269
|
-
<p className="mt-1 text-sm text-gray-600">
|
|
270
|
-
{queue.length} item{queue.length > 1 && 's'} queued
|
|
271
|
-
</p>
|
|
272
|
-
)}
|
|
273
|
-
</div>
|
|
274
|
-
</div>
|
|
275
|
-
|
|
276
|
-
{/* hidden file input — plain multi-file pick, works everywhere */}
|
|
277
|
-
<input
|
|
278
|
-
ref={fileInputRef}
|
|
279
|
-
type="file"
|
|
280
|
-
multiple
|
|
281
|
-
className="hidden"
|
|
282
|
-
onChange={handleSelect}
|
|
283
|
-
/>
|
|
284
|
-
|
|
285
|
-
{/* hidden directory input — Chromium, Safari 11.1+, Firefox 50+ */}
|
|
286
|
-
<input
|
|
287
|
-
ref={dirInputRef}
|
|
288
|
-
type="file"
|
|
289
|
-
multiple
|
|
290
|
-
className="hidden"
|
|
291
|
-
// non-standard attributes; both forms keep older Firefox happy
|
|
292
|
-
{...({ webkitdirectory: '', directory: '' } as any)}
|
|
293
|
-
onChange={handleSelect}
|
|
294
|
-
/>
|
|
295
|
-
|
|
296
|
-
{/* bottom controls */}
|
|
297
|
-
<div className="flex items-center justify-between mt-4">
|
|
298
|
-
<div className="flex-1 mr-4">
|
|
299
|
-
{!creatingNew ? (
|
|
300
|
-
<select
|
|
301
|
-
value={uploadDir}
|
|
302
|
-
onChange={handleDirChange}
|
|
303
|
-
className="form-select w-full"
|
|
304
|
-
>
|
|
305
|
-
{dirs.map(d => (
|
|
306
|
-
<option key={d} value={d}>{d}</option>
|
|
307
|
-
))}
|
|
308
|
-
<option value="__new__">+ Create new directory…</option>
|
|
309
|
-
</select>
|
|
310
|
-
) : (
|
|
311
|
-
<div className="flex">
|
|
312
|
-
<input
|
|
313
|
-
type="text"
|
|
314
|
-
value={newDirName}
|
|
315
|
-
placeholder="new-folder-name"
|
|
316
|
-
onChange={e => setNewDirName(e.target.value)}
|
|
317
|
-
onBlur={acceptNewDir}
|
|
318
|
-
onKeyDown={e => e.key === 'Enter' && acceptNewDir()}
|
|
319
|
-
className="form-input flex-1"
|
|
320
|
-
/>
|
|
321
|
-
<Button onClick={acceptNewDir} className="ml-2">
|
|
322
|
-
OK
|
|
323
|
-
</Button>
|
|
324
|
-
</div>
|
|
325
|
-
)}
|
|
326
|
-
</div>
|
|
327
|
-
|
|
328
|
-
<Button
|
|
329
|
-
color="blue"
|
|
330
|
-
variant="primary"
|
|
331
|
-
onClick={handleUploadAll}
|
|
332
|
-
disabled={busy || !queue.length}
|
|
333
|
-
>
|
|
334
|
-
{busy ? 'Uploading…' : `Upload ${queue.length}`}
|
|
335
|
-
</Button>
|
|
336
|
-
</div>
|
|
337
|
-
|
|
338
|
-
{message && (
|
|
339
|
-
<p
|
|
340
|
-
className={`text-center text-sm ${message.includes('failed') ? 'text-red-600' : 'text-gray-600'}`}
|
|
341
|
-
role="alert"
|
|
342
|
-
>
|
|
343
|
-
{message}
|
|
344
|
-
</p>
|
|
345
|
-
)}
|
|
346
|
-
|
|
347
|
-
{queue.length > 0 && (
|
|
348
|
-
<div className="wm-container mt-4">
|
|
349
|
-
<h3 className="mb-2 text-sm font-semibold text-gray-700">Queued Files</h3>
|
|
350
|
-
<ul className="max-h-64 space-y-4 overflow-y-auto">
|
|
351
|
-
{queue.map(({ file, relPath, progress }) => (
|
|
352
|
-
<li key={`${file.name}-${file.size}`} className="space-y-1">
|
|
353
|
-
<div className="flex justify-between items-center">
|
|
354
|
-
<span className="truncate text-sm text-gray-800">
|
|
355
|
-
{relPath}{file.name}
|
|
356
|
-
</span>
|
|
357
|
-
<span className="text-xs text-gray-500">{progress}%</span>
|
|
358
|
-
</div>
|
|
359
|
-
<div className="w-full bg-gray-200 rounded-full h-2 overflow-hidden">
|
|
360
|
-
<div className="bg-indigo-600 h-2" style={{ width: `${progress}%` }} />
|
|
361
|
-
</div>
|
|
362
|
-
</li>
|
|
363
|
-
))}
|
|
364
|
-
</ul>
|
|
365
|
-
</div>
|
|
366
|
-
)}
|
|
367
|
-
</div>
|
|
368
|
-
);
|
|
369
|
-
}
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
export type {QueuedFile, FileItem,FileTableProps,UploadProps,ShareModalProps,FileRowProps } from './../imports';
|
|
2
|
-
export {
|
|
3
|
-
CloudArrowUpIcon,
|
|
4
|
-
currentUsername,
|
|
5
|
-
Button,
|
|
6
|
-
Checkbox,
|
|
7
|
-
Input,
|
|
8
|
-
Spinner,
|
|
9
|
-
useEffect,
|
|
10
|
-
useRef,
|
|
11
|
-
useState,
|
|
12
|
-
useCallback,
|
|
13
|
-
ClipboardIcon,
|
|
14
|
-
createPortal,
|
|
15
|
-
} from './../imports';
|
|
16
|
-
export {useFiles} from './../hooks'
|
|
17
|
-
export { downloadFile, uploadFiles } from './../file_utils'
|
package/src/UI/index.tsx
DELETED
|
@@ -1,122 +0,0 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
|
|
3
|
-
export type String = string | undefined | null;
|
|
4
|
-
export type Astring = any[] | string | undefined | null;
|
|
5
|
-
export type Trailing = React.ReactNode
|
|
6
|
-
export type ImportElem = {
|
|
7
|
-
reaxlEle?:React.InputHTMLAttributes<HTMLInputElement>;
|
|
8
|
-
label?: String;
|
|
9
|
-
checked?:boolean;
|
|
10
|
-
type?:string;
|
|
11
|
-
onChange?: any;
|
|
12
|
-
value?: any;
|
|
13
|
-
trailing?:Trailing;
|
|
14
|
-
className?: String;
|
|
15
|
-
classNames?:Astring;
|
|
16
|
-
disabled?: any;
|
|
17
|
-
placeholder?: any;
|
|
18
|
-
error?: string;
|
|
19
|
-
min?:any,
|
|
20
|
-
readOnly?:boolean
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
// Utility to convert input to array
|
|
24
|
-
function ensure_list(obj: Astring | null | undefined): string[] {
|
|
25
|
-
if (Array.isArray(obj)) return obj.filter(Boolean);
|
|
26
|
-
if (typeof obj === 'string') return obj.split(' ').filter(Boolean);
|
|
27
|
-
return [];
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
// Get list and length safely
|
|
31
|
-
function getListAndLen(obj: Astring | null | undefined): { return_obj: string[]; ob_len: number } {
|
|
32
|
-
const return_obj = ensure_list(obj);
|
|
33
|
-
const ob_len = return_obj.length;
|
|
34
|
-
return { return_obj, ob_len };
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
// Combine class names with defaults
|
|
38
|
-
function combineClassNames(classNames: Astring = [], defaultClasses: Astring = ['form-group', 'form-label', 'flex', 'gap-2']): string[] {
|
|
39
|
-
const { return_obj: inputClasses } = getListAndLen(classNames);
|
|
40
|
-
const { return_obj: defaultClassList } = getListAndLen(defaultClasses);
|
|
41
|
-
|
|
42
|
-
// Combine and remove duplicates
|
|
43
|
-
const combined = [...new Set([...defaultClassList, ...inputClasses])];
|
|
44
|
-
|
|
45
|
-
console.log(`Combined classes (length: ${combined.length}): ${combined.join(' ')}`);
|
|
46
|
-
return combined;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
export function Button({
|
|
50
|
-
children,
|
|
51
|
-
color = 'gray',
|
|
52
|
-
variant = 'default',
|
|
53
|
-
className = '',
|
|
54
|
-
...rest
|
|
55
|
-
}: React.ButtonHTMLAttributes<HTMLButtonElement> & {
|
|
56
|
-
color?: 'gray' | 'green' | 'blue';
|
|
57
|
-
variant?: 'default' | 'icon' | 'primary' | 'secondary';
|
|
58
|
-
className?: string;
|
|
59
|
-
}) {
|
|
60
|
-
const variantClasses = {
|
|
61
|
-
default: 'btn',
|
|
62
|
-
icon: 'btn-icon',
|
|
63
|
-
primary: 'btn-primary',
|
|
64
|
-
secondary: 'btn-secondary',
|
|
65
|
-
};
|
|
66
|
-
|
|
67
|
-
const colorClasses = {
|
|
68
|
-
gray: '',
|
|
69
|
-
green: variant === 'primary' ? 'btn-success' : 'btn-secondary',
|
|
70
|
-
blue: variant === 'primary' ? 'btn-primary' : 'btn-secondary',
|
|
71
|
-
};
|
|
72
|
-
|
|
73
|
-
const classes = combineClassNames([variantClasses[variant], colorClasses[color], className]);
|
|
74
|
-
|
|
75
|
-
return (
|
|
76
|
-
<button className={classes.join(' ')} {...rest}>
|
|
77
|
-
{children}
|
|
78
|
-
</button>
|
|
79
|
-
);
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
export function Checkbox({
|
|
83
|
-
label,
|
|
84
|
-
className = '',
|
|
85
|
-
classNames = [],
|
|
86
|
-
...rest
|
|
87
|
-
}: ImportElem) {
|
|
88
|
-
const classes = combineClassNames(classNames, ['checkbox', 'wm-checkbox']);
|
|
89
|
-
|
|
90
|
-
return (
|
|
91
|
-
<label className={classes.join(' ')}>
|
|
92
|
-
<input type="checkbox" className="wm-checkbox" {...rest} />
|
|
93
|
-
{label && <span>{label}</span>}
|
|
94
|
-
</label>
|
|
95
|
-
);
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
export function Input({
|
|
99
|
-
label,
|
|
100
|
-
trailing,
|
|
101
|
-
className = '',
|
|
102
|
-
classNames = [],
|
|
103
|
-
...rest
|
|
104
|
-
}: ImportElem) {
|
|
105
|
-
const classes = combineClassNames(classNames, ['form-group', 'flex', 'gap-2']);
|
|
106
|
-
const inputClasses = combineClassNames(className, ['form-input']);
|
|
107
|
-
|
|
108
|
-
return (
|
|
109
|
-
<label className={classes.join(' ')}>
|
|
110
|
-
{label && <span className="form-label">{label}</span>}
|
|
111
|
-
<div className="flex gap-2">
|
|
112
|
-
<input className={inputClasses.join(' ')} {...rest} />
|
|
113
|
-
{trailing}
|
|
114
|
-
</div>
|
|
115
|
-
</label>
|
|
116
|
-
);
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
export function Spinner() {
|
|
120
|
-
const classes = combineClassNames('animate-pulse');
|
|
121
|
-
return <p className={classes.join(' ')}>Loading…</p>;
|
|
122
|
-
}
|
package/src/custom.d.ts
DELETED
package/src/file_utils/index.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export * from './src';
|
|
@@ -1,109 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Cookie-authenticated API helpers.
|
|
3
|
-
* All requests include credentials: 'include' so the browser sends the
|
|
4
|
-
* session cookie (auth_session) automatically. No JWT tokens are used.
|
|
5
|
-
*
|
|
6
|
-
* API base URL resolution order (first truthy value wins):
|
|
7
|
-
* 1. window.__SECURE_FILES_API_BASE__ – runtime override (any bundler)
|
|
8
|
-
* 2. VITE_SECURE_FILES_API_BASE – Vite env var
|
|
9
|
-
* 3. REACT_APP_SECURE_FILES_API_BASE – Create React App env var
|
|
10
|
-
* 4. /api/secure-files – same-origin default
|
|
11
|
-
*
|
|
12
|
-
* Subdomain API example (set one of the above to the full origin + path):
|
|
13
|
-
* VITE_SECURE_FILES_API_BASE=https://api.example.com/secure-files
|
|
14
|
-
*/
|
|
15
|
-
|
|
16
|
-
function resolveApiBase(): string {
|
|
17
|
-
// 1. Runtime window config — works regardless of bundler
|
|
18
|
-
if (typeof window !== "undefined" && (window as any).__SECURE_FILES_API_BASE__) {
|
|
19
|
-
return String((window as any).__SECURE_FILES_API_BASE__).replace(/\/+$/, "");
|
|
20
|
-
}
|
|
21
|
-
// 2. Vite (import.meta.env is replaced at build time; guard with try for non-Vite builds)
|
|
22
|
-
try {
|
|
23
|
-
const v = (import.meta as any).env?.VITE_SECURE_FILES_API_BASE;
|
|
24
|
-
if (v) return String(v).replace(/\/+$/, "");
|
|
25
|
-
} catch { /* not a Vite build */ }
|
|
26
|
-
// 3. Create React App / plain Node
|
|
27
|
-
try {
|
|
28
|
-
const v = (process as any).env?.REACT_APP_SECURE_FILES_API_BASE;
|
|
29
|
-
if (v) return String(v).replace(/\/+$/, "");
|
|
30
|
-
} catch { /* process not defined */ }
|
|
31
|
-
// 4. Same-origin default
|
|
32
|
-
return "/api/secure-files";
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
const API_BASE = resolveApiBase();
|
|
36
|
-
|
|
37
|
-
function apiUrl(path: string): string {
|
|
38
|
-
const p = path.startsWith("/") ? path : `/${path}`;
|
|
39
|
-
return `${API_BASE}${p}`;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
/**
|
|
43
|
-
* Generic JSON fetch with session-cookie auth.
|
|
44
|
-
* Throws Error("UNAUTHENTICATED") on 401 so callers can redirect to login.
|
|
45
|
-
*/
|
|
46
|
-
export async function cookieFetch<T = unknown>(
|
|
47
|
-
path: string,
|
|
48
|
-
body?: unknown,
|
|
49
|
-
method?: "GET" | "POST" | "PUT" | "PATCH" | "DELETE",
|
|
50
|
-
): Promise<T> {
|
|
51
|
-
const m = method ?? (body !== undefined ? "POST" : "GET");
|
|
52
|
-
const init: RequestInit = { method: m, credentials: "include" };
|
|
53
|
-
|
|
54
|
-
if (body !== undefined && body !== null) {
|
|
55
|
-
init.headers = { "Content-Type": "application/json" };
|
|
56
|
-
init.body = JSON.stringify(body);
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
const res = await fetch(apiUrl(path), init);
|
|
60
|
-
if (res.status === 401) throw new Error("UNAUTHENTICATED");
|
|
61
|
-
if (!res.ok) {
|
|
62
|
-
const msg = await res.text().catch(() => res.statusText);
|
|
63
|
-
throw new Error(`HTTP ${res.status}: ${msg}`);
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
const ct = res.headers.get("Content-Type") ?? "";
|
|
67
|
-
return (ct.includes("application/json") ? res.json() : res.text()) as Promise<T>;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
/**
|
|
71
|
-
* Check whether the current session is valid.
|
|
72
|
-
* Calls /userInfo which requires authentication; returns true if the user
|
|
73
|
-
* is logged in, false otherwise (no exception thrown).
|
|
74
|
-
*/
|
|
75
|
-
export async function checkAuth(): Promise<boolean> {
|
|
76
|
-
try {
|
|
77
|
-
const res = await fetch(apiUrl("/userInfo"), {
|
|
78
|
-
method: "GET",
|
|
79
|
-
credentials: "include",
|
|
80
|
-
});
|
|
81
|
-
return res.ok;
|
|
82
|
-
} catch {
|
|
83
|
-
return false;
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
/**
|
|
88
|
-
* Upload a single file using multipart/form-data with session-cookie auth.
|
|
89
|
-
* @param file The File object to upload.
|
|
90
|
-
* @param relPath Optional relative path within the user's directory.
|
|
91
|
-
*/
|
|
92
|
-
export async function cookieUpload(file: File, relPath = ""): Promise<unknown> {
|
|
93
|
-
const form = new FormData();
|
|
94
|
-
form.append("file", file);
|
|
95
|
-
if (relPath) form.append("rel_path", relPath);
|
|
96
|
-
|
|
97
|
-
const res = await fetch(apiUrl("/upload"), {
|
|
98
|
-
method: "POST",
|
|
99
|
-
credentials: "include",
|
|
100
|
-
body: form,
|
|
101
|
-
});
|
|
102
|
-
|
|
103
|
-
if (res.status === 401) throw new Error("UNAUTHENTICATED");
|
|
104
|
-
if (!res.ok) throw new Error(`Upload failed: HTTP ${res.status}`);
|
|
105
|
-
return res.json();
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
/** The resolved API base URL, exported for debugging / display purposes. */
|
|
109
|
-
export const SECURE_FILES_API_BASE = API_BASE;
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export { requireToken,alertit } from "./../imports";
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export * from './src';
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
|
|
2
|
-
import {removeFile} from './delete_select'
|
|
3
|
-
import {alertit} from './../imports'
|
|
4
|
-
/** Given a checkbox inside a table-row → download that row’s file. */
|
|
5
|
-
export async function removeRow(cb: HTMLInputElement): Promise<void> {
|
|
6
|
-
const row = cb.closest("tr") as HTMLTableRowElement | null;
|
|
7
|
-
alertit(row)
|
|
8
|
-
if (row && row?.id) await removeFile(row);
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import { cookieFetch } from '../../cookieFetch';
|
|
2
|
-
|
|
3
|
-
export async function removeFile(
|
|
4
|
-
item: { id?: number | string; filename?: string },
|
|
5
|
-
): Promise<void> {
|
|
6
|
-
const resp = await cookieFetch<unknown>("remove", { id: item.id }, "POST");
|
|
7
|
-
if (!resp || (resp as any) === false || (resp as any)?.result === false) {
|
|
8
|
-
throw new Error(`Could not delete "${item.filename ?? item.id}"`);
|
|
9
|
-
}
|
|
10
|
-
}
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import {removeFile} from './delete_select';
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
/** Bulk download: pass a NodeList or array of checkboxes. */
|
|
5
|
-
export async function removeSelected(checkboxes: Iterable<HTMLInputElement>): Promise<void> {
|
|
6
|
-
const list = Array.from(checkboxes);
|
|
7
|
-
if (!list.length) {
|
|
8
|
-
alert("No files selected.");
|
|
9
|
-
return;
|
|
10
|
-
}
|
|
11
|
-
for (const cb of list) {
|
|
12
|
-
alert(cb);
|
|
13
|
-
await removeFile(cb);
|
|
14
|
-
}
|
|
15
|
-
}
|