@shipstatic/drop 0.1.5 → 0.1.7
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/README.md +227 -180
- package/dist/index.cjs +167 -38
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +59 -13
- package/dist/index.d.ts +59 -13
- package/dist/index.js +168 -39
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -11834,6 +11834,38 @@ function stripCommonPrefix(files) {
|
|
|
11834
11834
|
path: f.path.startsWith(prefix) ? f.path.slice(prefix.length) : f.path
|
|
11835
11835
|
}));
|
|
11836
11836
|
}
|
|
11837
|
+
async function traverseFileTree(entry, files, currentPath = "") {
|
|
11838
|
+
if (entry.isFile) {
|
|
11839
|
+
const file = await new Promise((resolve, reject) => {
|
|
11840
|
+
entry.file(resolve, reject);
|
|
11841
|
+
});
|
|
11842
|
+
const relativePath = currentPath ? `${currentPath}/${file.name}` : file.name;
|
|
11843
|
+
Object.defineProperty(file, "webkitRelativePath", {
|
|
11844
|
+
value: relativePath,
|
|
11845
|
+
writable: false
|
|
11846
|
+
});
|
|
11847
|
+
files.push(file);
|
|
11848
|
+
} else if (entry.isDirectory) {
|
|
11849
|
+
const dirReader = entry.createReader();
|
|
11850
|
+
let allEntries = [];
|
|
11851
|
+
const readEntriesBatch = async () => {
|
|
11852
|
+
const batch = await new Promise(
|
|
11853
|
+
(resolve, reject) => {
|
|
11854
|
+
dirReader.readEntries(resolve, reject);
|
|
11855
|
+
}
|
|
11856
|
+
);
|
|
11857
|
+
if (batch.length > 0) {
|
|
11858
|
+
allEntries = allEntries.concat(batch);
|
|
11859
|
+
await readEntriesBatch();
|
|
11860
|
+
}
|
|
11861
|
+
};
|
|
11862
|
+
await readEntriesBatch();
|
|
11863
|
+
for (const childEntry of allEntries) {
|
|
11864
|
+
const entryPath = childEntry.isDirectory ? currentPath ? `${currentPath}/${childEntry.name}` : childEntry.name : currentPath;
|
|
11865
|
+
await traverseFileTree(childEntry, files, entryPath);
|
|
11866
|
+
}
|
|
11867
|
+
}
|
|
11868
|
+
}
|
|
11837
11869
|
function useDrop(options) {
|
|
11838
11870
|
const {
|
|
11839
11871
|
ship: ship$1,
|
|
@@ -11841,26 +11873,32 @@ function useDrop(options) {
|
|
|
11841
11873
|
onFilesReady,
|
|
11842
11874
|
stripPrefix = true
|
|
11843
11875
|
} = options;
|
|
11844
|
-
const
|
|
11845
|
-
|
|
11846
|
-
|
|
11847
|
-
|
|
11848
|
-
|
|
11876
|
+
const initialState = {
|
|
11877
|
+
value: "idle",
|
|
11878
|
+
files: [],
|
|
11879
|
+
sourceName: "",
|
|
11880
|
+
status: null
|
|
11881
|
+
};
|
|
11882
|
+
const [state, setState] = react.useState(initialState);
|
|
11849
11883
|
const isProcessingRef = react.useRef(false);
|
|
11884
|
+
const inputRef = react.useRef(null);
|
|
11885
|
+
const isProcessing = react.useMemo(() => state.value === "processing", [state.value]);
|
|
11886
|
+
const isDragging = react.useMemo(() => state.value === "dragging", [state.value]);
|
|
11850
11887
|
const processFiles = react.useCallback(async (newFiles) => {
|
|
11851
11888
|
if (isProcessingRef.current) {
|
|
11852
11889
|
console.warn("File processing already in progress. Ignoring duplicate call.");
|
|
11853
11890
|
return;
|
|
11854
11891
|
}
|
|
11855
11892
|
if (!newFiles || newFiles.length === 0) {
|
|
11856
|
-
setStatusText("No files selected.");
|
|
11857
11893
|
return;
|
|
11858
11894
|
}
|
|
11859
11895
|
isProcessingRef.current = true;
|
|
11860
|
-
|
|
11861
|
-
|
|
11862
|
-
|
|
11863
|
-
|
|
11896
|
+
setState({
|
|
11897
|
+
value: "processing",
|
|
11898
|
+
files: [],
|
|
11899
|
+
sourceName: "",
|
|
11900
|
+
status: { title: "Processing...", details: "Validating and preparing files." }
|
|
11901
|
+
});
|
|
11864
11902
|
try {
|
|
11865
11903
|
let detectedSourceName = "";
|
|
11866
11904
|
if (newFiles.length === 1 && isZipFile(newFiles[0])) {
|
|
@@ -11873,12 +11911,14 @@ function useDrop(options) {
|
|
|
11873
11911
|
detectedSourceName = newFiles[0].name;
|
|
11874
11912
|
}
|
|
11875
11913
|
}
|
|
11876
|
-
setSourceName(detectedSourceName);
|
|
11877
11914
|
const allFiles = [];
|
|
11878
11915
|
const shouldExtractZip = newFiles.length === 1 && isZipFile(newFiles[0]);
|
|
11879
11916
|
if (shouldExtractZip) {
|
|
11880
11917
|
const zipFile = newFiles[0];
|
|
11881
|
-
|
|
11918
|
+
setState((prev) => ({
|
|
11919
|
+
...prev,
|
|
11920
|
+
status: { title: "Extracting...", details: `Extracting ${zipFile.name}...` }
|
|
11921
|
+
}));
|
|
11882
11922
|
const { files: extractedFiles, errors } = await extractZipToFiles(zipFile);
|
|
11883
11923
|
if (errors.length > 0) {
|
|
11884
11924
|
console.warn("ZIP extraction errors:", errors);
|
|
@@ -11894,29 +11934,44 @@ function useDrop(options) {
|
|
|
11894
11934
|
const filePaths = allFiles.map(getFilePath);
|
|
11895
11935
|
const validPaths = new Set(ship.filterJunk(filePaths));
|
|
11896
11936
|
const cleanFiles = allFiles.filter((f) => validPaths.has(getFilePath(f)));
|
|
11897
|
-
|
|
11937
|
+
setState((prev) => ({
|
|
11938
|
+
...prev,
|
|
11939
|
+
status: { title: "Processing...", details: "Processing files..." }
|
|
11940
|
+
}));
|
|
11898
11941
|
const processedFiles = await Promise.all(
|
|
11899
11942
|
cleanFiles.map((file) => createProcessedFile(file))
|
|
11900
11943
|
);
|
|
11901
11944
|
const finalFiles = stripPrefix ? stripCommonPrefix(processedFiles) : processedFiles;
|
|
11902
11945
|
const config = await ship$1.getConfig();
|
|
11903
11946
|
const validation = ship.validateFiles(finalFiles, config);
|
|
11904
|
-
setFiles(validation.files);
|
|
11905
|
-
setValidationError(validation.error);
|
|
11906
11947
|
if (validation.error) {
|
|
11907
|
-
|
|
11948
|
+
setState({
|
|
11949
|
+
value: "error",
|
|
11950
|
+
files: validation.files,
|
|
11951
|
+
sourceName: detectedSourceName,
|
|
11952
|
+
status: { title: validation.error.error, details: validation.error.details }
|
|
11953
|
+
});
|
|
11908
11954
|
onValidationError?.(validation.error);
|
|
11909
11955
|
} else if (validation.validFiles.length > 0) {
|
|
11910
|
-
|
|
11956
|
+
setState({
|
|
11957
|
+
value: "ready",
|
|
11958
|
+
files: validation.files,
|
|
11959
|
+
sourceName: detectedSourceName,
|
|
11960
|
+
status: { title: "Ready", details: `${validation.validFiles.length} file(s) are ready.` }
|
|
11961
|
+
});
|
|
11911
11962
|
onFilesReady?.(validation.validFiles);
|
|
11912
11963
|
} else {
|
|
11913
11964
|
const noValidError = {
|
|
11914
11965
|
error: "No Valid Files",
|
|
11915
|
-
details: "
|
|
11966
|
+
details: "None of the provided files could be processed.",
|
|
11916
11967
|
isClientError: true
|
|
11917
11968
|
};
|
|
11918
|
-
|
|
11919
|
-
|
|
11969
|
+
setState({
|
|
11970
|
+
value: "error",
|
|
11971
|
+
files: validation.files,
|
|
11972
|
+
sourceName: detectedSourceName,
|
|
11973
|
+
status: { title: noValidError.error, details: noValidError.details }
|
|
11974
|
+
});
|
|
11920
11975
|
onValidationError?.(noValidError);
|
|
11921
11976
|
}
|
|
11922
11977
|
} catch (error) {
|
|
@@ -11925,38 +11980,112 @@ function useDrop(options) {
|
|
|
11925
11980
|
details: `Failed to process files: ${error instanceof Error ? error.message : String(error)}`,
|
|
11926
11981
|
isClientError: true
|
|
11927
11982
|
};
|
|
11928
|
-
|
|
11929
|
-
|
|
11983
|
+
setState((prev) => ({
|
|
11984
|
+
...prev,
|
|
11985
|
+
value: "error",
|
|
11986
|
+
status: { title: processingError.error, details: processingError.details }
|
|
11987
|
+
}));
|
|
11930
11988
|
onValidationError?.(processingError);
|
|
11931
11989
|
} finally {
|
|
11932
11990
|
isProcessingRef.current = false;
|
|
11933
|
-
setIsProcessing(false);
|
|
11934
11991
|
}
|
|
11935
11992
|
}, [ship$1, onValidationError, onFilesReady, stripPrefix]);
|
|
11936
11993
|
const clearAll = react.useCallback(() => {
|
|
11937
|
-
|
|
11938
|
-
setSourceName("");
|
|
11939
|
-
setStatusText("");
|
|
11940
|
-
setValidationError(null);
|
|
11994
|
+
setState(initialState);
|
|
11941
11995
|
isProcessingRef.current = false;
|
|
11942
|
-
setIsProcessing(false);
|
|
11943
11996
|
}, []);
|
|
11944
11997
|
const getValidFilesCallback = react.useCallback(() => {
|
|
11945
|
-
return getValidFiles(files);
|
|
11946
|
-
}, [files]);
|
|
11947
|
-
const updateFileStatus = react.useCallback((fileId,
|
|
11948
|
-
|
|
11949
|
-
|
|
11950
|
-
|
|
11998
|
+
return getValidFiles(state.files);
|
|
11999
|
+
}, [state.files]);
|
|
12000
|
+
const updateFileStatus = react.useCallback((fileId, fileState) => {
|
|
12001
|
+
setState((prev) => ({
|
|
12002
|
+
...prev,
|
|
12003
|
+
files: prev.files.map(
|
|
12004
|
+
(file) => file.id === fileId ? { ...file, ...fileState } : file
|
|
12005
|
+
)
|
|
12006
|
+
}));
|
|
12007
|
+
}, []);
|
|
12008
|
+
const handleDragOver = react.useCallback((e) => {
|
|
12009
|
+
e.preventDefault();
|
|
12010
|
+
setState((prev) => {
|
|
12011
|
+
if (prev.value === "idle" || prev.value === "ready" || prev.value === "error") {
|
|
12012
|
+
return { ...prev, value: "dragging" };
|
|
12013
|
+
}
|
|
12014
|
+
return prev;
|
|
12015
|
+
});
|
|
12016
|
+
}, []);
|
|
12017
|
+
const handleDragLeave = react.useCallback((e) => {
|
|
12018
|
+
e.preventDefault();
|
|
12019
|
+
setState((prev) => {
|
|
12020
|
+
if (prev.value !== "dragging") return prev;
|
|
12021
|
+
const nextValue = prev.files.length > 0 ? prev.status?.title === "Ready" ? "ready" : "error" : "idle";
|
|
12022
|
+
return { ...prev, value: nextValue };
|
|
12023
|
+
});
|
|
12024
|
+
}, []);
|
|
12025
|
+
const handleDrop = react.useCallback(async (e) => {
|
|
12026
|
+
e.preventDefault();
|
|
12027
|
+
const items = Array.from(e.dataTransfer.items);
|
|
12028
|
+
const files = [];
|
|
12029
|
+
let hasEntries = false;
|
|
12030
|
+
for (const item of items) {
|
|
12031
|
+
if (item.kind === "file") {
|
|
12032
|
+
const entry = item.webkitGetAsEntry?.();
|
|
12033
|
+
if (entry) {
|
|
12034
|
+
hasEntries = true;
|
|
12035
|
+
await traverseFileTree(
|
|
12036
|
+
entry,
|
|
12037
|
+
files,
|
|
12038
|
+
entry.isDirectory ? entry.name : ""
|
|
12039
|
+
);
|
|
12040
|
+
}
|
|
12041
|
+
}
|
|
12042
|
+
}
|
|
12043
|
+
if (!hasEntries && e.dataTransfer.files.length > 0) {
|
|
12044
|
+
files.push(...Array.from(e.dataTransfer.files));
|
|
12045
|
+
}
|
|
12046
|
+
if (files.length > 0) {
|
|
12047
|
+
await processFiles(files);
|
|
12048
|
+
} else if (state.value === "dragging") {
|
|
12049
|
+
setState((prev) => ({ ...prev, value: "idle" }));
|
|
12050
|
+
}
|
|
12051
|
+
}, [processFiles, state.value]);
|
|
12052
|
+
const handleInputChange = react.useCallback((e) => {
|
|
12053
|
+
const files = Array.from(e.target.files || []);
|
|
12054
|
+
if (files.length > 0) {
|
|
12055
|
+
processFiles(files);
|
|
12056
|
+
}
|
|
12057
|
+
}, [processFiles]);
|
|
12058
|
+
const open = react.useCallback(() => {
|
|
12059
|
+
inputRef.current?.click();
|
|
11951
12060
|
}, []);
|
|
12061
|
+
const getDropzoneProps = react.useCallback(() => ({
|
|
12062
|
+
onDragOver: handleDragOver,
|
|
12063
|
+
onDragLeave: handleDragLeave,
|
|
12064
|
+
onDrop: handleDrop,
|
|
12065
|
+
onClick: open
|
|
12066
|
+
}), [handleDragOver, handleDragLeave, handleDrop, open]);
|
|
12067
|
+
const getInputProps = react.useCallback(() => ({
|
|
12068
|
+
ref: inputRef,
|
|
12069
|
+
type: "file",
|
|
12070
|
+
style: { display: "none" },
|
|
12071
|
+
multiple: true,
|
|
12072
|
+
webkitdirectory: "",
|
|
12073
|
+
onChange: handleInputChange
|
|
12074
|
+
}), [handleInputChange]);
|
|
11952
12075
|
return {
|
|
11953
|
-
|
|
11954
|
-
|
|
11955
|
-
|
|
12076
|
+
// State machine
|
|
12077
|
+
state,
|
|
12078
|
+
// Convenience getters (computed from state)
|
|
11956
12079
|
isProcessing,
|
|
11957
|
-
|
|
12080
|
+
isDragging,
|
|
12081
|
+
// Primary API: Prop getters
|
|
12082
|
+
getDropzoneProps,
|
|
12083
|
+
getInputProps,
|
|
12084
|
+
// Actions
|
|
12085
|
+
open,
|
|
11958
12086
|
processFiles,
|
|
11959
12087
|
clearAll,
|
|
12088
|
+
// Helpers
|
|
11960
12089
|
getValidFiles: getValidFilesCallback,
|
|
11961
12090
|
updateFileStatus
|
|
11962
12091
|
};
|