@shipstatic/drop 0.1.8 → 0.1.10
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 +42 -44
- package/dist/index.cjs +42 -22
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +19 -10
- package/dist/index.d.ts +19 -10
- package/dist/index.js +43 -23
- package/dist/index.js.map +1 -1
- package/package.json +10 -9
package/README.md
CHANGED
|
@@ -85,9 +85,8 @@ function MyUploader() {
|
|
|
85
85
|
});
|
|
86
86
|
|
|
87
87
|
const handleUpload = async () => {
|
|
88
|
-
const validFiles = drop.getValidFiles();
|
|
89
88
|
// ProcessedFile extends StaticFile - no conversion needed!
|
|
90
|
-
await ship.deployments.create(validFiles.map(f => f.file));
|
|
89
|
+
await ship.deployments.create(drop.validFiles.map(f => f.file));
|
|
91
90
|
};
|
|
92
91
|
|
|
93
92
|
return (
|
|
@@ -107,15 +106,15 @@ function MyUploader() {
|
|
|
107
106
|
{drop.isDragging ? '📂 Drop here' : '📁 Click or drag files/folders'}
|
|
108
107
|
</div>
|
|
109
108
|
|
|
110
|
-
{/* Status - using state machine */}
|
|
111
|
-
{drop.
|
|
109
|
+
{/* Status - using state machine phase */}
|
|
110
|
+
{drop.status && (
|
|
112
111
|
<p>
|
|
113
|
-
<strong>{drop.
|
|
112
|
+
<strong>{drop.status.title}:</strong> {drop.status.details}
|
|
114
113
|
</p>
|
|
115
114
|
)}
|
|
116
115
|
|
|
117
116
|
{/* File list */}
|
|
118
|
-
{drop.
|
|
117
|
+
{drop.files.map(file => (
|
|
119
118
|
<div key={file.id}>
|
|
120
119
|
{file.name} - {file.status}
|
|
121
120
|
</div>
|
|
@@ -124,9 +123,9 @@ function MyUploader() {
|
|
|
124
123
|
{/* Upload button */}
|
|
125
124
|
<button
|
|
126
125
|
onClick={handleUpload}
|
|
127
|
-
disabled={drop.
|
|
126
|
+
disabled={drop.phase !== 'ready'}
|
|
128
127
|
>
|
|
129
|
-
Upload {drop.
|
|
128
|
+
Upload {drop.validFiles.length} files
|
|
130
129
|
</button>
|
|
131
130
|
</div>
|
|
132
131
|
);
|
|
@@ -232,17 +231,22 @@ interface DropOptions {
|
|
|
232
231
|
|
|
233
232
|
**Returns:**
|
|
234
233
|
|
|
234
|
+
```typescript
|
|
235
235
|
```typescript
|
|
236
236
|
interface DropReturn {
|
|
237
|
-
// State machine
|
|
238
|
-
/** Current state of the drop hook */
|
|
239
|
-
state: DropState;
|
|
240
|
-
|
|
241
237
|
// Convenience getters (computed from state)
|
|
238
|
+
/** Current phase of the state machine */
|
|
239
|
+
phase: DropStateValue;
|
|
242
240
|
/** Whether currently processing files (ZIP extraction, etc.) */
|
|
243
241
|
isProcessing: boolean;
|
|
244
242
|
/** Whether user is currently dragging over the dropzone */
|
|
245
243
|
isDragging: boolean;
|
|
244
|
+
/** Flattened access to files */
|
|
245
|
+
files: ProcessedFile[];
|
|
246
|
+
/** Flattened access to source name */
|
|
247
|
+
sourceName: string;
|
|
248
|
+
/** Flattened access to status */
|
|
249
|
+
status: DropStatus | null;
|
|
246
250
|
|
|
247
251
|
// Primary API: Prop getters for easy integration
|
|
248
252
|
/** Get props to spread on dropzone element (handles drag & drop) */
|
|
@@ -272,7 +276,7 @@ interface DropReturn {
|
|
|
272
276
|
|
|
273
277
|
// Helpers
|
|
274
278
|
/** Get only valid files ready for upload */
|
|
275
|
-
|
|
279
|
+
validFiles: ProcessedFile[];
|
|
276
280
|
/** Update upload state for a specific file (status, progress, message) */
|
|
277
281
|
updateFileStatus: (fileId: string, state: {
|
|
278
282
|
status: FileStatus;
|
|
@@ -289,22 +293,16 @@ type DropStateValue =
|
|
|
289
293
|
| 'ready' // Files are valid and ready for deployment
|
|
290
294
|
| 'error'; // An error occurred during processing
|
|
291
295
|
|
|
292
|
-
interface DropState {
|
|
293
|
-
value: DropStateValue;
|
|
294
|
-
files: ProcessedFile[];
|
|
295
|
-
sourceName: string;
|
|
296
|
-
status: DropStatus | null;
|
|
297
|
-
}
|
|
298
|
-
|
|
299
296
|
interface DropStatus {
|
|
300
297
|
title: string;
|
|
301
298
|
details: string;
|
|
299
|
+
errors?: string[];
|
|
302
300
|
}
|
|
303
301
|
```
|
|
304
302
|
|
|
305
303
|
## State Machine
|
|
306
304
|
|
|
307
|
-
The drop hook uses a state machine for predictable, clear state management. Instead of multiple boolean flags, you have a single `
|
|
305
|
+
The drop hook uses a state machine for predictable, clear state management. Instead of multiple boolean flags, you have a single `drop.phase` that represents exactly what's happening.
|
|
308
306
|
|
|
309
307
|
### State Flow
|
|
310
308
|
|
|
@@ -320,9 +318,8 @@ error → dragging → processing → ... (retry)
|
|
|
320
318
|
|
|
321
319
|
```tsx
|
|
322
320
|
function StatusIndicator({ drop }) {
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
switch (state.value) {
|
|
321
|
+
// Use drop.phase to switch-case on the state
|
|
322
|
+
switch (drop.phase) {
|
|
326
323
|
case 'idle':
|
|
327
324
|
return <p>Drop files here or click to select</p>;
|
|
328
325
|
|
|
@@ -330,12 +327,12 @@ function StatusIndicator({ drop }) {
|
|
|
330
327
|
return <p>Drop your files now!</p>;
|
|
331
328
|
|
|
332
329
|
case 'processing':
|
|
333
|
-
return <p>{
|
|
330
|
+
return <p>{drop.status?.details || 'Processing...'}</p>;
|
|
334
331
|
|
|
335
332
|
case 'ready':
|
|
336
333
|
return (
|
|
337
334
|
<div>
|
|
338
|
-
<p>✓ {
|
|
335
|
+
<p>✓ {drop.files.length} files ready</p>
|
|
339
336
|
<button>Upload to Ship</button>
|
|
340
337
|
</div>
|
|
341
338
|
);
|
|
@@ -343,8 +340,8 @@ function StatusIndicator({ drop }) {
|
|
|
343
340
|
case 'error':
|
|
344
341
|
return (
|
|
345
342
|
<div>
|
|
346
|
-
<p>✗ {
|
|
347
|
-
<p>{
|
|
343
|
+
<p>✗ {drop.status?.title}</p>
|
|
344
|
+
<p>{drop.status?.details}</p>
|
|
348
345
|
<button onClick={drop.clearAll}>Try Again</button>
|
|
349
346
|
</div>
|
|
350
347
|
);
|
|
@@ -357,14 +354,14 @@ function StatusIndicator({ drop }) {
|
|
|
357
354
|
For simpler use cases, boolean convenience getters are provided:
|
|
358
355
|
|
|
359
356
|
```tsx
|
|
360
|
-
// These are computed from
|
|
361
|
-
drop.isProcessing // true when
|
|
362
|
-
drop.isDragging // true when
|
|
363
|
-
|
|
364
|
-
// For error information, use the
|
|
365
|
-
drop.
|
|
366
|
-
drop.
|
|
367
|
-
drop.
|
|
357
|
+
// These are computed from drop.phase (read-only projections)
|
|
358
|
+
drop.isProcessing // true when phase === 'processing'
|
|
359
|
+
drop.isDragging // true when phase === 'dragging'
|
|
360
|
+
|
|
361
|
+
// For error information, use the flattened status object
|
|
362
|
+
drop.phase === 'error' // Check if in error state
|
|
363
|
+
drop.status?.title // Error title
|
|
364
|
+
drop.status?.details // Error details
|
|
368
365
|
```
|
|
369
366
|
|
|
370
367
|
### Benefits
|
|
@@ -384,7 +381,8 @@ Each file in the `state.files` array contains its own `status` and `statusMessag
|
|
|
384
381
|
function FileList({ drop }) {
|
|
385
382
|
return (
|
|
386
383
|
<div>
|
|
387
|
-
{
|
|
384
|
+
{/* Flattened access to files array */}
|
|
385
|
+
{drop.files.map(file => (
|
|
388
386
|
<div key={file.id}>
|
|
389
387
|
<span>{file.path}</span>
|
|
390
388
|
|
|
@@ -401,7 +399,7 @@ function FileList({ drop }) {
|
|
|
401
399
|
))}
|
|
402
400
|
|
|
403
401
|
{/* If validation fails, allow user to clear all and try again */}
|
|
404
|
-
{drop.
|
|
402
|
+
{drop.phase === 'error' && (
|
|
405
403
|
<button onClick={drop.clearAll}>
|
|
406
404
|
Clear All & Try Again
|
|
407
405
|
</button>
|
|
@@ -422,10 +420,10 @@ function FileList({ drop }) {
|
|
|
422
420
|
When files fail validation or processing, check the error state:
|
|
423
421
|
|
|
424
422
|
```tsx
|
|
425
|
-
{drop.
|
|
423
|
+
{drop.phase === 'error' && drop.status && (
|
|
426
424
|
<div>
|
|
427
|
-
<p>{drop.
|
|
428
|
-
<p>{drop.
|
|
425
|
+
<p>{drop.status.title}</p>
|
|
426
|
+
<p>{drop.status.details}</p>
|
|
429
427
|
</div>
|
|
430
428
|
)}
|
|
431
429
|
```
|
|
@@ -455,10 +453,10 @@ Use `clearAll()` to reset and try again:
|
|
|
455
453
|
|
|
456
454
|
```tsx
|
|
457
455
|
// If validation fails, show user which files failed
|
|
458
|
-
{drop.
|
|
456
|
+
{drop.phase === 'error' && (
|
|
459
457
|
<div>
|
|
460
458
|
<p>Validation failed. Please fix the issues and try again:</p>
|
|
461
|
-
{drop.
|
|
459
|
+
{drop.files.map(file => (
|
|
462
460
|
<div key={file.id}>
|
|
463
461
|
{file.path}: {file.statusMessage}
|
|
464
462
|
</div>
|
|
@@ -629,7 +627,7 @@ All errors are surfaced at the per-file level:
|
|
|
629
627
|
- Processing errors (e.g., ZIP extraction failures) are marked with `status: 'processing_error'`
|
|
630
628
|
- Validation failures are marked with `status: 'validation_failed'`
|
|
631
629
|
- The `statusMessage` always contains specific error details
|
|
632
|
-
- Failed files are excluded from `
|
|
630
|
+
- Failed files are excluded from `validFiles` and cannot be deployed
|
|
633
631
|
- No silent failures - all errors are visible to users
|
|
634
632
|
|
|
635
633
|
See the [Error Handling](#error-handling) section for examples of displaying per-file errors in your UI.
|
package/dist/index.cjs
CHANGED
|
@@ -11813,7 +11813,6 @@ async function createProcessedFile(file, options) {
|
|
|
11813
11813
|
status: FILE_STATUSES.PENDING
|
|
11814
11814
|
};
|
|
11815
11815
|
}
|
|
11816
|
-
var getValidFiles = ship.getValidFiles;
|
|
11817
11816
|
function stripCommonPrefix(files) {
|
|
11818
11817
|
if (files.length === 0) return files;
|
|
11819
11818
|
const paths = files.map((f) => f.path);
|
|
@@ -11829,10 +11828,20 @@ function stripCommonPrefix(files) {
|
|
|
11829
11828
|
}
|
|
11830
11829
|
if (commonDepth === 0) return files;
|
|
11831
11830
|
const prefix = segments.slice(0, commonDepth).join("/") + "/";
|
|
11832
|
-
return files.map((f) =>
|
|
11833
|
-
|
|
11834
|
-
|
|
11835
|
-
|
|
11831
|
+
return files.map((f) => {
|
|
11832
|
+
const newPath = f.path.startsWith(prefix) ? f.path.slice(prefix.length) : f.path;
|
|
11833
|
+
if (f.file) {
|
|
11834
|
+
Object.defineProperty(f.file, "webkitRelativePath", {
|
|
11835
|
+
value: newPath,
|
|
11836
|
+
writable: false,
|
|
11837
|
+
configurable: true
|
|
11838
|
+
});
|
|
11839
|
+
}
|
|
11840
|
+
return {
|
|
11841
|
+
...f,
|
|
11842
|
+
path: newPath
|
|
11843
|
+
};
|
|
11844
|
+
});
|
|
11836
11845
|
}
|
|
11837
11846
|
async function traverseFileTree(entry, files, currentPath = "") {
|
|
11838
11847
|
try {
|
|
@@ -11843,7 +11852,8 @@ async function traverseFileTree(entry, files, currentPath = "") {
|
|
|
11843
11852
|
const relativePath = currentPath ? `${currentPath}/${file.name}` : file.name;
|
|
11844
11853
|
Object.defineProperty(file, "webkitRelativePath", {
|
|
11845
11854
|
value: relativePath,
|
|
11846
|
-
writable: false
|
|
11855
|
+
writable: false,
|
|
11856
|
+
configurable: true
|
|
11847
11857
|
});
|
|
11848
11858
|
files.push(file);
|
|
11849
11859
|
} else if (entry.isDirectory) {
|
|
@@ -11888,6 +11898,7 @@ function useDrop(options) {
|
|
|
11888
11898
|
const inputRef = react.useRef(null);
|
|
11889
11899
|
const isProcessing = react.useMemo(() => state.value === "processing", [state.value]);
|
|
11890
11900
|
const isDragging = react.useMemo(() => state.value === "dragging", [state.value]);
|
|
11901
|
+
const validFiles = react.useMemo(() => ship.getValidFiles(state.files), [state.files]);
|
|
11891
11902
|
const processFiles = react.useCallback(async (newFiles) => {
|
|
11892
11903
|
if (isProcessingRef.current) {
|
|
11893
11904
|
console.warn("File processing already in progress. Ignoring duplicate call.");
|
|
@@ -11953,7 +11964,11 @@ function useDrop(options) {
|
|
|
11953
11964
|
value: "error",
|
|
11954
11965
|
files: validation.files,
|
|
11955
11966
|
sourceName: detectedSourceName,
|
|
11956
|
-
status: {
|
|
11967
|
+
status: {
|
|
11968
|
+
title: validation.error.error,
|
|
11969
|
+
details: validation.error.details,
|
|
11970
|
+
errors: validation.error.errors
|
|
11971
|
+
}
|
|
11957
11972
|
});
|
|
11958
11973
|
onValidationError?.(validation.error);
|
|
11959
11974
|
} else if (validation.validFiles.length > 0) {
|
|
@@ -11998,9 +12013,6 @@ function useDrop(options) {
|
|
|
11998
12013
|
setState(initialState);
|
|
11999
12014
|
isProcessingRef.current = false;
|
|
12000
12015
|
}, []);
|
|
12001
|
-
const getValidFilesCallback = react.useCallback(() => {
|
|
12002
|
-
return getValidFiles(state.files);
|
|
12003
|
-
}, [state.files]);
|
|
12004
12016
|
const updateFileStatus = react.useCallback((fileId, fileState) => {
|
|
12005
12017
|
setState((prev) => ({
|
|
12006
12018
|
...prev,
|
|
@@ -12030,21 +12042,21 @@ function useDrop(options) {
|
|
|
12030
12042
|
e.preventDefault();
|
|
12031
12043
|
const items = Array.from(e.dataTransfer.items);
|
|
12032
12044
|
const files = [];
|
|
12033
|
-
|
|
12045
|
+
const entriesToTraverse = [];
|
|
12034
12046
|
for (const item of items) {
|
|
12035
12047
|
if (item.kind === "file") {
|
|
12036
12048
|
try {
|
|
12037
12049
|
const entry = item.webkitGetAsEntry?.();
|
|
12038
|
-
if (entry) {
|
|
12039
|
-
|
|
12040
|
-
await traverseFileTree(
|
|
12041
|
-
entry,
|
|
12042
|
-
files,
|
|
12043
|
-
entry.isDirectory ? entry.name : ""
|
|
12044
|
-
);
|
|
12050
|
+
if (entry && entry.isDirectory) {
|
|
12051
|
+
entriesToTraverse.push({ entry, path: entry.name });
|
|
12045
12052
|
} else {
|
|
12046
12053
|
const file = item.getAsFile();
|
|
12047
12054
|
if (file) {
|
|
12055
|
+
Object.defineProperty(file, "webkitRelativePath", {
|
|
12056
|
+
value: file.name,
|
|
12057
|
+
writable: false,
|
|
12058
|
+
configurable: true
|
|
12059
|
+
});
|
|
12048
12060
|
files.push(file);
|
|
12049
12061
|
}
|
|
12050
12062
|
}
|
|
@@ -12057,7 +12069,12 @@ function useDrop(options) {
|
|
|
12057
12069
|
}
|
|
12058
12070
|
}
|
|
12059
12071
|
}
|
|
12060
|
-
if (
|
|
12072
|
+
if (entriesToTraverse.length > 0) {
|
|
12073
|
+
await Promise.all(entriesToTraverse.map(
|
|
12074
|
+
(item) => traverseFileTree(item.entry, files, item.path)
|
|
12075
|
+
));
|
|
12076
|
+
}
|
|
12077
|
+
if (files.length === 0 && e.dataTransfer.files.length > 0) {
|
|
12061
12078
|
files.push(...Array.from(e.dataTransfer.files));
|
|
12062
12079
|
}
|
|
12063
12080
|
if (files.length > 0) {
|
|
@@ -12091,10 +12108,13 @@ function useDrop(options) {
|
|
|
12091
12108
|
}), [handleInputChange]);
|
|
12092
12109
|
return {
|
|
12093
12110
|
// State machine
|
|
12094
|
-
state,
|
|
12095
12111
|
// Convenience getters (computed from state)
|
|
12112
|
+
phase: state.value,
|
|
12096
12113
|
isProcessing,
|
|
12097
12114
|
isDragging,
|
|
12115
|
+
files: state.files,
|
|
12116
|
+
sourceName: state.sourceName,
|
|
12117
|
+
status: state.status,
|
|
12098
12118
|
// Primary API: Prop getters
|
|
12099
12119
|
getDropzoneProps,
|
|
12100
12120
|
getInputProps,
|
|
@@ -12103,7 +12123,7 @@ function useDrop(options) {
|
|
|
12103
12123
|
processFiles,
|
|
12104
12124
|
clearAll,
|
|
12105
12125
|
// Helpers
|
|
12106
|
-
|
|
12126
|
+
validFiles,
|
|
12107
12127
|
updateFileStatus
|
|
12108
12128
|
};
|
|
12109
12129
|
}
|
|
@@ -12135,10 +12155,10 @@ exports.FILE_STATUSES = FILE_STATUSES;
|
|
|
12135
12155
|
exports.createProcessedFile = createProcessedFile;
|
|
12136
12156
|
exports.extractZipToFiles = extractZipToFiles;
|
|
12137
12157
|
exports.formatFileSize = formatFileSize;
|
|
12138
|
-
exports.getValidFiles = getValidFiles;
|
|
12139
12158
|
exports.isZipFile = isZipFile;
|
|
12140
12159
|
exports.normalizePath = normalizePath;
|
|
12141
12160
|
exports.stripCommonPrefix = stripCommonPrefix;
|
|
12161
|
+
exports.traverseFileTree = traverseFileTree;
|
|
12142
12162
|
exports.useDrop = useDrop;
|
|
12143
12163
|
//# sourceMappingURL=index.cjs.map
|
|
12144
12164
|
//# sourceMappingURL=index.cjs.map
|