@putkoff/abstract-securefiles 0.0.19 → 0.0.21
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/{secure_files.d.ts → SecureFiles.d.ts} +2 -2
- 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 +928 -9
- package/dist/index.cjs.map +1 -1
- package/dist/index.css +1 -1
- package/dist/index.d.ts +7 -1
- package/dist/index.js +882 -13
- 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 +7 -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/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
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
import type { FileItem, FileTableProps } from './../imports';
|
|
2
|
+
import React, { useState, useMemo, ChangeEvent } from 'react';
|
|
3
|
+
import {
|
|
4
|
+
useFiles,
|
|
5
|
+
downloadFile,
|
|
6
|
+
Spinner,
|
|
7
|
+
ShareModal,
|
|
8
|
+
Input,
|
|
9
|
+
Button,
|
|
10
|
+
currentUsername
|
|
11
|
+
} from './../imports';
|
|
12
|
+
import { downloadSelected } from './../../../file_utils';
|
|
13
|
+
|
|
14
|
+
// Tree node type
|
|
15
|
+
interface FileNode {
|
|
16
|
+
name: string;
|
|
17
|
+
path: string;
|
|
18
|
+
children: Map<string, FileNode>;
|
|
19
|
+
files: FileItem[];
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// Build a directory tree from a flat file list
|
|
23
|
+
function buildTree(files: FileItem[]): FileNode {
|
|
24
|
+
const root: FileNode = { name: '', path: '', children: new Map(), files: [] };
|
|
25
|
+
files.forEach(file => {
|
|
26
|
+
const parts = file.filepath.split('/');
|
|
27
|
+
let node = root;
|
|
28
|
+
parts.slice(0, -1).forEach((seg, idx) => {
|
|
29
|
+
const subPath = parts.slice(0, idx + 1).join('/');
|
|
30
|
+
if (!node.children.has(seg)) {
|
|
31
|
+
node.children.set(seg, { name: seg, path: subPath, children: new Map(), files: [] });
|
|
32
|
+
}
|
|
33
|
+
node = node.children.get(seg)!;
|
|
34
|
+
});
|
|
35
|
+
node.files.push(file);
|
|
36
|
+
});
|
|
37
|
+
return root;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Collect all file IDs under a node (recursive)
|
|
41
|
+
function collectNodeIds(node: FileNode): Array<number | string> {
|
|
42
|
+
let ids: Array<number | string> = [];
|
|
43
|
+
node.files.forEach(f => ids.push(f.id));
|
|
44
|
+
node.children.forEach(child => {
|
|
45
|
+
ids = ids.concat(collectNodeIds(child));
|
|
46
|
+
});
|
|
47
|
+
return ids;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export default function FileTable({
|
|
51
|
+
files,
|
|
52
|
+
loading,
|
|
53
|
+
error,
|
|
54
|
+
onToggleShare,
|
|
55
|
+
onPatchShare,
|
|
56
|
+
onDelete
|
|
57
|
+
}: FileTableProps) {
|
|
58
|
+
const { load } = useFiles();
|
|
59
|
+
const [search, setSearch] = useState('');
|
|
60
|
+
const [modalFile, setModalFile] = useState<FileItem | null>(null);
|
|
61
|
+
const [selected, setSelected] = useState<Set<number | string>>(new Set());
|
|
62
|
+
const [expanded, setExpanded] = useState<Set<string>>(new Set());
|
|
63
|
+
|
|
64
|
+
// Upload controls
|
|
65
|
+
const [uploadDir, setUploadDir] = useState<string>('');
|
|
66
|
+
const [uploadFile, setUploadFile] = useState<File | null>(null);
|
|
67
|
+
const handleFileChange = (e: ChangeEvent<HTMLInputElement>) => setUploadFile(e.target.files?.[0] ?? null);
|
|
68
|
+
const handleUpload = async () => {
|
|
69
|
+
if (!uploadFile) return;
|
|
70
|
+
const form = new FormData();
|
|
71
|
+
form.append('file', uploadFile);
|
|
72
|
+
const dirPart = uploadDir ? `${uploadDir}/` : '';
|
|
73
|
+
const url = `/api/secure-files/upload/${dirPart}${encodeURIComponent(uploadFile.name)}`;
|
|
74
|
+
await fetch(url, { method: 'POST', credentials: 'include', body: form });
|
|
75
|
+
setUploadFile(null);
|
|
76
|
+
load();
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
// Filter files by search term
|
|
80
|
+
const filtered = useMemo(
|
|
81
|
+
() => files.filter(f => f.filename.toLowerCase().includes(search.trim().toLowerCase())),
|
|
82
|
+
[files, search]
|
|
83
|
+
);
|
|
84
|
+
// Build tree and directory paths list
|
|
85
|
+
const tree = useMemo(() => buildTree(filtered), [filtered]);
|
|
86
|
+
const allDirPaths = useMemo(() => {
|
|
87
|
+
const paths: string[] = [''];
|
|
88
|
+
function collect(node: FileNode) {
|
|
89
|
+
node.children.forEach(child => {
|
|
90
|
+
paths.push(child.path);
|
|
91
|
+
collect(child);
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
collect(tree);
|
|
95
|
+
return paths;
|
|
96
|
+
}, [tree]);
|
|
97
|
+
|
|
98
|
+
// Selection & expand/collapse logic
|
|
99
|
+
const allIds = filtered.map(f => f.id);
|
|
100
|
+
const toggleAllSelection = () => selected.size < allIds.length ? setSelected(new Set(allIds)) : setSelected(new Set());
|
|
101
|
+
const toggleDirOpen = (path: string) => setExpanded(prev => {
|
|
102
|
+
const next = new Set(prev);
|
|
103
|
+
next.has(path) ? next.delete(path) : next.add(path);
|
|
104
|
+
return next;
|
|
105
|
+
});
|
|
106
|
+
const expandAll = () => setExpanded(new Set(allDirPaths));
|
|
107
|
+
const collapseAll = () => setExpanded(new Set());
|
|
108
|
+
|
|
109
|
+
// Recursive renderer: directory row with select-all checkbox, then its files, then subdirectories
|
|
110
|
+
const renderNode = (node: FileNode, depth = 0): React.ReactNode[] => {
|
|
111
|
+
const rows: React.ReactNode[] = [];
|
|
112
|
+
if (node.path) {
|
|
113
|
+
const isOpen = expanded.has(node.path);
|
|
114
|
+
const nodeIds = collectNodeIds(node);
|
|
115
|
+
const allSelected = nodeIds.every(id => selected.has(id));
|
|
116
|
+
const someSelected = nodeIds.some(id => selected.has(id));
|
|
117
|
+
rows.push(
|
|
118
|
+
<tr key={node.path}>
|
|
119
|
+
{/* Directory-level checkbox */}
|
|
120
|
+
<td className="px-4 py-2 bg-gray-100">
|
|
121
|
+
<input
|
|
122
|
+
type="checkbox"
|
|
123
|
+
checked={allSelected}
|
|
124
|
+
ref={el => { if (el) el.indeterminate = !allSelected && someSelected; }}
|
|
125
|
+
onChange={() => {
|
|
126
|
+
setSelected(prev => {
|
|
127
|
+
const next = new Set(prev);
|
|
128
|
+
if (allSelected) {
|
|
129
|
+
nodeIds.forEach(id => next.delete(id));
|
|
130
|
+
} else {
|
|
131
|
+
nodeIds.forEach(id => next.add(id));
|
|
132
|
+
}
|
|
133
|
+
return next;
|
|
134
|
+
});
|
|
135
|
+
}}
|
|
136
|
+
/>
|
|
137
|
+
</td>
|
|
138
|
+
{/* Directory label pushed left */}
|
|
139
|
+
<td className="px-4 py-2 bg-gray-100 font-bold" colSpan={5} style={{ textAlign: 'left' }}>
|
|
140
|
+
{node.path}
|
|
141
|
+
</td>
|
|
142
|
+
{/* Expand/collapse ticker on right */}
|
|
143
|
+
<td className="px-4 py-2 bg-gray-100 text-right">
|
|
144
|
+
<button onClick={() => toggleDirOpen(node.path)}>
|
|
145
|
+
{isOpen ? '▾' : '▸'}
|
|
146
|
+
</button>
|
|
147
|
+
</td>
|
|
148
|
+
</tr>
|
|
149
|
+
);
|
|
150
|
+
if (!isOpen) return rows;
|
|
151
|
+
}
|
|
152
|
+
// Render files in this directory
|
|
153
|
+
node.files.sort((a, b) => a.filename.localeCompare(b.filename)).forEach(f => {
|
|
154
|
+
const isOwner = f.uploader_id.toLowerCase() === (currentUsername() ?? '').toLowerCase();
|
|
155
|
+
const canDownload = isOwner || f.shareable;
|
|
156
|
+
rows.push(
|
|
157
|
+
<tr key={f.id}>
|
|
158
|
+
<td className="px-4 py-2">
|
|
159
|
+
<input
|
|
160
|
+
type="checkbox"
|
|
161
|
+
checked={selected.has(f.id)}
|
|
162
|
+
onChange={() => setSelected(prev => {
|
|
163
|
+
const next = new Set(prev);
|
|
164
|
+
next.has(f.id) ? next.delete(f.id) : next.add(f.id);
|
|
165
|
+
return next;
|
|
166
|
+
})}
|
|
167
|
+
/>
|
|
168
|
+
</td>
|
|
169
|
+
<td className="px-4 py-2" style={{ paddingLeft: (depth + 1) * 20 }}>
|
|
170
|
+
{f.filename}
|
|
171
|
+
</td>
|
|
172
|
+
<td className="px-4 py-2">
|
|
173
|
+
{canDownload ? <Button variant="secondary" onClick={() => downloadFile(f)}>Download</Button> : <span>🔒</span>}
|
|
174
|
+
</td>
|
|
175
|
+
<td className="px-4 py-2">
|
|
176
|
+
{isOwner && <Button variant="icon" onClick={() => setModalFile(f)}>⚙️</Button>}
|
|
177
|
+
</td>
|
|
178
|
+
<td className="px-4 py-2">
|
|
179
|
+
{isOwner && <Button variant={f.shareable ? 'primary' : 'secondary'} onClick={() => onToggleShare(f)}>{f.shareable ? 'Sharing' : 'Share'}</Button>}
|
|
180
|
+
</td>
|
|
181
|
+
<td className="px-4 py-2">
|
|
182
|
+
{isOwner && <Button variant="icon" onClick={() => onDelete(f).then(load)}>🗑</Button>}
|
|
183
|
+
</td>
|
|
184
|
+
<td className="px-4 py-2">
|
|
185
|
+
{f.download_count} / {f.download_limit ?? '∞'}
|
|
186
|
+
</td>
|
|
187
|
+
</tr>
|
|
188
|
+
);
|
|
189
|
+
});
|
|
190
|
+
// Render subdirectories
|
|
191
|
+
Array.from(node.children.values())
|
|
192
|
+
.sort((a, b) => a.name.localeCompare(b.name))
|
|
193
|
+
.forEach(child => rows.push(...renderNode(child, depth + 1)));
|
|
194
|
+
return rows;
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
// Batch actions
|
|
198
|
+
const handleDownloadAll = async () => {
|
|
199
|
+
const inputs = filtered.filter(f => selected.has(f.id)).map(f => document.createElement('input'));
|
|
200
|
+
inputs.forEach((cb, idx) => cb.value = filtered.filter(f => selected.has(f.id))[idx].filepath);
|
|
201
|
+
await downloadSelected(inputs);
|
|
202
|
+
};
|
|
203
|
+
const handleDeleteAll = async () => { await onDelete(filtered.filter(f => selected.has(f.id))); setSelected(new Set()); await load(); };
|
|
204
|
+
const handleShareAll = async () => { for (const f of filtered.filter(f => selected.has(f.id))) await onToggleShare(f); };
|
|
205
|
+
|
|
206
|
+
if (loading) return <Spinner />;
|
|
207
|
+
if (error) return <p className="text-red-600">{error.message}</p>;
|
|
208
|
+
|
|
209
|
+
return (
|
|
210
|
+
<>
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
{/* Search & actions */}
|
|
214
|
+
<div className="mb-4 flex flex-col md:flex-row md:justify-between gap-4">
|
|
215
|
+
<Input label="Search files" type="text" value={search} onChange={e => setSearch(e.target.value)} placeholder="Enter filename…" className="form-input flex-1" />
|
|
216
|
+
<div className="flex gap-2">
|
|
217
|
+
<Button onClick={expandAll} disabled={!allDirPaths.length}>Expand All</Button>
|
|
218
|
+
<Button onClick={collapseAll} disabled={!allDirPaths.length}>Collapse All</Button>
|
|
219
|
+
<Button onClick={handleDownloadAll} disabled={!selected.size}>Download Selected</Button>
|
|
220
|
+
<Button color="gray" onClick={handleDeleteAll} disabled={!selected.size}>Delete Selected</Button>
|
|
221
|
+
<Button onClick={handleShareAll} disabled={!selected.size}>Share Selected</Button>
|
|
222
|
+
</div>
|
|
223
|
+
</div>
|
|
224
|
+
|
|
225
|
+
{/* File tree table */}
|
|
226
|
+
<div className="overflow-x-auto wm-container">
|
|
227
|
+
<table className="file-table w-full">
|
|
228
|
+
<thead className="wm-thead">
|
|
229
|
+
<tr>
|
|
230
|
+
<th className="px-4 py-2 w-12"><input type="checkbox" checked={selected.size === filtered.length && filtered.length > 0} onChange={toggleAllSelection} /></th>
|
|
231
|
+
<th className="px-4 py-2">Filename</th>
|
|
232
|
+
<th className="px-4 py-2">Download</th>
|
|
233
|
+
<th className="px-4 py-2">Settings</th>
|
|
234
|
+
<th className="px-4 py-2">Share</th>
|
|
235
|
+
<th className="px-4 py-2">Delete</th>
|
|
236
|
+
<th className="px-4 py-2">Downloads</th>
|
|
237
|
+
</tr>
|
|
238
|
+
</thead>
|
|
239
|
+
<tbody>{renderNode(tree)}</tbody>
|
|
240
|
+
</table>
|
|
241
|
+
</div>
|
|
242
|
+
|
|
243
|
+
<ShareModal file={modalFile} onClose={() => setModalFile(null)} onSave={async patch => {
|
|
244
|
+
if (!modalFile) return;
|
|
245
|
+
const updated: FileItem = { ...modalFile, ...patch };
|
|
246
|
+
await onPatchShare(updated);
|
|
247
|
+
setModalFile(null);
|
|
248
|
+
}} />
|
|
249
|
+
</>
|
|
250
|
+
);
|
|
251
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {ShareModal} from './src';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {default as ShareModal} from './share_utils';
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
ShareModalProps
|
|
3
|
+
} from './../../imports';
|
|
4
|
+
import {
|
|
5
|
+
Button,
|
|
6
|
+
Checkbox,
|
|
7
|
+
Input,
|
|
8
|
+
ClipboardIcon,
|
|
9
|
+
createPortal,
|
|
10
|
+
useEffect,
|
|
11
|
+
useRef,
|
|
12
|
+
useState,
|
|
13
|
+
} from './../imports';
|
|
14
|
+
import { alertit } from '@putkoff/abstract-utilities'
|
|
15
|
+
|
|
16
|
+
export default function ShareModal({ file, onClose, onSave }: ShareModalProps) {
|
|
17
|
+
const [fileId, setFileId] = useState<string | number | "">("");
|
|
18
|
+
const [enabled, setEnabled] = useState<any>(false);
|
|
19
|
+
const [password, setPassword] = useState<any>(undefined);
|
|
20
|
+
const [strPassword, setStrPassword] = useState<any>("");
|
|
21
|
+
const [download_limit, setDownloadLimit] = useState<number | "">(0);
|
|
22
|
+
const [error, setError] = useState("");
|
|
23
|
+
const [busy, setBusy] = useState(false);
|
|
24
|
+
const modalRef = useRef<HTMLDivElement>(null);
|
|
25
|
+
|
|
26
|
+
const shareLink =
|
|
27
|
+
file && enabled
|
|
28
|
+
? `https://abstractendeavors.com/api/secure-files/secure-download/${file.id}`
|
|
29
|
+
: "";
|
|
30
|
+
|
|
31
|
+
useEffect(() => {
|
|
32
|
+
if (!file) return;
|
|
33
|
+
setFileId(file.id);
|
|
34
|
+
setEnabled(file.shareable);
|
|
35
|
+
// load both values from the `file` prop
|
|
36
|
+
setPassword(file.share_password);
|
|
37
|
+
setStrPassword(file.password_str ?? "");
|
|
38
|
+
setDownloadLimit(file.download_limit ?? 0);
|
|
39
|
+
setError("");
|
|
40
|
+
|
|
41
|
+
modalRef.current?.focus();
|
|
42
|
+
}, [file]);
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
useEffect(() => {
|
|
46
|
+
const h = (e: KeyboardEvent) => e.key === "Escape" && onClose();
|
|
47
|
+
window.addEventListener("keydown", h);
|
|
48
|
+
return () => window.removeEventListener("keydown", h);
|
|
49
|
+
}, [onClose]);
|
|
50
|
+
|
|
51
|
+
if (!file) return null;
|
|
52
|
+
|
|
53
|
+
async function handleSave() {
|
|
54
|
+
if (strPassword && strPassword.length < 4) {
|
|
55
|
+
setError("Password must be at least 4 characters");
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
setBusy(true);
|
|
59
|
+
const save_dict = {
|
|
60
|
+
id: fileId,
|
|
61
|
+
shareable: enabled,
|
|
62
|
+
needsPassword: !!strPassword,
|
|
63
|
+
share_password: strPassword || null,
|
|
64
|
+
password_str: strPassword || null,
|
|
65
|
+
download_limit: download_limit === "" ? null : Number(download_limit),
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
try {
|
|
69
|
+
const resp = await onSave(save_dict);
|
|
70
|
+
|
|
71
|
+
onClose();
|
|
72
|
+
} catch (err) {
|
|
73
|
+
setError(`${err} Failed to save settings. Please try again.`);
|
|
74
|
+
} finally {
|
|
75
|
+
setBusy(false);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return createPortal(
|
|
80
|
+
<>
|
|
81
|
+
<div
|
|
82
|
+
className="modal-backdrop z-100"
|
|
83
|
+
onClick={(e) => e.target === e.currentTarget && onClose()}
|
|
84
|
+
/>
|
|
85
|
+
<div className="modal-wrapper z-110">
|
|
86
|
+
<div
|
|
87
|
+
ref={modalRef}
|
|
88
|
+
role="dialog"
|
|
89
|
+
aria-modal="true"
|
|
90
|
+
tabIndex={-1}
|
|
91
|
+
className="modal-panel"
|
|
92
|
+
>
|
|
93
|
+
<h3 className="form-title">
|
|
94
|
+
Share settings for <span className="font-mono">{file.filename}</span>
|
|
95
|
+
</h3>
|
|
96
|
+
|
|
97
|
+
{error && <p className="form-error">{error}</p>}
|
|
98
|
+
|
|
99
|
+
<Checkbox
|
|
100
|
+
label="Enable sharing"
|
|
101
|
+
checked={enabled}
|
|
102
|
+
onChange={(e) => setEnabled(e.target.checked)}
|
|
103
|
+
className="checkbox"
|
|
104
|
+
/>
|
|
105
|
+
|
|
106
|
+
{/* This is the admin-editable plain-text password */}
|
|
107
|
+
<Input
|
|
108
|
+
label="Password (optional)"
|
|
109
|
+
type="text"
|
|
110
|
+
value={strPassword}
|
|
111
|
+
onChange={(e) => setStrPassword(e.target.value)}
|
|
112
|
+
disabled={!enabled}
|
|
113
|
+
className="form-input"
|
|
114
|
+
/>
|
|
115
|
+
<Input
|
|
116
|
+
label="Download limit (0 = ∞)"
|
|
117
|
+
type="number"
|
|
118
|
+
min={0}
|
|
119
|
+
value={download_limit}
|
|
120
|
+
onChange={(e) =>
|
|
121
|
+
setDownloadLimit(e.target.value === "" ? "" : Number(e.target.value))
|
|
122
|
+
}
|
|
123
|
+
disabled={!enabled}
|
|
124
|
+
className="form-input"
|
|
125
|
+
/>
|
|
126
|
+
|
|
127
|
+
{enabled && shareLink && (
|
|
128
|
+
<Input
|
|
129
|
+
label="Share link"
|
|
130
|
+
readOnly
|
|
131
|
+
value={shareLink}
|
|
132
|
+
className="form-input"
|
|
133
|
+
trailing={
|
|
134
|
+
<Button
|
|
135
|
+
variant="icon"
|
|
136
|
+
className="btn-icon"
|
|
137
|
+
onClick={() => navigator.clipboard.writeText(shareLink)}
|
|
138
|
+
>
|
|
139
|
+
<ClipboardIcon className="h-5 w-5" />
|
|
140
|
+
</Button>
|
|
141
|
+
}
|
|
142
|
+
/>
|
|
143
|
+
)}
|
|
144
|
+
|
|
145
|
+
<div className="mt-6 flex justify-end gap-2">
|
|
146
|
+
<Button color="green" variant="primary" onClick={handleSave} disabled={busy} className="btn-primary">
|
|
147
|
+
{busy ? "Saving…" : "Save"}
|
|
148
|
+
</Button>
|
|
149
|
+
<Button variant="secondary" onClick={onClose} disabled={busy} className="btn-secondary">
|
|
150
|
+
Cancel
|
|
151
|
+
</Button>
|
|
152
|
+
</div>
|
|
153
|
+
</div>
|
|
154
|
+
</div>
|
|
155
|
+
</>,
|
|
156
|
+
document.body
|
|
157
|
+
);
|
|
158
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {Upload} from './src';
|