@putkoff/abstract-securefiles 0.0.48 → 0.0.50
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 +47 -0
- package/dist/index.cjs +529 -1004
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +4 -8
- package/dist/index.js +522 -951
- package/dist/index.js.map +1 -1
- package/dist/types.d.ts +60 -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 +346 -0
- package/src/ServerFiles.tsx +339 -0
- package/src/ShareDialog.tsx +148 -0
- package/src/UploadPanel.tsx +217 -0
- package/src/api.ts +108 -0
- package/src/index.ts +4 -8
- package/src/styles.css +442 -0
- package/src/types.ts +56 -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
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
import React, { useRef, useState } from "react";
|
|
2
|
+
import type { SecureFilesApi } from "./api";
|
|
3
|
+
import { FolderSelect } from "./FolderSelect";
|
|
4
|
+
|
|
5
|
+
interface Queued {
|
|
6
|
+
file: File;
|
|
7
|
+
relPath: string;
|
|
8
|
+
progress: number;
|
|
9
|
+
error?: string;
|
|
10
|
+
done?: boolean;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function readEntries(reader: any): Promise<any[]> {
|
|
14
|
+
return new Promise((resolve, reject) => reader.readEntries(resolve, reject));
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
async function walkEntry(entry: any, prefix: string, out: { file: File; relPath: string }[]) {
|
|
18
|
+
if (entry.isFile) {
|
|
19
|
+
const file: File = await new Promise((resolve, reject) => entry.file(resolve, reject));
|
|
20
|
+
out.push({ file, relPath: prefix + file.name });
|
|
21
|
+
} else if (entry.isDirectory) {
|
|
22
|
+
const reader = entry.createReader();
|
|
23
|
+
let batch: any[];
|
|
24
|
+
do {
|
|
25
|
+
batch = await readEntries(reader);
|
|
26
|
+
for (const child of batch) await walkEntry(child, `${prefix}${entry.name}/`, out);
|
|
27
|
+
} while (batch.length > 0);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
async function filesFromDrop(dt: DataTransfer) {
|
|
32
|
+
const out: { file: File; relPath: string }[] = [];
|
|
33
|
+
const entries = Array.from(dt.items || [])
|
|
34
|
+
.map((item) => (item as any).webkitGetAsEntry?.())
|
|
35
|
+
.filter(Boolean);
|
|
36
|
+
if (entries.length) {
|
|
37
|
+
for (const entry of entries) await walkEntry(entry, "", out);
|
|
38
|
+
} else {
|
|
39
|
+
for (const file of Array.from(dt.files)) out.push({ file, relPath: file.name });
|
|
40
|
+
}
|
|
41
|
+
return out;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function joinTarget(target: string, relPath: string) {
|
|
45
|
+
const clean = target.trim().replace(/^\/+|\/+$/g, "");
|
|
46
|
+
return clean ? `${clean}/${relPath}` : relPath;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function CloudIcon() {
|
|
50
|
+
return (
|
|
51
|
+
<svg className="sf-drop__icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={1.5} aria-hidden="true">
|
|
52
|
+
<path
|
|
53
|
+
strokeLinecap="round"
|
|
54
|
+
strokeLinejoin="round"
|
|
55
|
+
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"
|
|
56
|
+
/>
|
|
57
|
+
</svg>
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function UploadPanel({
|
|
62
|
+
api,
|
|
63
|
+
folders,
|
|
64
|
+
onUploaded,
|
|
65
|
+
}: {
|
|
66
|
+
api: SecureFilesApi;
|
|
67
|
+
folders: string[];
|
|
68
|
+
onUploaded: () => void;
|
|
69
|
+
}) {
|
|
70
|
+
const [queue, setQueue] = useState<Queued[]>([]);
|
|
71
|
+
const [mode, setMode] = useState<"files" | "folder">("files");
|
|
72
|
+
const [target, setTarget] = useState("");
|
|
73
|
+
const [dragging, setDragging] = useState(false);
|
|
74
|
+
const [busy, setBusy] = useState(false);
|
|
75
|
+
const [message, setMessage] = useState<string | null>(null);
|
|
76
|
+
const fileInput = useRef<HTMLInputElement>(null);
|
|
77
|
+
const folderInput = useRef<HTMLInputElement>(null);
|
|
78
|
+
|
|
79
|
+
const enqueue = (items: { file: File; relPath: string }[]) => {
|
|
80
|
+
setMessage(null);
|
|
81
|
+
setQueue((q) => {
|
|
82
|
+
const kept = q.filter((x) => !x.done);
|
|
83
|
+
const seen = new Set(kept.map((x) => x.relPath));
|
|
84
|
+
return [...kept, ...items.filter((i) => !seen.has(i.relPath)).map((i) => ({ ...i, progress: 0 }))];
|
|
85
|
+
});
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
const openPicker = () => (mode === "folder" ? folderInput : fileInput).current?.click();
|
|
89
|
+
|
|
90
|
+
const fromInput = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
91
|
+
const files = Array.from(e.target.files || []);
|
|
92
|
+
enqueue(files.map((file) => ({ file, relPath: (file as any).webkitRelativePath || file.name })));
|
|
93
|
+
e.target.value = "";
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
const uploadAll = async () => {
|
|
97
|
+
setBusy(true);
|
|
98
|
+
setMessage(null);
|
|
99
|
+
let failed = 0;
|
|
100
|
+
for (let i = 0; i < queue.length; i++) {
|
|
101
|
+
const item = queue[i];
|
|
102
|
+
if (item.done) continue;
|
|
103
|
+
const set = (patch: Partial<Queued>) =>
|
|
104
|
+
setQueue((q) => q.map((x, idx) => (idx === i ? { ...x, ...patch } : x)));
|
|
105
|
+
try {
|
|
106
|
+
await api.upload(item.file, joinTarget(target, item.relPath), (p) => set({ progress: p }));
|
|
107
|
+
set({ progress: 1, done: true, error: undefined });
|
|
108
|
+
} catch (err) {
|
|
109
|
+
failed++;
|
|
110
|
+
set({ error: err instanceof Error ? err.message : String(err) });
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
setBusy(false);
|
|
114
|
+
setMessage(failed ? `${failed} upload(s) failed` : "Upload complete");
|
|
115
|
+
if (!failed) setQueue([]);
|
|
116
|
+
onUploaded();
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
const pending = queue.filter((q) => !q.done);
|
|
120
|
+
|
|
121
|
+
return (
|
|
122
|
+
<section className="sf-upload">
|
|
123
|
+
<div className="sf-row sf-mode" role="radiogroup" aria-label="Choose what clicking the upload area picks">
|
|
124
|
+
<label className="sf-check sf-check--inline">
|
|
125
|
+
<input type="radio" checked={mode === "files"} onChange={() => setMode("files")} /> Files
|
|
126
|
+
</label>
|
|
127
|
+
<label className="sf-check sf-check--inline">
|
|
128
|
+
<input type="radio" checked={mode === "folder"} onChange={() => setMode("folder")} /> Folder
|
|
129
|
+
</label>
|
|
130
|
+
<span className="sf-muted">(drag-and-drop accepts either)</span>
|
|
131
|
+
</div>
|
|
132
|
+
|
|
133
|
+
<div
|
|
134
|
+
className={`sf-drop${dragging ? " sf-drop--active" : ""}`}
|
|
135
|
+
role="button"
|
|
136
|
+
tabIndex={0}
|
|
137
|
+
aria-label={`Upload area: click to choose ${mode === "folder" ? "a folder" : "files"}, or drop files or folders`}
|
|
138
|
+
onClick={openPicker}
|
|
139
|
+
onKeyDown={(e) => {
|
|
140
|
+
if (e.key === "Enter" || e.key === " ") {
|
|
141
|
+
e.preventDefault();
|
|
142
|
+
openPicker();
|
|
143
|
+
}
|
|
144
|
+
}}
|
|
145
|
+
onDragEnter={(e) => {
|
|
146
|
+
e.preventDefault();
|
|
147
|
+
setDragging(true);
|
|
148
|
+
}}
|
|
149
|
+
onDragOver={(e) => e.preventDefault()}
|
|
150
|
+
onDragLeave={(e) => {
|
|
151
|
+
if (!e.currentTarget.contains(e.relatedTarget as Node)) setDragging(false);
|
|
152
|
+
}}
|
|
153
|
+
onDrop={async (e) => {
|
|
154
|
+
e.preventDefault();
|
|
155
|
+
setDragging(false);
|
|
156
|
+
enqueue(await filesFromDrop(e.dataTransfer));
|
|
157
|
+
}}
|
|
158
|
+
>
|
|
159
|
+
<CloudIcon />
|
|
160
|
+
<div>
|
|
161
|
+
<p className="sf-drop__title">Drag & drop files or folders here</p>
|
|
162
|
+
<p className="sf-muted">Click anywhere to choose {mode === "folder" ? "a folder" : "files"}</p>
|
|
163
|
+
{pending.length > 0 && (
|
|
164
|
+
<p className="sf-drop__count">
|
|
165
|
+
{pending.length} item{pending.length === 1 ? "" : "s"} queued
|
|
166
|
+
</p>
|
|
167
|
+
)}
|
|
168
|
+
</div>
|
|
169
|
+
</div>
|
|
170
|
+
<input ref={fileInput} type="file" multiple hidden onChange={fromInput} />
|
|
171
|
+
<input
|
|
172
|
+
ref={folderInput}
|
|
173
|
+
type="file"
|
|
174
|
+
multiple
|
|
175
|
+
hidden
|
|
176
|
+
onChange={fromInput}
|
|
177
|
+
{...({ webkitdirectory: "", directory: "" } as any)}
|
|
178
|
+
/>
|
|
179
|
+
|
|
180
|
+
<div className="sf-row">
|
|
181
|
+
<FolderSelect label="Upload into" folders={folders} value={target} onChange={setTarget} />
|
|
182
|
+
<button type="button" className="sf-btn sf-btn--primary" disabled={busy || !pending.length} onClick={uploadAll}>
|
|
183
|
+
{busy ? "Uploading…" : `Upload ${pending.length}`}
|
|
184
|
+
</button>
|
|
185
|
+
{queue.length > 0 && !busy && (
|
|
186
|
+
<button type="button" className="sf-btn" onClick={() => setQueue([])}>
|
|
187
|
+
Clear
|
|
188
|
+
</button>
|
|
189
|
+
)}
|
|
190
|
+
</div>
|
|
191
|
+
|
|
192
|
+
{message && (
|
|
193
|
+
<p className={message.includes("failed") ? "sf-error" : "sf-muted"} role="status">
|
|
194
|
+
{message}
|
|
195
|
+
</p>
|
|
196
|
+
)}
|
|
197
|
+
|
|
198
|
+
{queue.length > 0 && (
|
|
199
|
+
<ul className="sf-queue">
|
|
200
|
+
{queue.map((q, i) => (
|
|
201
|
+
<li key={`${q.relPath}-${i}`}>
|
|
202
|
+
<span className="sf-queue__name">{joinTarget(target, q.relPath)}</span>
|
|
203
|
+
{q.error ? (
|
|
204
|
+
<span className="sf-error">{q.error}</span>
|
|
205
|
+
) : (
|
|
206
|
+
<span className="sf-row">
|
|
207
|
+
<progress max={1} value={q.progress} />
|
|
208
|
+
<span className="sf-muted">{Math.round(q.progress * 100)}%</span>
|
|
209
|
+
</span>
|
|
210
|
+
)}
|
|
211
|
+
</li>
|
|
212
|
+
))}
|
|
213
|
+
</ul>
|
|
214
|
+
)}
|
|
215
|
+
</section>
|
|
216
|
+
);
|
|
217
|
+
}
|
package/src/api.ts
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import type { BrowseResult, LinkRoot, RootChallenge, SecureFile, ShareUpdate } from "./types";
|
|
2
|
+
|
|
3
|
+
export const DEFAULT_API_BASE = "https://api.abstractendeavors.com";
|
|
4
|
+
|
|
5
|
+
export class ApiError extends Error {
|
|
6
|
+
constructor(message: string, public status: number) {
|
|
7
|
+
super(message);
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
async function errorFrom(res: Response): Promise<ApiError> {
|
|
12
|
+
let message = `${res.status} ${res.statusText}`;
|
|
13
|
+
try {
|
|
14
|
+
const body = await res.json();
|
|
15
|
+
message = body?.message || body?.error || message;
|
|
16
|
+
} catch {
|
|
17
|
+
/* non-JSON error body */
|
|
18
|
+
}
|
|
19
|
+
if (res.status === 401) message = "Please log in to use Secure Files.";
|
|
20
|
+
return new ApiError(message, res.status);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function createApi(apiBase: string = DEFAULT_API_BASE) {
|
|
24
|
+
const root = `${apiBase.replace(/\/+$/, "")}/secure-files`;
|
|
25
|
+
|
|
26
|
+
async function json<T>(path: string, init: RequestInit = {}): Promise<T> {
|
|
27
|
+
const res = await fetch(`${root}${path}`, {
|
|
28
|
+
credentials: "include",
|
|
29
|
+
...init,
|
|
30
|
+
headers: { Accept: "application/json", ...(init.body ? { "Content-Type": "application/json" } : {}) },
|
|
31
|
+
});
|
|
32
|
+
if (!res.ok) throw await errorFrom(res);
|
|
33
|
+
return res.json();
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return {
|
|
37
|
+
list: () => json<{ user_name: string; is_admin: boolean; files: SecureFile[] }>("/userInfo"),
|
|
38
|
+
|
|
39
|
+
roots: () => json<{ roots: LinkRoot[] }>("/admin/roots"),
|
|
40
|
+
|
|
41
|
+
requestRoot: (path: string) =>
|
|
42
|
+
json<{ challenge: RootChallenge }>("/admin/roots", { method: "POST", body: JSON.stringify({ path }) }),
|
|
43
|
+
|
|
44
|
+
confirmRoot: (nonce: string, signature: string, label?: string) =>
|
|
45
|
+
json<{ roots: LinkRoot[] }>("/admin/roots/confirm", {
|
|
46
|
+
method: "POST",
|
|
47
|
+
body: JSON.stringify({ nonce, signature, label }),
|
|
48
|
+
}),
|
|
49
|
+
|
|
50
|
+
removeRoot: (id: number) => json<{ roots: LinkRoot[] }>(`/admin/roots/${id}`, { method: "DELETE" }),
|
|
51
|
+
|
|
52
|
+
browse: (path: string) => json<BrowseResult>(`/admin/browse?path=${encodeURIComponent(path)}`),
|
|
53
|
+
|
|
54
|
+
link: (paths: string[], folder: string) =>
|
|
55
|
+
json<{ linked: string[]; errors: { path: string; error: string }[] }>("/admin/link", {
|
|
56
|
+
method: "POST",
|
|
57
|
+
body: JSON.stringify({ paths, folder }),
|
|
58
|
+
}),
|
|
59
|
+
|
|
60
|
+
updateShare: (update: ShareUpdate) =>
|
|
61
|
+
json<{ file: SecureFile }>("/files/share", { method: "PATCH", body: JSON.stringify(update) }),
|
|
62
|
+
|
|
63
|
+
remove: (ids: number[]) =>
|
|
64
|
+
json<{ removed: number[]; errors: { id: number; error: string }[] }>("/remove", {
|
|
65
|
+
method: "POST",
|
|
66
|
+
body: JSON.stringify({ ids }),
|
|
67
|
+
}),
|
|
68
|
+
|
|
69
|
+
/** Hand the download to the browser so it streams to disk (native progress + resume, no memory limit). */
|
|
70
|
+
download(ids: number[]): void {
|
|
71
|
+
const a = document.createElement("a");
|
|
72
|
+
a.href = `${root}/download?ids=${ids.join(",")}`;
|
|
73
|
+
a.rel = "noopener";
|
|
74
|
+
document.body.appendChild(a);
|
|
75
|
+
a.click();
|
|
76
|
+
a.remove();
|
|
77
|
+
},
|
|
78
|
+
|
|
79
|
+
upload(file: File, relPath: string, onProgress?: (fraction: number) => void): Promise<void> {
|
|
80
|
+
return new Promise((resolve, reject) => {
|
|
81
|
+
const form = new FormData();
|
|
82
|
+
form.append("file", file);
|
|
83
|
+
form.append("rel_path", relPath);
|
|
84
|
+
const xhr = new XMLHttpRequest();
|
|
85
|
+
xhr.open("POST", `${root}/upload`);
|
|
86
|
+
xhr.withCredentials = true;
|
|
87
|
+
xhr.upload.onprogress = (e) => {
|
|
88
|
+
if (e.lengthComputable) onProgress?.(e.loaded / e.total);
|
|
89
|
+
};
|
|
90
|
+
xhr.onload = () => {
|
|
91
|
+
if (xhr.status >= 200 && xhr.status < 300) return resolve();
|
|
92
|
+
let message = `Upload failed (${xhr.status})`;
|
|
93
|
+
try {
|
|
94
|
+
message = JSON.parse(xhr.responseText)?.message || message;
|
|
95
|
+
} catch {
|
|
96
|
+
/* non-JSON */
|
|
97
|
+
}
|
|
98
|
+
if (xhr.status === 401) message = "Please log in to use Secure Files.";
|
|
99
|
+
reject(new ApiError(message, xhr.status));
|
|
100
|
+
};
|
|
101
|
+
xhr.onerror = () => reject(new ApiError("Network error during upload", 0));
|
|
102
|
+
xhr.send(form);
|
|
103
|
+
});
|
|
104
|
+
},
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export type SecureFilesApi = ReturnType<typeof createApi>;
|
package/src/index.ts
CHANGED
|
@@ -1,8 +1,4 @@
|
|
|
1
|
-
export {
|
|
2
|
-
export {
|
|
3
|
-
export
|
|
4
|
-
export
|
|
5
|
-
export * from './UI';
|
|
6
|
-
export * from './types';
|
|
7
|
-
export * from './styles';
|
|
8
|
-
export { SecureFiles } from './App'
|
|
1
|
+
export { default as SecureFiles } from "./SecureFiles";
|
|
2
|
+
export { createApi, DEFAULT_API_BASE, ApiError } from "./api";
|
|
3
|
+
export type { SecureFilesApi } from "./api";
|
|
4
|
+
export type { SecureFile, ShareUpdate, SecureFilesProps, LinkRoot, RootChallenge, BrowseResult } from "./types";
|