├── .browserslistrc ├── .editorconfig ├── .flowconfig ├── .github ├── .nvmrc ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── actions │ ├── actions-token-unused │ │ ├── action.yml │ │ └── index.js │ ├── bundle-size-report │ │ ├── action.yml │ │ └── index.mjs │ ├── npm-deprecate │ │ └── action.yml │ ├── prepare │ │ └── action.yml │ ├── save-versions-to-wf │ │ ├── action.yml │ │ └── index.js │ └── versionLog │ │ ├── action.yml │ │ └── index.js ├── dependabot.yml ├── package.json ├── pnpm-lock.yaml └── workflows │ ├── deprecate.yml │ ├── pr.yml │ ├── release.yml │ ├── stalebot.yml │ └── storybook-publish.yml ├── .gitignore ├── .husky ├── commit-msg ├── pre-commit └── pre-push ├── .npmignore ├── .npmrc ├── .nvmrc ├── .storybook ├── UploadyStoryDecorator.tsx ├── VersionBadge.tsx ├── cypressAddon │ └── cypressDecorator.ts ├── main.ts ├── manager-head.html ├── manager.tsx ├── preview-head.html ├── preview.tsx ├── theme.ts ├── uploadyPreset.ts └── welcome.storydoc.mdx ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE.md ├── README.md ├── SECURITY.md ├── babel.config.js ├── bundle-size-report.json ├── bundle.config.mjs ├── codecov.yml ├── commitlint.config.mjs ├── cypress.config.mjs ├── cypress ├── component │ └── upload-preview-ctest.js ├── constants.js ├── e2e-weights.json ├── fixtures │ ├── flower.jpg │ └── sea.jpg ├── integration │ ├── chunked-sender │ │ ├── ChunkedSender-Progress-spec.js │ │ └── ChunkedSender-error-spec.js │ ├── chunked-uploady │ │ ├── ChunkedUploady-Abort-spec.js │ │ ├── ChunkedUploady-WithChunkEventHooks-spec.js │ │ └── ChunkedUploady-custom-success-spec.js │ ├── dropFile.js │ ├── intercept.js │ ├── mock-sender │ │ └── MockSender-progress-spec.js │ ├── native-uploady │ │ └── NativeUploady-simple-spec.js │ ├── retry-hooks │ │ ├── RetryHooks-queue-spec.js │ │ ├── RetryHooks-withRetry-all-spec.js │ │ ├── RetryHooks-withRetry-batch-spec.js │ │ └── RetryHooks-withRetry-item-spec.js │ ├── selectFiles.js │ ├── tus-uploady │ │ ├── TusUploady-parallel-spec.js │ │ ├── TusUploady-parallel-with-data-spec.js │ │ ├── TusUploady-resume-event-spec.js │ │ ├── TusUploady-retry-spec.js │ │ ├── TusUploady-send-data-spec.js │ │ ├── TusUploady-simple-spec.js │ │ ├── clearTusPersistStorage.js │ │ ├── runParallerlUpload.js │ │ └── tusIntercept.js │ ├── umd │ │ ├── all-umd-spec.js │ │ ├── core-ui-chunked-umd-spec.js │ │ ├── core-ui-umd-spec.js │ │ └── core-umd-spec.js │ ├── upload-button │ │ ├── UploadButton-asButton-spec.js │ │ ├── UploadButton-custom-input-button-spec.js │ │ ├── UploadButton-customInputAndForm-spec.js │ │ ├── UploadButton-differentConfig-spec.js │ │ ├── UploadButton-disabled-spec.js │ │ ├── UploadButton-eventHooks-spec.js │ │ ├── UploadButton-eventListeners-spec.js │ │ ├── UploadButton-form-spec.js │ │ ├── UploadButton-group-spec.js │ │ ├── UploadButton-progress-spec.js │ │ ├── UploadButton-simple-multiple-spec.js │ │ ├── UploadButton-simple-spec.js │ │ └── UploadButton-styled-spec.js │ ├── upload-drop-zone │ │ ├── UploadDropZone-3rd-party-spec.js │ │ ├── UploadDropZone-custom-remove-spec.js │ │ ├── UploadDropZone-differentConfig-spec.js │ │ ├── UploadDropZone-dropHandler-spec.js │ │ ├── UploadDropZone-get-files-filter-spec.js │ │ ├── UploadDropZone-handle-only-files-spec.js │ │ ├── UploadDropZone-keep-on-child-spec.js │ │ ├── UploadDropZone-no-handle-spec.js │ │ ├── UploadDropZone-simple-no-contain-check-spec.js │ │ └── UploadDropZone-simple-spec.js │ ├── upload-paste │ │ ├── UploadPaste-dropzone-spec.js │ │ ├── UploadPaste-element-spec.js │ │ ├── UploadPaste-simple-spec.js │ │ ├── UploadPaste-uploadButton-spec.js │ │ └── UploadPaste-window-spec.js │ ├── upload-preview │ │ ├── UploadPreview-clear-spec.js │ │ ├── UploadPreview-crop-form-spec.js │ │ ├── UploadPreview-crop-spec.js │ │ ├── UploadPreview-custom-method-spec.js │ │ ├── UploadPreview-multi-crop-spec.js │ │ ├── UploadPreview-progress-spec.js │ │ ├── UploadPreview-removePreview-spec.js │ │ ├── UploadPreview-simple-fallback-spec.js │ │ ├── UploadPreview-simple-multiple-spec.js │ │ ├── UploadPreview-simple-spec.js │ │ ├── UploadPreview-two-fields-spec.js │ │ └── examineCroppedUploadReq.js │ ├── upload-url-input │ │ └── UploadUrlInput-simple-spec.js │ ├── uploadFile.js │ ├── uploader │ │ ├── Uploader-data-test-spec.js │ │ ├── Uploader-no-prepare-pollute-spec.js │ │ ├── Uploader-proto-pollute-spec.js │ │ └── Uploader-recover-from-error-spec.js │ └── uploady │ │ ├── Uploady-abort-spec.js │ │ ├── Uploady-autoUpload-off-spec.js │ │ ├── Uploady-cancel-on-add-spec.js │ │ ├── Uploady-cancel-with-async-presend-spec.js │ │ ├── Uploady-custom-success-spec.js │ │ ├── Uploady-customResponseFormat-spec.js │ │ ├── Uploady-failed-mock-spec.js │ │ ├── Uploady-fast-abort-spec.js │ │ ├── Uploady-filesParamName-spec.js │ │ ├── Uploady-headerFromPreSend-spec.js │ │ ├── Uploady-internal-input-spec.js │ │ ├── Uploady-invalid-batch-start-spec.js │ │ ├── Uploady-invalid-request-pre-send-spec.js │ │ ├── Uploady-pending-with-options-spec.js │ │ ├── Uploady-simple-spec.js │ │ └── Uploady-undefined-param-spec.js ├── plugins │ └── index.js ├── reporters-config.json-old ├── support │ ├── component-index.html │ ├── component.js │ ├── e2e.js │ ├── iframe.js │ ├── index.d.ts │ ├── index.js │ ├── pasteFile.js │ ├── setUploadOptions.js │ ├── storyLog.js │ ├── visitStory.js │ └── wait.js ├── tsconfig.json └── webpack.cypress.config.mjs ├── eslint.config.mjs ├── flow-typed.config.json ├── flow-typed └── npm │ ├── bom.js │ ├── cssom.js │ ├── dom.js │ ├── flow-bin_v0.x.x.js │ ├── invariant_v2.x.x.js │ ├── json-api.js │ ├── jsx-custom.js │ ├── node.js │ ├── serviceworkers.js │ ├── shelljs_vx.x.x.js │ ├── streams.js │ ├── webpack_v5.x.x.js │ └── yargs_v17.x.x.js ├── guides ├── README.md └── rpldy-demo.gif ├── lerna.json ├── netlify.toml ├── nx.json ├── package.json ├── packages ├── core │ ├── abort │ │ ├── .npmignore │ │ ├── README.md │ │ ├── package.json │ │ ├── src │ │ │ ├── abort.js │ │ │ ├── fastAbort.js │ │ │ ├── getAbortEnhancer.js │ │ │ ├── index.js │ │ │ ├── tests │ │ │ │ ├── abort.test.js │ │ │ │ ├── fastAbort.test.js │ │ │ │ └── getAbortEnhancer.test.js │ │ │ └── types.js │ │ └── types │ │ │ ├── index.d.ts │ │ │ └── index.test-d.ts │ ├── chunked-sender │ │ ├── .npmignore │ │ ├── ChunkedSender.stories.js │ │ ├── ChunkedSender.storydoc.mdx │ │ ├── README.md │ │ ├── package.json │ │ ├── src │ │ │ ├── chunkedSender │ │ │ │ ├── ChunkedSendError.js │ │ │ │ ├── createChunkedSender.js │ │ │ │ ├── getChunkedState.js │ │ │ │ ├── getChunks.js │ │ │ │ ├── getChunksToSend.js │ │ │ │ ├── handleChunkRequest.js │ │ │ │ ├── index.js │ │ │ │ ├── processChunkProgressData.js │ │ │ │ ├── processChunks.js │ │ │ │ ├── sendChunk.js │ │ │ │ ├── sendChunks.js │ │ │ │ ├── tests │ │ │ │ │ ├── createChunkedSender.test.js │ │ │ │ │ ├── getChunkedState.test.js │ │ │ │ │ ├── getChunks.test.js │ │ │ │ │ ├── getChunksToSend.test.js │ │ │ │ │ ├── handleChunkRequest.test.js │ │ │ │ │ ├── mocks │ │ │ │ │ │ └── getChunkedState.mock.js │ │ │ │ │ ├── processChunkProgressData.test.js │ │ │ │ │ ├── processChunks.test.js │ │ │ │ │ ├── sendChunk.test.js │ │ │ │ │ └── sendChunks.test.js │ │ │ │ └── types.js │ │ │ ├── consts.js │ │ │ ├── defaults.js │ │ │ ├── getChunkedEnhancer.js │ │ │ ├── index.js │ │ │ ├── tests │ │ │ │ ├── chunkedEnhancer.test.js │ │ │ │ └── utils.test.js │ │ │ ├── types.js │ │ │ └── utils.js │ │ └── types │ │ │ ├── index.d.ts │ │ │ └── index.test-d.tsx │ ├── life-events │ │ ├── .npmignore │ │ ├── README.md │ │ ├── package.json │ │ ├── src │ │ │ ├── consts.js │ │ │ ├── defaults.js │ │ │ ├── index.js │ │ │ ├── lifeEvents.js │ │ │ ├── lifePack.js │ │ │ ├── tests │ │ │ │ ├── lifeEvents.test.js │ │ │ │ ├── lifePack.test.js │ │ │ │ ├── mocks │ │ │ │ │ └── rpldy-life-events.mock.js │ │ │ │ └── utils.test.js │ │ │ ├── types.js │ │ │ └── utils.js │ │ └── types │ │ │ ├── index.d.ts │ │ │ └── index.test-d.tsx │ ├── mock-sender │ │ ├── .npmignore │ │ ├── MockSender.stories.js │ │ ├── MockSender.storydoc.mdx │ │ ├── README.md │ │ ├── package.json │ │ ├── src │ │ │ ├── consts.js │ │ │ ├── defaults.js │ │ │ ├── getMockSenderEnhancer.js │ │ │ ├── index.js │ │ │ ├── mockSender.js │ │ │ ├── tests │ │ │ │ ├── getMockSenderEnhancer.test.js │ │ │ │ └── mockSender.test.js │ │ │ └── types.js │ │ └── types │ │ │ ├── index.d.ts │ │ │ └── index.test-d.tsx │ ├── raw-uploader │ │ ├── .npmignore │ │ ├── README.md │ │ ├── package.json │ │ ├── src │ │ │ ├── index.js │ │ │ └── types.js │ │ └── types │ │ │ ├── index.d.ts │ │ │ └── index.test-d.ts │ ├── retry │ │ ├── .npmignore │ │ ├── README.md │ │ ├── package.json │ │ ├── src │ │ │ ├── consts.js │ │ │ ├── index.js │ │ │ ├── retry.js │ │ │ ├── tests │ │ │ │ └── retry.test.js │ │ │ └── types.js │ │ └── types │ │ │ ├── index.d.ts │ │ │ └── index.test-d.tsx │ ├── safe-storage │ │ ├── .npmignore │ │ ├── README.md │ │ ├── package.json │ │ ├── src │ │ │ ├── index.js │ │ │ ├── safeStorageCreator.js │ │ │ ├── storage.js │ │ │ ├── tests │ │ │ │ ├── localStorage.test.js │ │ │ │ └── sessionStorage.test.js │ │ │ └── types.js │ │ └── types │ │ │ ├── index.d.ts │ │ │ └── index.test-d.ts │ ├── sender │ │ ├── .npmignore │ │ ├── README.md │ │ ├── package.json │ │ ├── src │ │ │ ├── MissingUrlError.js │ │ │ ├── consts.js │ │ │ ├── index.js │ │ │ ├── types.js │ │ │ └── xhrSender │ │ │ │ ├── prepareFormData.js │ │ │ │ ├── tests │ │ │ │ ├── prepareFormData.test.js │ │ │ │ └── xhrSender.test.js │ │ │ │ └── xhrSender.js │ │ └── types │ │ │ ├── index.d.ts │ │ │ └── index.test-d.tsx │ ├── shared │ │ ├── .npmignore │ │ ├── README.md │ │ ├── package.json │ │ ├── src │ │ │ ├── batchItem.js │ │ │ ├── consts.js │ │ │ ├── enums.js │ │ │ ├── index.js │ │ │ ├── logger.js │ │ │ ├── request │ │ │ │ ├── XhrPromise.js │ │ │ │ ├── index.js │ │ │ │ ├── parseResponseHeaders.js │ │ │ │ ├── request.js │ │ │ │ └── tests │ │ │ │ │ ├── parseResponseHeaders.test.js │ │ │ │ │ └── request.test.js │ │ │ ├── tests │ │ │ │ ├── batchItem.test.js │ │ │ │ ├── logger.test.js │ │ │ │ ├── mocks │ │ │ │ │ └── rpldy-shared.mock.js │ │ │ │ ├── triggerCancellable.test.js │ │ │ │ └── triggerUpdater.test.js │ │ │ ├── triggerCancellable.js │ │ │ ├── triggerUpdater.js │ │ │ ├── types.js │ │ │ └── utils │ │ │ │ ├── clone.js │ │ │ │ ├── devFreeze.js │ │ │ │ ├── hasWindow.js │ │ │ │ ├── index.js │ │ │ │ ├── isEmpty.js │ │ │ │ ├── isFunction.js │ │ │ │ ├── isPlainObject.js │ │ │ │ ├── isProduction.js │ │ │ │ ├── isPromise.js │ │ │ │ ├── isSamePropInArrays.js │ │ │ │ ├── merge.js │ │ │ │ ├── pick.js │ │ │ │ ├── scheduleIdleWork.js │ │ │ │ └── tests │ │ │ │ ├── clone.test.js │ │ │ │ ├── devFreeze.test.js │ │ │ │ ├── isFunction.test.js │ │ │ │ ├── isPlainObject.test.js │ │ │ │ ├── isPromise.test.js │ │ │ │ ├── isSamePropInArrays.test.js │ │ │ │ ├── merge.test.js │ │ │ │ ├── pick.test.js │ │ │ │ └── scheduleIdleWork.test.js │ │ └── types │ │ │ ├── index.d.ts │ │ │ └── index.test-d.tsx │ ├── simple-state │ │ ├── .npmignore │ │ ├── README.md │ │ ├── package.json │ │ ├── src │ │ │ ├── consts.js │ │ │ ├── createState.js │ │ │ ├── index.js │ │ │ ├── tests │ │ │ │ ├── createState-prod.test.js │ │ │ │ ├── createState.test.js │ │ │ │ ├── mocks │ │ │ │ │ └── getTestData.mock.js │ │ │ │ └── unwrap.test.js │ │ │ ├── types.js │ │ │ └── utils.js │ │ └── types │ │ │ ├── index.d.ts │ │ │ └── index.test-d.ts │ ├── tus-sender │ │ ├── .npmignore │ │ ├── README.md │ │ ├── TusSender.stories.js │ │ ├── TusSender.storydoc.mdx │ │ ├── package.json │ │ ├── src │ │ │ ├── consts.js │ │ │ ├── defaults.js │ │ │ ├── featureDetection.js │ │ │ ├── getTusEnhancer.js │ │ │ ├── index.js │ │ │ ├── resumableStore.js │ │ │ ├── tests │ │ │ │ ├── featureDetection.test.js │ │ │ │ ├── getTusEnhancer.test.js │ │ │ │ ├── resumableStore.test.js │ │ │ │ ├── tusState.mock.js │ │ │ │ └── utils.test.js │ │ │ ├── tusSender │ │ │ │ ├── createTusSender.js │ │ │ │ ├── handleEvents.js │ │ │ │ ├── handleParallelTusUpload.js │ │ │ │ ├── handleTusUpload.js │ │ │ │ ├── index.js │ │ │ │ ├── initTusUpload │ │ │ │ │ ├── createStateItemData.js │ │ │ │ │ ├── createUpload.js │ │ │ │ │ ├── index.js │ │ │ │ │ ├── initParallelTusUpload.js │ │ │ │ │ ├── initTusUpload.js │ │ │ │ │ ├── resumeUpload.js │ │ │ │ │ └── tests │ │ │ │ │ │ ├── createStateItemData.test.js │ │ │ │ │ │ ├── createUpload.test.js │ │ │ │ │ │ ├── initParallelTusUpload.test.js │ │ │ │ │ │ ├── initTusUpload.test.js │ │ │ │ │ │ └── resumeUpload.test.js │ │ │ │ ├── tests │ │ │ │ │ ├── createTusSender.test.js │ │ │ │ │ ├── hadnleEvents-no-chunking.test.js │ │ │ │ │ ├── hadnleEvents-with-chunking.test.js │ │ │ │ │ ├── handleParallelTusUpload.test.js │ │ │ │ │ ├── handleTusUpload.test.js │ │ │ │ │ ├── tusSend-no-chunking.test.js │ │ │ │ │ ├── tusSend-with-chunking.test.js │ │ │ │ │ └── utils.test.js │ │ │ │ ├── tusSend.js │ │ │ │ ├── types.js │ │ │ │ └── utils.js │ │ │ ├── types.js │ │ │ └── utils.js │ │ └── types │ │ │ ├── index.d.ts │ │ │ └── index.test-d.ts │ └── uploader │ │ ├── .npmignore │ │ ├── README.md │ │ ├── Uploader.stories.js │ │ ├── Uploader.storydoc.mdx │ │ ├── package.json │ │ ├── src │ │ ├── batch.js │ │ ├── batchItemsSender.js │ │ ├── composeEnhancers.js │ │ ├── consts.js │ │ ├── defaults.js │ │ ├── index.js │ │ ├── processor.js │ │ ├── queue │ │ │ ├── batchHelpers.js │ │ │ ├── index.js │ │ │ ├── itemHelpers.js │ │ │ ├── preSendPrepare.js │ │ │ ├── processAbort.js │ │ │ ├── processBatchItems.js │ │ │ ├── processFinishedRequest.js │ │ │ ├── processQueueNext.js │ │ │ ├── tests │ │ │ │ ├── batchHelpers.test.js │ │ │ │ ├── itemHelpers.test.js │ │ │ │ ├── mocks │ │ │ │ │ └── getQueueState.mock.js │ │ │ │ ├── preSendPrepare.test.js │ │ │ │ ├── processAbort.test.js │ │ │ │ ├── processBatchItems.test.js │ │ │ │ ├── processFinishedRequest.test.js │ │ │ │ ├── processQueueNext.test.js │ │ │ │ └── uploaderQueue.test.js │ │ │ ├── types.js │ │ │ └── uploaderQueue.js │ │ ├── tests │ │ │ ├── batch.test.js │ │ │ ├── batchItemsSender.test.js │ │ │ ├── composeEnhancers.test.js │ │ │ ├── mocks │ │ │ │ └── rpldy-uploader.mock.js │ │ │ ├── processor.test.js │ │ │ ├── uploader.test.js │ │ │ └── utils.test.js │ │ ├── types.js │ │ ├── uploader.js │ │ └── utils.js │ │ └── types │ │ ├── index.d.ts │ │ └── index.test-d.ts ├── native │ └── native-uploady │ │ ├── .npmignore │ │ ├── NativeUploady.stories.js │ │ ├── NativeUploady.storydoc.mdx │ │ ├── README.md │ │ ├── package.json │ │ ├── src │ │ ├── NativeUploady.js │ │ ├── index.js │ │ ├── tests │ │ │ └── NativeUploady.test.jsx │ │ └── types.js │ │ └── types │ │ ├── index.d.ts │ │ └── index.test-d.tsx └── ui │ ├── chunked-uploady │ ├── .npmignore │ ├── ChunkedUploady.stories.js │ ├── ChunkedUploady.storydoc.mdx │ ├── README.md │ ├── package.json │ ├── src │ │ ├── ChunkedUploady.js │ │ ├── chunkEventListenerHooks.js │ │ ├── index.js │ │ ├── tests │ │ │ ├── ChunkedUploady-no-hunking.test.jsx │ │ │ ├── ChunkedUploady-with-chunking.test.jsx │ │ │ └── chunkEventListenerHooks.test.js │ │ └── types.js │ └── types │ │ ├── index.d.ts │ │ └── index.test-d.tsx │ ├── retry-hooks │ ├── .npmignore │ ├── README.md │ ├── RetryHooks.stories.js │ ├── RetryHooks.storydoc.mdx │ ├── package.json │ ├── src │ │ ├── consts.js │ │ ├── index.js │ │ ├── tests │ │ │ ├── useBatchRetry.test.js │ │ │ ├── useRetry.test.js │ │ │ └── useRetryListener.test.js │ │ ├── types.js │ │ ├── useBatchRetry.js │ │ ├── useRetry.js │ │ └── useRetryListener.js │ └── types │ │ ├── index.d.ts │ │ └── index.test-d.tsx │ ├── shared │ ├── .npmignore │ ├── README.md │ ├── package.json │ ├── src │ │ ├── NoDomUploady.js │ │ ├── UploadyContext.js │ │ ├── assertContext.js │ │ ├── consts.js │ │ ├── hocs │ │ │ ├── createRequestUpdateHoc.js │ │ │ ├── tests │ │ │ │ ├── withBatchStartUpdate.test.jsx │ │ │ │ └── withRequestPreSendUpdate.test.jsx │ │ │ ├── withBatchStartUpdate.js │ │ │ └── withRequestPreSendUpdate.js │ │ ├── hooks │ │ │ ├── eventListenerHooks.js │ │ │ ├── hooksUtils.js │ │ │ ├── tests │ │ │ │ ├── eventListenerHooks.test.js │ │ │ │ ├── hooksUtils.test.js │ │ │ │ ├── useAbortAll.test.js │ │ │ │ ├── useAbortBatch.test.js │ │ │ │ ├── useAbortItem.test.js │ │ │ │ ├── useUploadOptions.test.js │ │ │ │ ├── useUploader.test.js │ │ │ │ └── useUploadyContext.test.js │ │ │ ├── useAbortAll.js │ │ │ ├── useAbortBatch.js │ │ │ ├── useAbortItem.js │ │ │ ├── useUploadOptions.js │ │ │ ├── useUploader.js │ │ │ └── useUploadyContext.js │ │ ├── index.js │ │ ├── tests │ │ │ ├── NoDomUploady.test.jsx │ │ │ ├── UploadyContext.test.js │ │ │ ├── assertContext.test.js │ │ │ ├── mocks │ │ │ │ ├── UploadyContext.mock.jsx │ │ │ │ └── rpldy-ui-shared.mock.jsx │ │ │ ├── uploadyVersion.test.js │ │ │ └── utils.test.jsx │ │ ├── types.js │ │ ├── uploadyVersion.js │ │ └── utils.js │ └── types │ │ ├── index.d.ts │ │ └── index.test-d.tsx │ ├── tus-uploady │ ├── .npmignore │ ├── README.md │ ├── TusUploady.stories.js │ ├── TusUploady.storydoc.mdx │ ├── package.json │ ├── src │ │ ├── TusUploady.js │ │ ├── consts.js │ │ ├── index.js │ │ ├── tests │ │ │ ├── TusUploady.withChunkSupport.test.jsx │ │ │ ├── TusUploady.withoutChunkSupport.test.jsx │ │ │ ├── useClearResumablesStore.test.js │ │ │ └── useTusResumeStartListener.test.js │ │ ├── types.js │ │ ├── useClearResumableStore.js │ │ └── useTusResumeStartListener.js │ └── types │ │ ├── index.d.ts │ │ └── index.test-d.tsx │ ├── upload-button │ ├── .npmignore │ ├── README.md │ ├── UploadButton.stories.js │ ├── UploadButton.storydoc.mdx │ ├── package.json │ ├── src │ │ ├── UploadButton.js │ │ ├── UploadButton.test.jsx │ │ ├── asUploadButton.js │ │ ├── asUploadButton.test.jsx │ │ ├── index.js │ │ └── types.js │ └── types │ │ ├── index.d.ts │ │ └── index.test-d.tsx │ ├── upload-drop-zone │ ├── .npmignore │ ├── README.md │ ├── UploadDropZone.stories.js │ ├── UploadDropZone.storydoc.mdx │ ├── package.json │ ├── src │ │ ├── UploadDropZone.js │ │ ├── UploadDropZone.test.jsx │ │ ├── index.js │ │ └── types.js │ └── types │ │ ├── index.d.ts │ │ └── index.test-d.tsx │ ├── upload-paste │ ├── .npmignore │ ├── README.md │ ├── UploadPaste.stories.js │ ├── UploadPaste.storydoc.mdx │ ├── package.json │ ├── src │ │ ├── index.js │ │ ├── tests │ │ │ ├── usePasteHandler.test.jsx │ │ │ ├── usePasteUpload.test.jsx │ │ │ └── withPasteUpload.test.jsx │ │ ├── types.js │ │ ├── usePasteHandler.js │ │ ├── usePasteUpload.js │ │ └── withPasteUpload.js │ └── types │ │ ├── index.d.ts │ │ └── index.test-d.tsx │ ├── upload-preview │ ├── .npmignore │ ├── README.md │ ├── UploadPreview.stories.js │ ├── UploadPreview.storydoc.mdx │ ├── package.json │ ├── src │ │ ├── UploadPreview.js │ │ ├── consts.js │ │ ├── defaults.js │ │ ├── index.js │ │ ├── tests │ │ │ ├── UploadPreview.test.jsx │ │ │ ├── usePreviewsLoader.test.js │ │ │ └── utils.test.js │ │ ├── types.js │ │ ├── usePreviewsLoader.js │ │ └── utils.js │ └── types │ │ ├── index.d.ts │ │ └── index.test-d.tsx │ ├── upload-url-input │ ├── .npmignore │ ├── README.md │ ├── UploadUrlInput.stories.js │ ├── UploadUrlInput.storydoc.mdx │ ├── package.json │ ├── src │ │ ├── UploadUrlInput.js │ │ ├── UploadUrlInput.test.jsx │ │ ├── index.js │ │ └── types.js │ └── types │ │ ├── index.d.ts │ │ └── index.test-d.tsx │ └── uploady │ ├── .npmignore │ ├── README.md │ ├── Uploady.stories.js │ ├── Uploady.storydoc.mdx │ ├── all-bundle-entry.js │ ├── package.json │ ├── src │ ├── Uploady.js │ ├── index.js │ ├── tests │ │ ├── Uploady.test.jsx │ │ ├── mocks │ │ │ └── rpldy-uploady.mock.jsx │ │ └── useFileInput.test.jsx │ └── useFileInput.js │ └── types │ ├── index.d.ts │ └── index.test-d.tsx ├── pnpm-lock.yaml ├── pnpm-workspace.yaml ├── scripts ├── build.mjs ├── bundle.mjs ├── ensureTypes.mjs ├── extractChangeLogNotes.js ├── lernaUtils.mjs ├── parallel-e2e.mjs ├── reportBundleSizeToGithub.mjs ├── upgradeDep.mjs ├── uploadyVersion.mjs └── utils.mjs ├── story-helpers ├── ComponentsStyles.js ├── ProgressReportTable.jsx ├── ReactCropWithImage.jsx ├── StoryAbortButton.jsx ├── StoryUploadProgress.jsx ├── UmdBundleScript.jsx ├── Welcome.jsx ├── consts.js ├── createUploadyStory.jsx ├── cropImage.js ├── dropZoneCss.js ├── getCsfExport.js ├── helpers.js ├── index.js ├── storySetupControls │ ├── args.js │ └── useStoryUploadySetupFromArgs.js ├── uploadDestinations.js ├── uploadyStoryLogger.js ├── useEventsLogUpdater.js ├── useExternalUploadOptionsProvider.js └── useScript.js ├── test ├── umd-bundle │ ├── rpldy-all.html │ ├── rpldy-core.html │ ├── rpldy-ui-core-chunked.html │ └── rpldy-ui-core.html └── vitest-setup.mjs ├── tsconfig.json └── vite.config.mjs /.browserslistrc: -------------------------------------------------------------------------------- 1 | defaults and > 1% 2 | not IE 11 3 | not IE_Mob 11 4 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | 3 | root = true 4 | 5 | [*] 6 | charset = utf-8 7 | indent_style = space 8 | indent_size = 4 9 | end_of_line = lf 10 | insert_final_newline = true 11 | trim_trailing_whitespace = true 12 | 13 | [*.md] 14 | insert_final_newline = false 15 | trim_trailing_whitespace = false 16 | -------------------------------------------------------------------------------- /.github/.nvmrc: -------------------------------------------------------------------------------- 1 | v20.15.1 2 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: [yoavniran] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: react-uploady 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 13 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a bug report 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | --- 8 | 9 | > Recommended - Check out the [discussions area](https://github.com/rpldy/react-uploady/discussions) before opening a new issue. 10 | 11 | **Describe the bug** 12 | A clear and concise description of what the bug is. 13 | 14 | **To Reproduce** 15 | Steps to reproduce the behavior: 16 | 1. Go to '...' 17 | 2. Click on '....' 18 | 3. Scroll down to '....' 19 | 4. See error 20 | 21 | **Expected behavior** 22 | A clear and concise description of what you expected to happen. 23 | 24 | **Versions** 25 | Specify the versions of the @rpldy packages you're using. 26 | Which browser this bug reproduces in. 27 | 28 | **Code** 29 | Please provide code sample or better yet, a link to a reproducing repository or best yet, codesandbox (fiddle, etc) where issue can be reproduced easily 30 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | > Recommended - Check out the [discussions area](https://github.com/rpldy/react-uploady/discussions) before opening a new issue. 11 | 12 | 13 | **Is your feature request related to a problem? Please describe.** 14 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 15 | 16 | **Describe the solution you'd like** 17 | A clear and concise description of what you want to happen. 18 | 19 | **Describe alternatives you've considered** 20 | A clear and concise description of any alternative solutions or features you've considered. 21 | 22 | **Additional context** 23 | Add any other context or screenshots about the feature request here. 24 | -------------------------------------------------------------------------------- /.github/actions/actions-token-unused/action.yml: -------------------------------------------------------------------------------- 1 | name: GH Actions Tokens 2 | description: Retrieve env variables for GH Actions 3 | 4 | outputs: 5 | ACTIONS_RUNTIME_TOKEN: 6 | description: The runtime token for GH Actions 7 | value: ${{ steps.get-gh-actions-tokens.outputs.runtimeToken }} 8 | ACTIONS_RESULTS_URL: 9 | description: The results URL for GH Actions 10 | value: ${{ steps.get-gh-actions-tokens.outputs.resultsUrl }} 11 | 12 | runs: 13 | using: composite 14 | steps: 15 | - name: Get GH Actions Tokens 16 | id: get-gh-actions-tokens 17 | uses: actions/github-script@v7 18 | with: 19 | script: | 20 | const script = require('./.github/actions/actions-token/index.js'); 21 | return await script({ fetch, github, context, core }); 22 | -------------------------------------------------------------------------------- /.github/actions/actions-token-unused/index.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports = async ({ core }) => { 3 | const runtimeToken = process.env.ACTIONS_RUNTIME_TOKEN; 4 | core.setOutput("runtimeToken", runtimeToken); 5 | 6 | const resultsUrl = process.env.ACTIONS_RESULTS_URL; 7 | core.setOutput("resultsUrl", resultsUrl); 8 | 9 | core.info("Got GH Actions Tokens and set into step output"); 10 | }; 11 | -------------------------------------------------------------------------------- /.github/actions/save-versions-to-wf/action.yml: -------------------------------------------------------------------------------- 1 | name: Save Uploady Versions to Workflow 2 | description: Fetch and save Uploady versions to workflow version input 3 | 4 | inputs: 5 | workflow-file: 6 | description: Path to workflow file 7 | required: true 8 | workflow-input: 9 | description: Name of the input to update with version 10 | default: "version" 11 | required: false 12 | 13 | runs: 14 | using: "node20" 15 | main: "index.js" 16 | -------------------------------------------------------------------------------- /.github/actions/versionLog/action.yml: -------------------------------------------------------------------------------- 1 | name: "Extract Version Changelog" 2 | description: "Take the log information from the changelog" 3 | outputs: 4 | VERSION: 5 | description: "The new semver version to be released" 6 | VERSION_LOG: 7 | description: "The log info for the new release" 8 | runs: 9 | using: "node20" 10 | main: "index.js" 11 | -------------------------------------------------------------------------------- /.github/actions/versionLog/index.js: -------------------------------------------------------------------------------- 1 | const core = require("@actions/core"), 2 | { extractChangelogNotesForCurrentVersion } = require("../../../scripts/extractChangeLogNotes"); 3 | 4 | const extractVersionLog = async () => { 5 | try { 6 | console.log("about to retrieve version info from CHANGELOG"); 7 | const { version, versionLog } = await extractChangelogNotesForCurrentVersion(); 8 | 9 | if (!versionLog) { 10 | core.setFailed(`Failed to retrieve log info for ${version}`); 11 | } else { 12 | core.setOutput("VERSION", version); 13 | core.setOutput("VERSION_LOG", versionLog); 14 | 15 | console.log(`Retrieved version ${version} log = `, versionLog); 16 | } 17 | } catch (ex) { 18 | core.setFailed(ex.message); 19 | } 20 | }; 21 | 22 | extractVersionLog(); 23 | 24 | // github = require("@actions/github"), 25 | //core.getInput('who-to-greet'); 26 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "npm" 9 | directory: "/" 10 | schedule: 11 | interval: "monthly" 12 | -------------------------------------------------------------------------------- /.github/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "uploady github workflows", 3 | "version": "1.0.0", 4 | "private": true, 5 | "dependencies": { 6 | "@actions/core": "^1.10.0", 7 | "@actions/github": "^6.0.0", 8 | "js-yaml": "^4.1.0" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /.github/workflows/stalebot.yml: -------------------------------------------------------------------------------- 1 | name: 'Close stale issues' 2 | on: 3 | schedule: 4 | - cron: '30 1 * * *' 5 | workflow_dispatch: 6 | 7 | permissions: 8 | issues: write 9 | 10 | jobs: 11 | stale: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/stale@v8 15 | with: 16 | days-before-stale: 10 17 | days-before-close: 5 18 | stale-issue-message: "It's been a while. Waiting for an update... 🧐" 19 | close-issue-message: "It's been too long. Closing issue for now. 😿" 20 | stale-issue-label: 'stale' 21 | exempt-issue-labels: 'in-progress, no-stale' 22 | remove-issue-stale-when-updated: true 23 | remove-stale-when-updated: true 24 | -------------------------------------------------------------------------------- /.github/workflows/storybook-publish.yml: -------------------------------------------------------------------------------- 1 | name: Uploady Storybook Publish 2 | 3 | on: 4 | workflow_dispatch: 5 | push: 6 | branches: 7 | - master 8 | 9 | defaults: 10 | run: 11 | shell: bash 12 | 13 | permissions: 14 | contents: read 15 | 16 | jobs: 17 | publish: 18 | name: Publish Uploady Storybook 19 | runs-on: ubuntu-latest 20 | steps: 21 | - uses: actions/checkout@v4 22 | 23 | - name: Prepare 24 | uses: ./.github/actions/prepare 25 | 26 | - name: Build Storybook Site 27 | run: pnpm sb:build:prod 28 | 29 | - name: Install Netlify CLI 30 | run: pnpm install -w netlify-cli 31 | 32 | - name: Publish Storybook to Netlify 33 | env: 34 | NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }} 35 | NETLIFY_SITE_ID: ${{ secrets.NETLIFY_SITE_ID }} 36 | run: | 37 | pnpm netlify deploy --prod --debug --cwd .sb-static 38 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | .vscode/ 3 | .DS_Store 4 | .jest-cache/ 5 | .jest-coverage/ 6 | .sb-static/ 7 | .env 8 | .eslintcache 9 | 10 | reports/ 11 | coverage 12 | node_modules 13 | stats.json 14 | dist 15 | output 16 | build 17 | bundle/ 18 | packages/**/umd 19 | .history 20 | storybook-static 21 | npm-debug.log 22 | lerna-debug.log 23 | yarn-error.log 24 | 25 | jsexample.js 26 | jsexample2.js 27 | test.js 28 | todo.md 29 | 30 | packages/**/lib 31 | packages/**/.flowconfig 32 | packages/**/LICENSE.md 33 | 34 | /test.js 35 | /experiment.js 36 | 37 | cypress/videos/ 38 | cypress/screenshots/ 39 | cypress/examples 40 | cypress/results/ 41 | runner-results/ 42 | 43 | tstest/ 44 | 45 | server/tusFiles/ 46 | test/react-uploady-ssr/ 47 | 48 | cypress/test-bundle 49 | 50 | # Local Netlify folder 51 | .netlify 52 | .nx/ 53 | /cypress/downloads 54 | /test.mjs 55 | test/test-bundle-size.mjs 56 | 57 | flow-bin-doc.txt 58 | -------------------------------------------------------------------------------- /.husky/commit-msg: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | npx --no -- commitlint --edit "$1" 5 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | pnpm flow && pnpm lint --quiet 5 | -------------------------------------------------------------------------------- /.husky/pre-push: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | src/ 2 | *.story.js 3 | *.stories.js 4 | *.test.js 5 | tests/ 6 | mocks/ 7 | lerna.json 8 | *.log 9 | jest* 10 | .flowconfig 11 | .editorconfig 12 | .eslintignore 13 | .eslintrc.js 14 | .circleci/ 15 | .sb-static/ 16 | .env 17 | .jest-cache/ 18 | .jest-coverage/ 19 | coverage/ 20 | .github/ 21 | 22 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | # pnpm - flow-bin requires hoisting of deps 2 | public-hoist-pattern[]=invariant 3 | public-hoist-pattern[]=html-dir-content 4 | public-hoist-pattern[]=react-dnd 5 | public-hoist-pattern[]=react-image-crop 6 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | v20.15.1 2 | -------------------------------------------------------------------------------- /.storybook/VersionBadge.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import styled from "styled-components"; 3 | 4 | const Badge = styled.span` 5 | font-weight: bold; 6 | background-color: #0a173c; 7 | border-radius: 6px; 8 | padding: 2px 4px; 9 | font-size: 10px; 10 | top: -2px; 11 | position: relative; 12 | color: #ffffff; 13 | margin-left: 4px; 14 | `; 15 | 16 | const VersionBadge = ({ pkg, preText = "", className, withUrl, versions }) => { 17 | const info = !!pkg && 18 | versions.find(({ name }) => name === pkg); 19 | 20 | const badge = info ? 23 | {preText}{info.version} 24 | : null; 25 | 26 | return info && 27 | (withUrl ? 28 | {badge} : 33 | badge); 34 | }; 35 | 36 | export default VersionBadge; 37 | -------------------------------------------------------------------------------- /.storybook/cypressAddon/cypressDecorator.ts: -------------------------------------------------------------------------------- 1 | import { makeDecorator } from "@storybook/preview-api"; 2 | 3 | export default makeDecorator({ 4 | name: "cypressDecorator", 5 | wrapper: (getStory, context) => { 6 | const win = (window.parent && window.parent.Cypress) ? 7 | window.parent : 8 | (window.Cypress ? window : null); 9 | 10 | if (win) { 11 | win.__cypressEnv = process.env.NODE_ENV; 12 | win.__cypressResults = win.__cypressResults || { storyLog: []}; 13 | 14 | //clear story log on each story render 15 | win.__cypressResults.storyLog = []; 16 | delete win.__extUploadOptions; 17 | delete win.__extPreSendOptions; 18 | 19 | //TODO: this isnt a good practice to keep these special params in different places in the code. Need to refactor to cleaner solution 20 | } 21 | 22 | return getStory(context); 23 | }, 24 | parameterName: "cypressDecorator", 25 | skipIfNoParametersOrOptions: false, 26 | }); 27 | -------------------------------------------------------------------------------- /.storybook/manager.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { addons } from "@storybook/manager-api"; 3 | import theme from "./theme"; 4 | import VersionBadge from "./VersionBadge"; 5 | 6 | addons.setConfig({ 7 | // isFullscreen: false, 8 | // showAddonsPanel: true, 9 | // panelPosition: 'right', 10 | selectedPanel: "REACT_STORYBOOK/readme/panel", 11 | theme, 12 | sidebar: { 13 | renderLabel: ({ name }) => { 14 | //storybook doesnt pass all the CSF info for isComponent items :( 15 | const pkg = window._storyToPackage?.[name]; 16 | 17 | //before SB7 it was possible to add the Versions through the manager's webpack build but no longer... :( 18 | const versions = document.getElementById("storybook-preview-iframe").contentWindow._getPackageVersions?.(); 19 | 20 | return (
21 | {name} 22 | {pkg && versions && 23 | } 27 |
); 28 | }, 29 | } 30 | }); 31 | -------------------------------------------------------------------------------- /.storybook/preview-head.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.storybook/theme.ts: -------------------------------------------------------------------------------- 1 | import { create } from "@storybook/theming"; 2 | 3 | export default create({ 4 | base: "light", 5 | 6 | colorPrimary: "#092358", 7 | colorSecondary: "#1b5c85", 8 | appBg: "#dbdbdb", 9 | inputBorder: "#15b2d4", 10 | // textColor: 'black', 11 | textInverseColor: "#fff", 12 | barTextColor: "#273f3f", 13 | // barSelectedColor: 'black', 14 | // barBg: 'hotpink', 15 | 16 | // inputBg: 'white', 17 | // inputTextColor: 'black', 18 | // inputBorderRadius: 4, 19 | 20 | brandUrl: "https://react-uploady.org", 21 | brandImage: "https://res.cloudinary.com/yoav-cloud/image/upload/w_300/v22212321/rpldy/logo/react-uploady-text-logo.png", 22 | }); 23 | -------------------------------------------------------------------------------- /.storybook/welcome.storydoc.mdx: -------------------------------------------------------------------------------- 1 | import { Meta } from "@storybook/blocks"; 2 | import { WelcomeReactUploady } from "../story-helpers/Welcome"; 3 | 4 | 5 | 6 | 7 | 8 |
9 | Support us on Open Collective 10 | 11 | 12 | Open Collective 13 | 14 | 15 | 16 |
17 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Yoav Niran 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Supported Versions 4 | 5 | The following table describes the versions of this project that are currently supported with security updates: 6 | 7 | | Version | Supported | 8 | | ------- | ------------------ | 9 | | 1.x | :white_check_mark: | 10 | | 0.x | :x: | 11 | 12 | ## Reporting a Vulnerability 13 | 14 | We consider the security a top priority and will work to resolve any vulnerabilities. 15 | 16 | If you believe you have found a security vulnerability in React-Uploady, we encourage you to report it to us know right away. 17 | We will investigate all credible reports, and do our best to quickly fix the issue. 18 | 19 | To report, simply open a new [isuse](https://github.com/rpldy/react-uploady/issues) 20 | 21 | We appreciate any assistance and support by reporting issues quickly and responsibly. 22 | -------------------------------------------------------------------------------- /bundle-size-report.json: -------------------------------------------------------------------------------- 1 | [{"name":"all","size":"23.66KB","max":25000,"success":true},{"name":"core","size":"10.63KB","max":12000,"success":true},{"name":"ui-core","size":"13.57KB","max":15000,"success":true},{"name":"ui-core-chunked","size":"16.04KB","max":17500,"success":true}] -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | comment: 2 | layout: "reach, diff, flags, files" 3 | behavior: default 4 | require_changes: false # if true: only post the comment if coverage changes 5 | require_base: no # [yes :: must have a base report to post] 6 | require_head: yes # [yes :: must have a head report to post] 7 | branches: # branch names that can post comment 8 | - "master" 9 | -------------------------------------------------------------------------------- /commitlint.config.mjs: -------------------------------------------------------------------------------- 1 | export default { 2 | extends: [ 3 | "@commitlint/config-conventional", 4 | "@commitlint/config-lerna-scopes" 5 | ], 6 | 7 | "rules": { 8 | "header-max-length": [2, "always", 120], 9 | 10 | } 11 | }; 12 | -------------------------------------------------------------------------------- /cypress/constants.js: -------------------------------------------------------------------------------- 1 | export const DEFAULT_URL = "http://test.upload/url"; 2 | 3 | export const UPLOAD_URL = Cypress.env("UPLOAD_URL") || DEFAULT_URL; 4 | 5 | export const DEFAULT_METHOD = "POST"; 6 | 7 | export const WAIT_X_SHORT = 200; 8 | 9 | export const WAIT_SHORT = 800; 10 | 11 | export const WAIT_MEDIUM = 1500; 12 | 13 | export const WAIT_LONG = 2500; 14 | 15 | export const WAIT_X_LONG = 3000; 16 | 17 | export const BATCH_ADD = /BATCH_ADD/; 18 | 19 | export const BATCH_ERROR = /BATCH_ERROR/; 20 | 21 | export const BATCH_FINALIZE = /BATCH_FINALIZE/; 22 | 23 | export const BATCH_ABORT = /BATCH_ABORT/; 24 | 25 | export const BATCH_PROGRESS = /BATCH_PROGRESS/; 26 | 27 | export const ITEM_START = /ITEM_START/; 28 | 29 | export const ITEM_ABORT = /ITEM_ABORT/; 30 | 31 | export const ITEM_PROGRESS = /ITEM_PROGRESS/; 32 | 33 | export const ITEM_FINISH = /ITEM_FINISH/; 34 | 35 | export const ITEM_ERROR = /ITEM_ERROR/; 36 | 37 | export const ITEM_CANCEL = /ITEM_CANCEL/; 38 | 39 | export const CHUNK_START = /CHUNK_START/; 40 | 41 | export const CHUNK_FINISH = /CHUNK_FINISH/; 42 | 43 | export const ALL_ABORT = /ALL_ABORT/; 44 | -------------------------------------------------------------------------------- /cypress/fixtures/flower.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rpldy/react-uploady/2c92e126c8b2e583fc6180b036e49d62e3e00a5c/cypress/fixtures/flower.jpg -------------------------------------------------------------------------------- /cypress/fixtures/sea.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rpldy/react-uploady/2c92e126c8b2e583fc6180b036e49d62e3e00a5c/cypress/fixtures/sea.jpg -------------------------------------------------------------------------------- /cypress/integration/chunked-uploady/ChunkedUploady-custom-success-spec.js: -------------------------------------------------------------------------------- 1 | import { interceptWithHandler } from "../intercept"; 2 | import uploadFile from "../uploadFile"; 3 | 4 | describe("ChunkedUploady - Custom Success Callback", () => { 5 | const fileName = "flower.jpg"; 6 | 7 | before(() => { 8 | cy.visitStory( 9 | "chunkedUploady", 10 | "simple", 11 | { useMock: false, chunkSize: 50000 } 12 | ); 13 | }); 14 | 15 | it("should work with custom success code", () => { 16 | interceptWithHandler((req) => { 17 | req.reply(308, { success: true }); 18 | }); 19 | 20 | cy.setUploadOptions({ isSuccessfulCall: (xhr) => xhr.status === 308 }); 21 | 22 | uploadFile(fileName, () => { 23 | cy.waitShort(); 24 | 25 | cy.storyLog().assertFileItemStartFinish(fileName, 1); 26 | }); 27 | }); 28 | }); 29 | -------------------------------------------------------------------------------- /cypress/integration/dropFile.js: -------------------------------------------------------------------------------- 1 | import selectFiles from "./selectFiles"; 2 | 3 | const dropFile = (fixtureName, cb, dropZone = "#upload-drop-zone", options = {}) => { 4 | selectFiles( 5 | fixtureName, 6 | dropZone, 7 | "uploadDropZone", 8 | cb, 9 | { ...options, action: "drag-drop", aliasAsInput: true } 10 | ); 11 | }; 12 | 13 | export const dropFiles = (fixtureName, times, cb, dropZone = "#upload-drop-zone", options = {}) => 14 | dropFile(fixtureName, cb, dropZone, { ...options, times }); 15 | 16 | export default dropFile; 17 | -------------------------------------------------------------------------------- /cypress/integration/native-uploady/NativeUploady-simple-spec.js: -------------------------------------------------------------------------------- 1 | import uploadFile from "../uploadFile"; 2 | 3 | describe("NativeUploady - Simple", () => { 4 | const fileName = "flower.jpg"; 5 | 6 | before(() => { 7 | cy.visitStory( 8 | "nativeUploady", 9 | "simple", 10 | { mockDelay: 100 } 11 | ); 12 | }); 13 | 14 | it("should use native uploady", () => { 15 | uploadFile(fileName, () => { 16 | cy.waitExtraShort(); 17 | cy.storyLog().assertFileItemStartFinish(fileName, 1); 18 | }); 19 | }); 20 | }); 21 | -------------------------------------------------------------------------------- /cypress/integration/tus-uploady/clearTusPersistStorage.js: -------------------------------------------------------------------------------- 1 | const clearTusPersistStorage = () => { 2 | for (let i = 0; i < localStorage.length; i++) { 3 | const key = localStorage.key(i); 4 | 5 | if (key && !key.startsWith("__rpldy-tus__")) { 6 | localStorage.removeItem(key); 7 | } 8 | } 9 | }; 10 | 11 | export default clearTusPersistStorage; 12 | -------------------------------------------------------------------------------- /cypress/integration/umd/core-ui-umd-spec.js: -------------------------------------------------------------------------------- 1 | import intercept from "../intercept"; 2 | import uploadFile from "../uploadFile"; 3 | import { ITEM_START, BATCH_ADD } from "../../constants"; 4 | 5 | describe("UMD UI CORE - Bundle", () => { 6 | const fileName = "flower.jpg"; 7 | 8 | beforeEach(() => { 9 | cy.visitStory("uploady", "umd-core-ui", { useMock: false }); 10 | }); 11 | 12 | it("should use uploady and upload file", () => { 13 | intercept(); 14 | 15 | uploadFile(fileName, () => { 16 | cy.wait("@uploadReq") 17 | .interceptFormData((formData) => { 18 | expect(formData["file"]).to.eq(fileName); 19 | }) 20 | .its("response.statusCode") 21 | .should("eq", 200); 22 | 23 | cy.storyLog().assertLogPattern(BATCH_ADD, { times: 1 }); 24 | cy.storyLog().assertLogPattern(ITEM_START, { times: 1 }); 25 | }, "#upload-button"); 26 | }); 27 | }); 28 | -------------------------------------------------------------------------------- /cypress/integration/umd/core-umd-spec.js: -------------------------------------------------------------------------------- 1 | import intercept from "../intercept"; 2 | import uploadFile from "../uploadFile"; 3 | import { ITEM_START, BATCH_ADD } from "../../constants"; 4 | 5 | describe("UMD Core - Bundle", () => { 6 | const fileName = "flower.jpg"; 7 | 8 | beforeEach(() => { 9 | cy.visitStory("uploader", "umd-core", { useMock: false }); 10 | }); 11 | 12 | it("should use uploady with uploader", () => { 13 | intercept(); 14 | 15 | uploadFile(fileName, () => { 16 | cy.wait("@uploadReq") 17 | .interceptFormData((formData) => { 18 | expect(formData["file"]).to.eq(fileName); 19 | }) 20 | .its("response.statusCode").should("eq", 200); 21 | 22 | cy.storyLog().assertLogPattern(BATCH_ADD, { times: 1 }); 23 | cy.storyLog().assertLogPattern(ITEM_START, { times: 1 }); 24 | }, "#upload-button"); 25 | }); 26 | }); 27 | -------------------------------------------------------------------------------- /cypress/integration/upload-button/UploadButton-asButton-spec.js: -------------------------------------------------------------------------------- 1 | import uploadFile from "../uploadFile"; 2 | 3 | describe("UploadButton - With Component asButton", () => { 4 | const fileName = "flower.jpg"; 5 | 6 | before(() => { 7 | cy.visitStory("uploadButton", "with-component-as-button"); 8 | }); 9 | 10 | it("should make any custom component an upload button", () => { 11 | uploadFile(fileName, () => { 12 | cy.waitMedium(); 13 | cy.storyLog().assertFileItemStartFinish(fileName, 1); 14 | }, "#div-upload"); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /cypress/integration/upload-button/UploadButton-customInputAndForm-spec.js: -------------------------------------------------------------------------------- 1 | import intercept from "../intercept"; 2 | import uploadFile from "../uploadFile"; 3 | 4 | describe("UploadButton - With Custom File Input And Form", () => { 5 | const fileName = "flower.jpg"; 6 | 7 | before(() => { 8 | cy.visitStory("uploadButton", "with-custom-file-input-and-form"); 9 | }); 10 | 11 | it("should use form attributes ", () => { 12 | intercept("http://react-uploady-dummy-server.comm"); 13 | 14 | uploadFile(fileName, () => { 15 | cy.wait("@uploadReq").then((xhr) => { 16 | expect(xhr.response.body.success).to.eq(true); 17 | }); 18 | }); 19 | }); 20 | }); 21 | -------------------------------------------------------------------------------- /cypress/integration/upload-button/UploadButton-differentConfig-spec.js: -------------------------------------------------------------------------------- 1 | import uploadFile from "../uploadFile"; 2 | 3 | describe("UploadButton - Different Configuration", () => { 4 | const fileName = "flower.jpg"; 5 | 6 | before(() => { 7 | cy.visitStory("uploadButton", "different-configuration"); 8 | }); 9 | 10 | it("should allow overriding upload options from button", () => { 11 | //test button with autoUpload = false 12 | uploadFile(fileName, () => { 13 | cy.waitExtraShort(); 14 | cy.storyLog().assertLogEntryCount(1); 15 | }, "#upload-a"); 16 | 17 | //test other button with custom destination header 18 | uploadFile(fileName, () => { 19 | cy.waitExtraShort(); 20 | 21 | cy.storyLog().assertLogEntryContains(1, { 22 | destination: { 23 | url: "http://react-uploady-dummy-server.comm", 24 | headers: { 25 | "x-test": "1234" 26 | } 27 | } 28 | }); 29 | }, "#upload-b"); 30 | }); 31 | }); 32 | -------------------------------------------------------------------------------- /cypress/integration/upload-button/UploadButton-disabled-spec.js: -------------------------------------------------------------------------------- 1 | import uploadFile from "../uploadFile"; 2 | 3 | describe("UploadButton - Disabled During Upload", () => { 4 | const fileName = "flower.jpg"; 5 | 6 | before(() => { 7 | cy.visitStory("uploadButton", "disabled-during-upload"); 8 | }); 9 | 10 | it("should disable upload button during upload", () => { 11 | cy.get("input") 12 | .should("exist") 13 | .as("fInput"); 14 | 15 | uploadFile(fileName, () => { 16 | cy.waitExtraShort(); 17 | cy.get("@uploadButton").should("be.disabled"); 18 | 19 | cy.waitMedium(); 20 | cy.storyLog().assertFileItemStartFinish(fileName, 1); 21 | cy.get("@uploadButton").should("not.be.disabled"); 22 | }); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /cypress/integration/upload-button/UploadButton-eventHooks-spec.js: -------------------------------------------------------------------------------- 1 | import uploadFile from "../uploadFile"; 2 | 3 | describe("UploadButton - With Event Hooks", () => { 4 | const fileName = "flower.jpg"; 5 | 6 | before(() => { 7 | cy.visitStory("uploadButton", "with-event-hooks", { mockDelay: 100 }); 8 | }); 9 | 10 | it("should use event hooks", () => { 11 | uploadFile(fileName, () => { 12 | cy.waitExtraShort(); 13 | 14 | cy.get("ul[data-test='hooks-events']") 15 | .should("be.visible") 16 | .as("eventsLog"); 17 | 18 | const eventsItems = cy.get("@eventsLog").find("li"); 19 | 20 | eventsItems.first() 21 | .should("contain", "hooks: Batch Start - batch-1 - item count = 1") 22 | .next() 23 | .should("contain", `hooks: Item Start - batch-1.item-1 : ${fileName}`) 24 | .next() 25 | .should("contain", `hooks: Item Finish - batch-1.item-1 : ${fileName}`) 26 | .next() 27 | .should("contain", "hooks: Batch Finish - batch-1 - item count = 1"); 28 | }); 29 | }); 30 | }); 31 | -------------------------------------------------------------------------------- /cypress/integration/upload-button/UploadButton-eventListeners-spec.js: -------------------------------------------------------------------------------- 1 | import uploadFile from "../uploadFile"; 2 | 3 | describe("UploadButton - With Event Listeners", () => { 4 | const fileName = "flower.jpg"; 5 | 6 | before(() => { 7 | cy.visitStory("uploadButton", "with-event-listeners", { mockDelay: 100 }); 8 | }); 9 | 10 | it("should use event listeners", () => { 11 | uploadFile(fileName, () => { 12 | cy.waitExtraShort(); 13 | 14 | cy.get("ul[data-test='hooks-events']") 15 | .should("be.visible") 16 | .as("eventsLog"); 17 | 18 | const eventsItems = cy.get("@eventsLog").find("li"); 19 | 20 | eventsItems.first() 21 | .should("contain", "Batch Start - batch-1 - item count = 1") 22 | .next() 23 | .should("contain", `Item Start - batch-1.item-1 : ${fileName}`) 24 | .next() 25 | .should("contain", `Item Finish - batch-1.item-1 : ${fileName}`) 26 | .next() 27 | .should("contain", "Batch Finish - batch-1 - item count = 1"); 28 | }); 29 | }); 30 | }); 31 | -------------------------------------------------------------------------------- /cypress/integration/upload-button/UploadButton-simple-multiple-spec.js: -------------------------------------------------------------------------------- 1 | import { uploadFileTimes } from "../uploadFile"; 2 | 3 | describe("UploadButton - Simple - Multiple files", () => { 4 | const fileName = "flower.jpg"; 5 | 6 | before(() => { 7 | cy.visitStory( 8 | "uploadButton", 9 | "simple", 10 | { mockDelay: 100 } 11 | ); 12 | }); 13 | 14 | it("should use uploady to upload multiple files", () => { 15 | cy.get("input") 16 | .should("exist") 17 | .as("fInput"); 18 | 19 | uploadFileTimes(fileName, () => { 20 | cy.waitMedium(); 21 | 22 | cy.storyLog().assertFileItemStartFinish(fileName, 1); 23 | cy.storyLog().assertFileItemStartFinish("flower2.jpg", 3); 24 | cy.storyLog().assertFileItemStartFinish("flower3.jpg", 5); 25 | }, 3); 26 | }); 27 | }); 28 | -------------------------------------------------------------------------------- /cypress/integration/upload-button/UploadButton-simple-spec.js: -------------------------------------------------------------------------------- 1 | import uploadFile from "../uploadFile"; 2 | 3 | describe("UploadButton - Simple", () => { 4 | const fileName = "flower.jpg"; 5 | 6 | before(() => { 7 | cy.visitStory( 8 | "uploadButton", 9 | "simple", 10 | { mockDelay: 100 } 11 | ); 12 | }); 13 | 14 | it("should use uploady", () => { 15 | cy.get("input") 16 | .should("exist") 17 | .as("fInput"); 18 | 19 | uploadFile(fileName, () => { 20 | cy.waitShort(); 21 | cy.storyLog().assertFileItemStartFinish(fileName, 1); 22 | }, "button"); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /cypress/integration/upload-button/UploadButton-styled-spec.js: -------------------------------------------------------------------------------- 1 | import uploadFile from "../uploadFile"; 2 | 3 | describe("UploadButton - With Styled Component", () => { 4 | const fileName = "flower.jpg"; 5 | 6 | before(() => { 7 | cy.visitStory( 8 | "uploadButton", 9 | "with-styled-component", 10 | { mockDelay: 100 } 11 | ); 12 | }); 13 | 14 | it("should be styled with styled-components", () => { 15 | uploadFile(fileName, () => { 16 | cy.get("@uploadButton") 17 | .should("have.css", "background-color", "rgb(1, 9, 22)") 18 | .should("have.css", "color", "rgb(176, 177, 179)"); 19 | 20 | cy.waitShort(); 21 | cy.storyLog().assertFileItemStartFinish(fileName, 1); 22 | }); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /cypress/integration/upload-drop-zone/UploadDropZone-custom-remove-spec.js: -------------------------------------------------------------------------------- 1 | describe("UploadDropZone - Custom Remove", () => { 2 | before(() => { 3 | cy.visitStory("uploadDropZone", "with-full-screen"); 4 | }); 5 | 6 | it("should remove the drag overlay", () => { 7 | cy.get("#upload-drop-zone") 8 | .trigger("dragenter", { dataTransfer: { items: [ { kind: "file" } ] } }); 9 | 10 | cy.get(".dropIndicator") 11 | .should("be.visible"); 12 | 13 | cy.get(".dropIndicator") 14 | .trigger("dragleave"); 15 | 16 | cy.get(".dropIndicator") 17 | .should("not.be.visible"); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /cypress/integration/upload-drop-zone/UploadDropZone-differentConfig-spec.js: -------------------------------------------------------------------------------- 1 | import dropFile from "../dropFile"; 2 | 3 | describe("UploadDropZone - Different Config", () => { 4 | const fileName = "flower.jpg"; 5 | 6 | before(() => { 7 | cy.visitStory("uploadDropZone", "different-configuration"); 8 | }); 9 | 10 | it("should allow overriding upload options from dropzone", () => { 11 | //test button with autoUpload = false 12 | dropFile(fileName, () => { 13 | cy.waitExtraShort(); 14 | cy.storyLog().assertLogEntryCount(1); 15 | }, "#upload-dz-a"); 16 | 17 | //test other button with custom destination header 18 | dropFile(fileName, () => { 19 | cy.waitExtraShort(); 20 | 21 | cy.storyLog().assertLogEntryContains(1, { 22 | destination: { 23 | headers: { 24 | "x-test": "1234" 25 | } 26 | } 27 | }); 28 | }, "#upload-dz-b"); 29 | }); 30 | }); 31 | -------------------------------------------------------------------------------- /cypress/integration/upload-drop-zone/UploadDropZone-dropHandler-spec.js: -------------------------------------------------------------------------------- 1 | import dropFile from "../dropFile"; 2 | 3 | describe("UploadDropZone - Drop Handler", () => { 4 | const fileName = "flower.jpg"; 5 | 6 | before(() => { 7 | cy.visitStory( 8 | "uploadDropZone", 9 | "with-drop-handler", 10 | { mockDelay: 100 } 11 | ); 12 | }); 13 | 14 | it("should upload result from drop handler", () => { 15 | dropFile(fileName, () => { 16 | cy.waitShort(); 17 | cy.storyLog().assertUrlItemStartFinish("https://i.pinimg.com/originals/51/bf/9c/51bf9c7fdf0d4303140c4949afd1d7b8.jpg", 1); 18 | }); 19 | }); 20 | }); 21 | -------------------------------------------------------------------------------- /cypress/integration/upload-drop-zone/UploadDropZone-no-handle-spec.js: -------------------------------------------------------------------------------- 1 | import { ITEM_START } from "../../constants"; 2 | import dropFile from "../dropFile"; 3 | 4 | describe("UploadDropZone - shouldHandleDrag", () => { 5 | const fileName = "flower.jpg"; 6 | 7 | before(() => { 8 | cy.visitStory( 9 | "uploadDropZone", 10 | "with-dnd-turned-off", 11 | { mockDelay: 100 } 12 | ); 13 | }); 14 | 15 | it("should not do drop when shouldHandleDrag = false", () => { 16 | dropFile(fileName, () => { 17 | cy.waitExtraShort(); 18 | cy.storyLog().assertNoLogPattern(ITEM_START); 19 | }); 20 | }); 21 | }); 22 | -------------------------------------------------------------------------------- /cypress/integration/upload-drop-zone/UploadDropZone-simple-no-contain-check-spec.js: -------------------------------------------------------------------------------- 1 | describe("UploadDropZone - No Contain Check", () => { 2 | before(() => { 3 | cy.visitStory("uploadDropZone", "with-aria-modal-overlay", ); 4 | }); 5 | 6 | it("should work with aria modal", () => { 7 | cy.get("#aria-modal-modal") 8 | .trigger("dragenter", { dataTransfer: { items: [ { kind: "file" } ] } }); 9 | 10 | cy.get(".dropIndicator") 11 | .should("be.visible"); 12 | 13 | cy.get(".dropIndicator") 14 | .trigger("dragleave"); 15 | 16 | cy.get(".dropIndicator") 17 | .should("not.be.visible"); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /cypress/integration/upload-drop-zone/UploadDropZone-simple-spec.js: -------------------------------------------------------------------------------- 1 | import dropFile from "../dropFile"; 2 | 3 | describe("UploadDropZone - Simple", () => { 4 | const fileName = "flower.jpg"; 5 | 6 | before(() => { 7 | cy.visitStory("uploadDropZone", "simple", { mockDelay: 100 }); 8 | }); 9 | 10 | it("should upload dropped file", () => { 11 | dropFile(fileName, () => { 12 | cy.waitExtraShort(); 13 | cy.storyLog().assertFileItemStartFinish(fileName, 1); 14 | }); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /cypress/integration/upload-paste/UploadPaste-simple-spec.js: -------------------------------------------------------------------------------- 1 | describe("UploadPaste - Simple", () => { 2 | const fileName = "flower.jpg"; 3 | 4 | beforeEach(() => { 5 | cy.visitStory("uploadPaste", "simple", { mockDelay: 100 }); 6 | }); 7 | 8 | it("should upload pasted file", () => { 9 | cy.get("#paste-area") 10 | .should("exist") 11 | .pasteFile(fileName); 12 | 13 | cy.waitMedium(); 14 | cy.storyLog().assertFileItemStartFinish(fileName, 1); 15 | }); 16 | 17 | it("should upload pasted files", () => { 18 | cy.get("#paste-area") 19 | .should("exist") 20 | .pasteFile(fileName, 2); 21 | 22 | cy.waitLong(); 23 | cy.storyLog().assertFileItemStartFinish(fileName, 1); 24 | cy.storyLog().assertFileItemStartFinish("flower2.jpg", 3); 25 | }); 26 | }); 27 | -------------------------------------------------------------------------------- /cypress/integration/upload-paste/UploadPaste-window-spec.js: -------------------------------------------------------------------------------- 1 | describe("UploadPaste - Window Listener", () => { 2 | const fileName = "flower.jpg"; 3 | 4 | const loadPage = () => 5 | cy.visitStory( 6 | "uploadPaste", 7 | "with-window-paste", 8 | { mockDelay: 100 } 9 | ); 10 | 11 | it("should upload pasted file from anywhere on the page", () => { 12 | loadPage(); 13 | //wait for body to render first 14 | cy.get("#storybook-root button"); 15 | 16 | cy.get("body") 17 | .pasteFile(fileName); 18 | 19 | cy.waitExtraShort(); 20 | cy.storyLog().assertFileItemStartFinish(fileName, 1); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /cypress/integration/upload-preview/UploadPreview-clear-spec.js: -------------------------------------------------------------------------------- 1 | import uploadFile from "../uploadFile"; 2 | 3 | describe("UploadPreview - Clear", () => { 4 | const fileName = "flower.jpg"; 5 | 6 | before(() => { 7 | cy.visitStory("uploadPreview", "with-preview-methods"); 8 | }); 9 | 10 | it("should show upload preview", () => { 11 | uploadFile(fileName, () => { 12 | uploadFile(fileName, () => { 13 | cy.get("img[data-test='upload-preview']") 14 | .should("be.visible") 15 | .invoke("attr", "src") 16 | .should("match", /blob:/); 17 | 18 | cy.get("img[data-test='upload-preview']") 19 | .should("not.have.length", 2); 20 | 21 | cy.get("#clear-btn") 22 | .should("have.text", "Clear 2 previews") 23 | .click(); 24 | 25 | cy.get("img[data-test='upload-preview']") 26 | .should("have.length", 0); 27 | }, "#upload-btn"); 28 | }, "#upload-btn"); 29 | }); 30 | }); 31 | -------------------------------------------------------------------------------- /cypress/integration/upload-preview/UploadPreview-progress-spec.js: -------------------------------------------------------------------------------- 1 | import { interceptWithDelay } from "../intercept"; 2 | import uploadFile from "../uploadFile"; 3 | 4 | describe("UploadPreview - Progress", () => { 5 | const fileName = "flower.jpg"; 6 | 7 | beforeEach(() => { 8 | cy.visitStory( 9 | "uploadPreview", 10 | "with-progress", 11 | { useMock: false } 12 | ); 13 | }); 14 | 15 | it("should show upload preview", () => { 16 | interceptWithDelay(100, "uploadReq"); 17 | 18 | uploadFile(fileName, () => { 19 | cy.get(".preview-img") 20 | .should("have.css", "opacity", "0"); 21 | 22 | cy.wait(100); 23 | cy.get(".preview-img") 24 | .should("have.css", "opacity", "1"); 25 | }); 26 | }); 27 | }); 28 | -------------------------------------------------------------------------------- /cypress/integration/upload-preview/UploadPreview-simple-fallback-spec.js: -------------------------------------------------------------------------------- 1 | import uploadFile from "../uploadFile"; 2 | 3 | describe("UploadPreview - Simple", () => { 4 | const fileName = "flower.jpg"; 5 | 6 | before(() => { 7 | cy.visitStory( 8 | "uploadPreview", 9 | "simple", 10 | { customArgs: { maxPreviewSize: 1000 } } 11 | ); 12 | }); 13 | 14 | it("should show fallback on image > max size", () => { 15 | uploadFile(fileName, () => { 16 | cy.get("img[data-test='upload-preview']") 17 | .should("be.visible") 18 | .invoke("attr", "src") 19 | .should("match", /https:\/\/picsum.photos\/50/); 20 | }); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /cypress/integration/upload-preview/UploadPreview-simple-multiple-spec.js: -------------------------------------------------------------------------------- 1 | import { uploadFileTimes } from "../uploadFile"; 2 | 3 | describe("UploadPreview - Simple - Multiple files", () => { 4 | const fileName = "flower.jpg"; 5 | 6 | before(() => { 7 | cy.visitStory("uploadPreview", "simple"); 8 | }); 9 | 10 | it("should show upload preview for multiple files", () => { 11 | uploadFileTimes(fileName, () => { 12 | cy.waitShort(); 13 | cy.get("img[data-test='upload-preview']") 14 | .should("be.visible") 15 | .should("have.length", 3) 16 | .invoke("attr", "src") 17 | .should("match", /blob:/); 18 | }, 3, "button"); 19 | }); 20 | }); 21 | -------------------------------------------------------------------------------- /cypress/integration/upload-preview/UploadPreview-simple-spec.js: -------------------------------------------------------------------------------- 1 | import uploadFile from "../uploadFile"; 2 | 3 | describe("UploadPreview - Simple", () => { 4 | const fileName = "flower.jpg"; 5 | 6 | before(() => { 7 | cy.visitStory("uploadPreview", "simple"); 8 | }); 9 | 10 | it("should show upload preview", () => { 11 | uploadFile(fileName, () => { 12 | uploadFile(fileName, () => { 13 | cy.get("img[data-test='upload-preview']") 14 | .should("be.visible") 15 | .invoke("attr", "src") 16 | .should("match", /blob:/); 17 | 18 | cy.get("img[data-test='upload-preview']") 19 | .should("have.length", 1); 20 | }); 21 | }); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /cypress/integration/upload-preview/examineCroppedUploadReq.js: -------------------------------------------------------------------------------- 1 | const CROPPED_MAX_SIZE = 70_000; 2 | 3 | const examineCroppedUploadReq = (req, name) => 4 | req.interceptFormData((formData) => { 5 | expect(formData["file"]).to.eq(name); 6 | }) 7 | .its("request.headers") 8 | .its("content-length") 9 | .then((length) => { 10 | expect(parseInt(length)).to.be.lessThan(CROPPED_MAX_SIZE); 11 | }); 12 | 13 | const examineFullUploadRequest = (req, name) => 14 | req.interceptFormData((formData) => { 15 | expect(formData["file"]).to.eq(name); 16 | }) 17 | .its("request.headers") 18 | .its("content-length") 19 | .then((length) => { 20 | expect(parseInt(length)).to.be.least(37200); 21 | }); 22 | 23 | export { 24 | CROPPED_MAX_SIZE, 25 | examineCroppedUploadReq, 26 | examineFullUploadRequest, 27 | }; 28 | -------------------------------------------------------------------------------- /cypress/integration/upload-url-input/UploadUrlInput-simple-spec.js: -------------------------------------------------------------------------------- 1 | import { BATCH_ADD } from "../../constants"; 2 | 3 | describe("UploadButton - Simple", () => { 4 | 5 | before(() => { 6 | cy.visitStory( 7 | "uploadUrlInput", 8 | "simple", 9 | { mockDelay: 100 } 10 | ); 11 | }); 12 | 13 | it("should use uploady", () => { 14 | cy.get("input#url-input") 15 | .should("exist") 16 | .type("http://test.com{enter}"); 17 | 18 | cy.waitExtraShort(); 19 | cy.storyLog().assertLogPattern(BATCH_ADD); 20 | cy.storyLog().assertUrlItemStartFinish("http://test.com", 1); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /cypress/integration/uploadFile.js: -------------------------------------------------------------------------------- 1 | import selectFiles from "./selectFiles"; 2 | 3 | const uploadFile = (fixtureName, cb, button = "button", options = { }) => { 4 | const selectorRoot = cy.config("spec").specType !== "component" ? "#storybook-root " : ""; 5 | 6 | selectFiles( 7 | fixtureName, 8 | (button === false ? button : `${selectorRoot}${button}`), 9 | "uploadButton", 10 | cb, 11 | { ...options, force: true } 12 | ); 13 | }; 14 | 15 | export const uploadFileTimes = (fileName, cb, times, button = "button", options = {}, iframe) => 16 | uploadFile(fileName, cb, button, { ...options, times, }, iframe); 17 | 18 | export default uploadFile; 19 | -------------------------------------------------------------------------------- /cypress/integration/uploader/Uploader-recover-from-error-spec.js: -------------------------------------------------------------------------------- 1 | import uploadFile from "../uploadFile"; 2 | import { ITEM_START, ITEM_ERROR } from "../../constants"; 3 | 4 | describe("Uploader - recover from sender error test", () => { 5 | const fileName = "flower.jpg"; 6 | 7 | before(() => { 8 | cy.visitStory( 9 | "uploader", 10 | "with-custom-ui", 11 | { uploadUrl: "http://localhost:8439/not-exist" } 12 | ); 13 | }); 14 | 15 | it("should upload again after unexpected sender error", () => { 16 | uploadFile(fileName, () => { 17 | uploadFile(fileName, () => { 18 | cy.waitMedium(); 19 | 20 | cy.storyLog().assertLogPattern(ITEM_START, { times: 2 }); 21 | cy.storyLog().assertLogPattern(ITEM_ERROR, { times: 2 }); 22 | }, "#upload-button"); 23 | }, "#upload-button"); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /cypress/integration/uploady/Uploady-customResponseFormat-spec.js: -------------------------------------------------------------------------------- 1 | import intercept from "../intercept"; 2 | import uploadFile from "../uploadFile"; 3 | 4 | describe("Uploady - Custom Response Formatter", () => { 5 | const fileName = "flower.jpg"; 6 | 7 | beforeEach(() => { 8 | cy.visitStory( 9 | "uploady", 10 | "with-custom-response-format", 11 | { useMock: false } 12 | ); 13 | }); 14 | 15 | it("should use custom response formatter function", () => { 16 | intercept(); 17 | 18 | uploadFile(fileName, () => { 19 | cy.wait("@uploadReq"); 20 | 21 | cy.storyLog().assertLogEntryContains(2, { 22 | uploadResponse: { 23 | data: "200 - Yay!" 24 | } 25 | }); 26 | }); 27 | }); 28 | }); 29 | -------------------------------------------------------------------------------- /cypress/integration/uploady/Uploady-failed-mock-spec.js: -------------------------------------------------------------------------------- 1 | import uploadFile from "../uploadFile"; 2 | import { ITEM_ERROR, ITEM_START } from "../../constants"; 3 | 4 | describe("Uploady - Failed Mock Send", () => { 5 | const fileName = "flower.jpg"; 6 | 7 | before(() => { 8 | cy.visitStory( 9 | "uploady", 10 | "with-failing-mock-sender", 11 | ); 12 | }); 13 | 14 | it("failed mock send should trigger item error", () => { 15 | uploadFile(fileName, () => { 16 | cy.waitShort(); 17 | cy.storyLog().assertLogPattern(ITEM_START); 18 | cy.waitShort(); 19 | 20 | cy.storyLog().assertLogPattern(ITEM_ERROR) 21 | .then((matches) => { 22 | const logIndex = matches[0].index; 23 | cy.storyLog().assertLogEntryContains(logIndex, { state: "error" }); 24 | }); 25 | }); 26 | }); 27 | }); 28 | -------------------------------------------------------------------------------- /cypress/integration/uploady/Uploady-filesParamName-spec.js: -------------------------------------------------------------------------------- 1 | import intercept from "../intercept"; 2 | import uploadFile from "../uploadFile"; 3 | 4 | describe("Uploady - filesParamName", () => { 5 | const fileName = "flower.jpg"; 6 | 7 | before(() => { 8 | cy.visitStory( 9 | "uploady", 10 | "with-custom-field-name", 11 | { useMock: false } 12 | ); 13 | }); 14 | 15 | it("should set the files param name to custom value", () => { 16 | intercept(); 17 | 18 | uploadFile(fileName, () => { 19 | cy.get("#upload-button") 20 | .click(); 21 | 22 | cy.wait("@uploadReq") 23 | .interceptFormData((formData) => { 24 | expect(formData["customFieldName"]).to.equal(fileName); 25 | cy.waitShort(); 26 | cy.storyLog().assertFileItemStartFinish(fileName, 1); 27 | }); 28 | }); 29 | }); 30 | }); 31 | -------------------------------------------------------------------------------- /cypress/integration/uploady/Uploady-headerFromPreSend-spec.js: -------------------------------------------------------------------------------- 1 | import intercept from "../intercept"; 2 | import uploadFile from "../uploadFile"; 3 | 4 | describe("Uploady - Header from RequestPreSend hook", () => { 5 | const fileName = "flower.jpg"; 6 | 7 | beforeEach(() => { 8 | cy.visitStory( 9 | "uploady", 10 | "with-header-from-file-name", 11 | { useMock: false } 12 | ); 13 | }); 14 | 15 | it("should create a header from pre send hook using file name", () => { 16 | intercept(); 17 | 18 | uploadFile(fileName, () => { 19 | cy.wait("@uploadReq") 20 | .then((xhr) => { 21 | cy.storyLog().assertLogEntryContains(2, { 22 | uploadResponse: { 23 | data: { success: true } 24 | } 25 | }); 26 | 27 | expect(xhr.request.headers["x-file-names-lengths"]).to.equal("10"); 28 | }); 29 | }); 30 | }); 31 | }); 32 | -------------------------------------------------------------------------------- /cypress/integration/uploady/Uploady-internal-input-spec.js: -------------------------------------------------------------------------------- 1 | describe("Uploady - Internal Input", () => { 2 | before(() => { 3 | cy.visitStory("uploady", "with-exposed-internal-input"); 4 | }); 5 | 6 | it("should use internal file input from useFileInput", () => { 7 | cy.get("#select-input-type") 8 | .select("dir"); 9 | 10 | cy.get("input[type='file']") 11 | .should("have.attr", "webkitdirectory", "true"); 12 | }); 13 | }); 14 | -------------------------------------------------------------------------------- /cypress/integration/uploady/Uploady-invalid-request-pre-send-spec.js: -------------------------------------------------------------------------------- 1 | import { ITEM_ERROR, ITEM_FINISH, ITEM_START } from "../../constants"; 2 | import intercept from "../intercept"; 3 | import { uploadFileTimes } from "../uploadFile"; 4 | 5 | describe("Uploady - invalid requestPreSend", () => { 6 | const fileName = "flower.jpg"; 7 | 8 | beforeEach(() => { 9 | cy.visitStory( 10 | "uploady", 11 | "test-invalid-pre-send", 12 | { useMock: false } 13 | ); 14 | }); 15 | 16 | it("should fail invalid updated data from pre send - forbidden item props", () => { 17 | intercept(); 18 | 19 | uploadFileTimes(fileName, () => { 20 | cy.waitShort(); 21 | 22 | cy.storyLog().assertLogPattern(ITEM_START, { times: 2 }); 23 | cy.storyLog().assertLogPattern(ITEM_FINISH, { times: 1 }); 24 | cy.storyLog().assertLogPattern(ITEM_ERROR, { times: 1 }); 25 | }, 2, "#upload-button"); 26 | }); 27 | }); 28 | -------------------------------------------------------------------------------- /cypress/integration/uploady/Uploady-pending-with-options-spec.js: -------------------------------------------------------------------------------- 1 | import intercept from "../intercept"; 2 | import uploadFile from "../uploadFile"; 3 | import { ITEM_FINISH, ITEM_START } from "../../constants"; 4 | 5 | describe("Uploady - autoUpload off tests", () => { 6 | const fileName = "flower.jpg"; 7 | 8 | before(() => { 9 | cy.visitStory( 10 | "uploady", 11 | "with-auto-upload-off", 12 | { useMock: false } 13 | ); 14 | }); 15 | 16 | it("should process pending with options", () => { 17 | intercept(); 18 | 19 | uploadFile(fileName, () => { 20 | cy.storyLog().assertLogPattern(ITEM_START, { times: 0 }); 21 | 22 | cy.get("#process-pending-param") 23 | .click(); 24 | 25 | cy.wait("@uploadReq") 26 | .interceptFormData((formData) => { 27 | expect(formData["test"]).to.equal("123"); 28 | }); 29 | 30 | cy.storyLog().assertLogPattern(ITEM_START, { times: 1 }); 31 | cy.storyLog().assertLogPattern(ITEM_FINISH, { times: 1 }); 32 | }, "#upload-button"); 33 | }); 34 | }); 35 | -------------------------------------------------------------------------------- /cypress/plugins/index.js: -------------------------------------------------------------------------------- 1 | /// 2 | // *********************************************************** 3 | // This example plugins/index.js can be used to load plugins 4 | // 5 | // You can change the location of this file or turn off loading 6 | // the plugins file with the 'pluginsFile' configuration option. 7 | // 8 | // You can read more here: 9 | // https://on.cypress.io/plugins-guide 10 | // *********************************************************** 11 | 12 | // This function is called when a project is opened or re-opened (e.g. due to 13 | // the project's config changing) 14 | 15 | // /** 16 | // * @type {Cypress.PluginConfig} 17 | // */ 18 | // module.exports = (on, config) => { 19 | // // `on` is used to hook into various events Cypress emits 20 | // // `config` is the resolved Cypress config 21 | // } 22 | -------------------------------------------------------------------------------- /cypress/reporters-config.json-old: -------------------------------------------------------------------------------- 1 | { 2 | "reporterEnabled": "json", 3 | "reporterOptions": { 4 | "mochaFile": "cypress/results/results-[hash].xml", 5 | "output-file": "cypress/results/json/results-[hash].json" 6 | }, 7 | "jsonReporterReporterOptions": { 8 | "output-file": "cypress/results/json/results-[hash].json" 9 | }, 10 | "mochaJunitReporterReporterOptions": { 11 | "mochaFile": "cypress/results/results-[suiteName].xml", 12 | "toConsole": true 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /cypress/support/component-index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Components App 8 | 9 | 10 |
11 | 12 | -------------------------------------------------------------------------------- /cypress/support/component.js: -------------------------------------------------------------------------------- 1 | // *********************************************************** 2 | // This example support/component.js is processed and 3 | // loaded automatically before your test files. 4 | // 5 | // This is a great place to put global configuration and 6 | // behavior that modifies Cypress. 7 | // 8 | // You can change the location of this file or turn off 9 | // automatically serving support files with the 10 | // 'supportFile' configuration option. 11 | // 12 | // You can read more here: 13 | // https://on.cypress.io/configuration 14 | // *********************************************************** 15 | 16 | 17 | // Alternatively you can use CommonJS syntax: 18 | // require('./commands') 19 | import "cypress-intercept-formdata"; 20 | import { mount } from "cypress/react18"; 21 | 22 | Cypress.Commands.add("mount", mount); 23 | 24 | // Example use: 25 | // cy.mount() 26 | -------------------------------------------------------------------------------- /cypress/support/e2e.js: -------------------------------------------------------------------------------- 1 | import "cypress-intercept-formdata"; 2 | import "cypress-mochawesome-reporter/register"; 3 | 4 | import "./storyLog"; 5 | import "./visitStory"; 6 | import "./pasteFile"; 7 | import "./setUploadOptions"; 8 | import "./wait"; 9 | -------------------------------------------------------------------------------- /cypress/support/index.js: -------------------------------------------------------------------------------- 1 | import "./e2e"; 2 | -------------------------------------------------------------------------------- /cypress/support/setUploadOptions.js: -------------------------------------------------------------------------------- 1 | Cypress.Commands.add("setUploadOptions", (options) => 2 | //cy.wrap(window.parent) 3 | cy.window() 4 | .then((w) => { 5 | if (w._setUploadOptions) { 6 | w._setUploadOptions(options); 7 | } else { 8 | w.__extUploadOptions = options; 9 | } 10 | })); 11 | 12 | Cypress.Commands.add("setPreSendOptions", (options) => 13 | //cy.wrap(window.parent) 14 | cy.window() 15 | .then((w) => { 16 | w.__extPreSendOptions = options; 17 | })); 18 | -------------------------------------------------------------------------------- /cypress/support/wait.js: -------------------------------------------------------------------------------- 1 | import { WAIT_X_SHORT, WAIT_SHORT, WAIT_MEDIUM, WAIT_LONG, WAIT_X_LONG } from "../constants"; 2 | 3 | [ 4 | { name: "ExtraShort", time: WAIT_X_SHORT }, 5 | { name: "Short", time: WAIT_SHORT }, 6 | { name: "Medium", time: WAIT_MEDIUM }, 7 | { name: "Long", time: WAIT_LONG }, 8 | { name: "ExtraLong", time: WAIT_X_LONG }, 9 | ] 10 | .forEach(({ name, time }) => 11 | Cypress.Commands.add(`wait${name}`, () => { 12 | cy.wait(time); 13 | })); 14 | 15 | -------------------------------------------------------------------------------- /cypress/tsconfig.json: -------------------------------------------------------------------------------- 1 | 2 | { 3 | "compilerOptions": { 4 | "strict": true, 5 | "baseUrl": "../node_modules", 6 | "target": "es5", 7 | "experimentalDecorators": true, 8 | "skipLibCheck": true, 9 | "noImplicitAny": false, 10 | "lib": ["es6", "dom"], 11 | "types": ["cypress"] 12 | }, 13 | "include": ["**/*.ts"] 14 | } 15 | -------------------------------------------------------------------------------- /cypress/webpack.cypress.config.mjs: -------------------------------------------------------------------------------- 1 | import webpack from "webpack"; 2 | 3 | const config = { 4 | plugins: [ 5 | new webpack.EnvironmentPlugin({ 6 | "BUILD_TIME_VERSION": "test", 7 | }), 8 | ], 9 | 10 | resolve: { 11 | mainFields: ["main:dev", "module", "main"], 12 | }, 13 | 14 | module: { 15 | rules: [ 16 | { 17 | test: /\.?js$/, 18 | exclude: /node_module/, 19 | use: { 20 | loader: "babel-loader", 21 | } 22 | }, 23 | { 24 | test: /\.css$/i, 25 | use: ["style-loader", "css-loader"], 26 | }, 27 | ] 28 | }, 29 | }; 30 | 31 | export default config; 32 | -------------------------------------------------------------------------------- /flow-typed.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": ["node", "dom", "bom", "intl", "cssom", "indexeddb", "serviceworkers", "webassembly", "jsx"], 3 | "ignore": ["@rpldy", "@babel", "@actions", "@testing-library", "@vitest", "babel-plugin-*"], 4 | "workspaces": [ 5 | "packages/core/*", 6 | "packages/ui/*", 7 | "packages/native/*" 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /flow-typed/npm/flow-bin_v0.x.x.js: -------------------------------------------------------------------------------- 1 | // flow-typed signature: 28fdff7f110e1c75efab63ff205dda30 2 | // flow-typed version: c6154227d1/flow-bin_v0.x.x/flow_>=v0.104.x 3 | 4 | declare module "flow-bin" { 5 | declare module.exports: string; 6 | } 7 | -------------------------------------------------------------------------------- /flow-typed/npm/invariant_v2.x.x.js: -------------------------------------------------------------------------------- 1 | // flow-typed signature: 4daa25492655417e7c0763d1d0b30fbb 2 | // flow-typed version: c6154227d1/invariant_v2.x.x/flow_>=v0.104.x 3 | 4 | declare module invariant { 5 | declare module.exports: (condition: boolean, message: string) => void; 6 | } 7 | -------------------------------------------------------------------------------- /guides/README.md: -------------------------------------------------------------------------------- 1 | # Guides 2 | 3 | The Guides section is now housed in our [Documentation Website](https://react-uploady.org/docs/guides/). 4 | -------------------------------------------------------------------------------- /guides/rpldy-demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rpldy/react-uploady/2c92e126c8b2e583fc6180b036e49d62e3e00a5c/guides/rpldy-demo.gif -------------------------------------------------------------------------------- /lerna.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "1.10.0", 3 | "command": { 4 | "version": { 5 | "allowBranch": "release-*" 6 | }, 7 | "publish": { 8 | "ignoreChanges": [ 9 | "**/tests/**", 10 | "**/*.test.js" 11 | ], 12 | "message": "chore: publish new version: %v" 13 | } 14 | }, 15 | "$schema": "node_modules/lerna/schemas/lerna-schema.json", 16 | "npmClient": "pnpm" 17 | } -------------------------------------------------------------------------------- /netlify.toml: -------------------------------------------------------------------------------- 1 | [build] 2 | publish = ".sb-static" 3 | command = "pnpm sb:build" 4 | -------------------------------------------------------------------------------- /nx.json: -------------------------------------------------------------------------------- 1 | { 2 | "tasksRunnerOptions": { 3 | "default": { 4 | "runner": "nx/tasks-runners/default", 5 | "options": { 6 | "cacheableOperations": [ 7 | "build" 8 | ] 9 | } 10 | } 11 | }, 12 | "targetDefaults": { 13 | "build": { 14 | "outputs": [ 15 | "{projectRoot}/lib", 16 | "{projectRoot}/LICENSE.md", 17 | "{projectRoot}/.npmignore" 18 | ] 19 | } 20 | }, 21 | "$schema": "./node_modules/nx/schemas/nx-schema.json", 22 | "namedInputs": { 23 | "default": [ 24 | "{projectRoot}/**/*", 25 | "sharedGlobals" 26 | ], 27 | "sharedGlobals": [], 28 | "production": [ 29 | "default" 30 | ] 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /packages/core/abort/.npmignore: -------------------------------------------------------------------------------- 1 | src/ 2 | *.story.js 3 | *.stories.js 4 | *.test.js 5 | tests/ 6 | mocks/ 7 | lerna.json 8 | *.log 9 | jest* 10 | .flowconfig 11 | .editorconfig 12 | .eslintignore 13 | .eslintrc.js 14 | .circleci/ 15 | .sb-static/ 16 | .env 17 | .jest-cache/ 18 | .jest-coverage/ 19 | coverage/ 20 | .github/ 21 | 22 | -------------------------------------------------------------------------------- /packages/core/abort/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "1.10.0", 3 | "name": "@rpldy/abort", 4 | "description": "adds the capability to abort/cancel running & pending uploads", 5 | "author": "yoav niran (https://github.com/yoavniran)", 6 | "main": "lib/cjs/index.js", 7 | "module": "lib/esm/index.js", 8 | "main:dev": "src/index.js", 9 | "types": "types/index.d.ts", 10 | "license": "MIT", 11 | "homepage": "https://react-uploady.org", 12 | "repository": { 13 | "type": "git", 14 | "url": "https://github.com/rpldy/react-uploady.git", 15 | "directory": "packages/core/abort" 16 | }, 17 | "funding": { 18 | "type": "opencollective", 19 | "url": "https://opencollective.com/react-uploady" 20 | }, 21 | "scripts": { 22 | "build": "node ../../../scripts/build.mjs" 23 | }, 24 | "dependencies": { 25 | "@rpldy/raw-uploader": "^1.10.0", 26 | "@rpldy/shared": "^1.10.0", 27 | "@rpldy/simple-state": "^1.10.0" 28 | }, 29 | "devDependencies": { 30 | "flow-bin": "^0.272.2" 31 | }, 32 | "publishConfig": { 33 | "access": "public" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /packages/core/abort/src/fastAbort.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import type { Batch } from "@rpldy/shared"; 3 | import type { AbortsMap } from "./types"; 4 | 5 | const fastAbortBatch = (batch: Batch, aborts: AbortsMap) => { 6 | batch.items.forEach(({ id }) => aborts[id]?.()); 7 | }; 8 | 9 | const fastAbortAll = (aborts: AbortsMap) => { 10 | const runFn = (fn: () => boolean) => fn(); 11 | 12 | Object.values(aborts) 13 | .forEach(runFn); 14 | }; 15 | 16 | export { 17 | fastAbortAll, 18 | fastAbortBatch, 19 | }; 20 | -------------------------------------------------------------------------------- /packages/core/abort/src/getAbortEnhancer.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import { abortAll, abortBatch, abortItem } from "./abort"; 3 | import type { UploaderEnhancer, UploaderType } from "@rpldy/raw-uploader"; 4 | 5 | const getAbortEnhancer = (): UploaderEnhancer => { 6 | /** 7 | * an uploader enhancer function to add abort functionality 8 | */ 9 | return (uploader: UploaderType): UploaderType => { 10 | //$FlowIssue[incompatible-call]: unsure how to tell Flow this is ok... 11 | uploader.update({ abortAll, abortBatch, abortItem }); 12 | return uploader; 13 | }; 14 | }; 15 | 16 | export default getAbortEnhancer; 17 | -------------------------------------------------------------------------------- /packages/core/abort/src/index.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import getAbortEnhancer from "./getAbortEnhancer"; 3 | 4 | export default getAbortEnhancer; 5 | 6 | export type { 7 | AbortMethod, 8 | AbortsMap, 9 | AbortAllMethod, 10 | AbortBatchMethod, 11 | AbortResult, 12 | AbortItemMethod, 13 | CreateOptionsWithAbort, 14 | } from "./types"; 15 | -------------------------------------------------------------------------------- /packages/core/abort/src/tests/fastAbort.test.js: -------------------------------------------------------------------------------- 1 | import { fastAbortBatch, fastAbortAll } from "../fastAbort"; 2 | 3 | describe("fast abort tests", () => { 4 | it("should fast abort batch items", () => { 5 | const abort = vi.fn(); 6 | 7 | fastAbortBatch({ items: [{ id: "u1" }, { id: "u2" }, { id: "u3" }] }, { "u1": abort, "u2": abort, "u4": abort }); 8 | 9 | expect(abort).toHaveBeenCalledTimes(2); 10 | }); 11 | 12 | it("should fast abort all", () => { 13 | const abort = vi.fn(); 14 | 15 | fastAbortAll({ "u1": abort, "u2": abort }); 16 | 17 | expect(abort).toHaveBeenCalledTimes(2); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /packages/core/abort/src/tests/getAbortEnhancer.test.js: -------------------------------------------------------------------------------- 1 | import getAbortEnhancer from "../getAbortEnhancer"; 2 | import { abortItem, abortBatch, abortAll } from "../abort"; 3 | 4 | vi.mock("../abort"); 5 | 6 | describe("getAbortEnhancer tests", () => { 7 | it("should enhance uploader with abort fns", () => { 8 | const enhancer = getAbortEnhancer(); 9 | const uploader = { update: vi.fn() }; 10 | 11 | const enhanced = enhancer(uploader); 12 | 13 | expect(uploader.update).toHaveBeenCalledWith({ 14 | abortAll, abortBatch, abortItem 15 | }); 16 | 17 | expect(enhanced).toBe(uploader); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /packages/core/abort/types/index.d.ts: -------------------------------------------------------------------------------- 1 | import { RawUploaderEnhancer } from "@rpldy/raw-uploader"; 2 | 3 | export const getAbortEnhancer: () => RawUploaderEnhancer; 4 | 5 | export default getAbortEnhancer; 6 | -------------------------------------------------------------------------------- /packages/core/abort/types/index.test-d.ts: -------------------------------------------------------------------------------- 1 | import getAbortEnhancer from "./index"; 2 | import { createRawUploader } from "@rpldy/raw-uploader"; 3 | 4 | const testEnhancer = (): void => { 5 | const abortEnhancer = getAbortEnhancer(); 6 | 7 | const uploader = createRawUploader({ 8 | enhancer: abortEnhancer, 9 | }); 10 | 11 | console.log(uploader.getOptions()); 12 | }; 13 | 14 | export { 15 | testEnhancer, 16 | }; 17 | -------------------------------------------------------------------------------- /packages/core/chunked-sender/.npmignore: -------------------------------------------------------------------------------- 1 | src/ 2 | *.story.js 3 | *.stories.js 4 | *.test.js 5 | tests/ 6 | mocks/ 7 | lerna.json 8 | *.log 9 | jest* 10 | .flowconfig 11 | .editorconfig 12 | .eslintignore 13 | .eslintrc.js 14 | .circleci/ 15 | .sb-static/ 16 | .env 17 | .jest-cache/ 18 | .jest-coverage/ 19 | coverage/ 20 | .github/ 21 | 22 | -------------------------------------------------------------------------------- /packages/core/chunked-sender/ChunkedSender.storydoc.mdx: -------------------------------------------------------------------------------- 1 | import { Markdown } from "@storybook/blocks"; 2 | import readme from "./README.md?raw"; 3 | 4 | {readme} 5 | -------------------------------------------------------------------------------- /packages/core/chunked-sender/src/chunkedSender/ChunkedSendError.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | export default class ChunkedSendError extends Error { 4 | constructor(message: string) { 5 | super(message); 6 | this.name = "ChunkedSendError"; 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /packages/core/chunked-sender/src/chunkedSender/getChunksToSend.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import ChunkedSendError from "./ChunkedSendError"; 3 | import type { Chunk, ChunkedState } from "./types"; 4 | 5 | const getChunksToSend = (chunkedState: ChunkedState): Array => { 6 | const state = chunkedState.getState(); 7 | 8 | const chunks = [], 9 | inProgressIds = Object.keys(state.requests), 10 | parallel = state.parallel || 1; 11 | 12 | for (let i = 0; i < state.chunks.length && 13 | (inProgressIds.length + chunks.length) < parallel; i++) { 14 | const chunk = state.chunks[i]; 15 | 16 | if (!inProgressIds.includes(chunk.id)) { 17 | if (!chunk.attempt || chunk.attempt < state.retries) { 18 | chunks.push(chunk); 19 | } else { 20 | throw new ChunkedSendError("chunk failure"); 21 | } 22 | } 23 | } 24 | 25 | return chunks; 26 | }; 27 | 28 | export default getChunksToSend; 29 | -------------------------------------------------------------------------------- /packages/core/chunked-sender/src/chunkedSender/index.js: -------------------------------------------------------------------------------- 1 | import createChunkedSender from "./createChunkedSender"; 2 | export default createChunkedSender; 3 | -------------------------------------------------------------------------------- /packages/core/chunked-sender/src/chunkedSender/processChunkProgressData.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import type { BatchItem } from "@rpldy/shared"; 3 | import type { ChunkedState } from "./types"; 4 | 5 | const processChunkProgressData = ( 6 | chunkedState: ChunkedState, 7 | item: BatchItem, 8 | chunkId: string, 9 | chunkUploaded: number 10 | ): { loaded: number, total: number } => { 11 | chunkedState.updateState((state) => { 12 | state.uploaded[chunkId] = Math.max(chunkUploaded, (state.uploaded[chunkId] || 0)); 13 | }); 14 | 15 | const state = chunkedState.getState(); 16 | 17 | const loadedSum = Object.keys(state.uploaded) 18 | .reduce((res, id) => res + state.uploaded[id], 19 | //we start from the offset of the first chunk to get an accurate progress on resumed uploads 20 | state.startByte); 21 | 22 | const total = item.file.size; 23 | 24 | return { loaded: Math.min(loadedSum, total), total }; 25 | }; 26 | 27 | export default processChunkProgressData; 28 | -------------------------------------------------------------------------------- /packages/core/chunked-sender/src/chunkedSender/tests/getChunkedState.test.js: -------------------------------------------------------------------------------- 1 | import getChunkedState from "../getChunkedState"; 2 | 3 | vi.mock("@rpldy/simple-state", () => ({ 4 | default: (state) => ({ 5 | state, 6 | update: (updater) => updater(state), 7 | }) 8 | })); 9 | 10 | describe("getChunkedState tests", () => { 11 | it("should return chunked state", () => { 12 | const chunkedState = getChunkedState([{}, {}], "test.com", { method: "PUT", startByte: 3 }, { chunked: true }); 13 | 14 | const state = chunkedState.getState(); 15 | 16 | expect(state.chunks).toHaveLength(2); 17 | expect(state.url).toBe("test.com"); 18 | 19 | let updateCnt = 0; 20 | 21 | chunkedState.updateState((state) => { 22 | updateCnt++; 23 | expect(state.startByte).toBe(3); 24 | }); 25 | 26 | expect(updateCnt).toBe(1); 27 | }); 28 | }); 29 | -------------------------------------------------------------------------------- /packages/core/chunked-sender/src/chunkedSender/tests/mocks/getChunkedState.mock.js: -------------------------------------------------------------------------------- 1 | const getChunkedState = (state = {}) => { 2 | const getState = vi.fn(() => state); 3 | 4 | const updateState = vi.fn((updater) => { 5 | updater(state); 6 | }); 7 | 8 | return { 9 | getState, 10 | updateState, 11 | }; 12 | } 13 | 14 | export default getChunkedState; 15 | -------------------------------------------------------------------------------- /packages/core/chunked-sender/src/consts.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import { devFreeze } from "@rpldy/shared"; 3 | 4 | export const CHUNK_EVENTS: Object = devFreeze({ 5 | CHUNK_START: "CHUNK_START", 6 | CHUNK_FINISH: "CHUNK_FINISH", 7 | }); 8 | 9 | export const CHUNKED_SENDER_TYPE = "rpldy-chunked-sender"; 10 | -------------------------------------------------------------------------------- /packages/core/chunked-sender/src/defaults.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import { devFreeze } from "@rpldy/shared"; 3 | 4 | export const DEFAULT_OPTIONS: Object = devFreeze({ 5 | chunked: true, 6 | chunkSize: 5242880, 7 | retries: 0, 8 | parallel: 1 9 | }); 10 | -------------------------------------------------------------------------------- /packages/core/chunked-sender/src/getChunkedEnhancer.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import { logger } from "@rpldy/shared"; 3 | import createChunkedSender from "./chunkedSender"; 4 | 5 | import type { UploaderEnhancer, UploaderType, UploaderCreateOptions } from "@rpldy/uploader"; 6 | import type { ChunkedOptions } from "./types"; 7 | import type { TriggerMethod } from "@rpldy/life-events"; 8 | 9 | const getChunkedEnhancer = (options: ChunkedOptions): UploaderEnhancer => { 10 | //return uploader enhancer 11 | return (uploader: UploaderType, trigger: TriggerMethod): UploaderType => { 12 | const sender = createChunkedSender(options, trigger); 13 | logger.debugLog("chunkedSenderEnhancer: Created chunked-sender instance with options: ", options); 14 | uploader.update({ send: sender.send }); 15 | return uploader; 16 | }; 17 | }; 18 | 19 | export default getChunkedEnhancer; 20 | -------------------------------------------------------------------------------- /packages/core/chunked-sender/src/index.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import createChunkedSender from "./chunkedSender"; 3 | import getChunkedEnhancer from "./getChunkedEnhancer"; 4 | import { CHUNKING_SUPPORT, getChunkDataFromFile } from "./utils"; 5 | import { CHUNK_EVENTS, CHUNKED_SENDER_TYPE } from "./consts"; 6 | import { DEFAULT_OPTIONS as CHUNKED_DEFAULT_OPTIONS } from "./defaults"; 7 | 8 | export default getChunkedEnhancer; 9 | 10 | const DEFAULT_CHUNK_SIZE = CHUNKED_DEFAULT_OPTIONS.chunkSize; 11 | 12 | export { 13 | CHUNK_EVENTS, 14 | CHUNKING_SUPPORT, 15 | CHUNKED_SENDER_TYPE, 16 | CHUNKED_DEFAULT_OPTIONS, 17 | DEFAULT_CHUNK_SIZE, 18 | 19 | getChunkedEnhancer, 20 | createChunkedSender, 21 | getChunkDataFromFile, 22 | }; 23 | 24 | export type { 25 | ChunkedOptions, 26 | MandatoryChunkedOptions, 27 | ChunkedSender, 28 | ChunkedSendOptions, 29 | ChunkEventData, 30 | ChunkStartEventData, 31 | ChunkFinishEventData, 32 | } from "./types"; 33 | 34 | export type { 35 | OnProgress, 36 | SendOptions, 37 | SendResult, 38 | } from "@rpldy/sender"; 39 | -------------------------------------------------------------------------------- /packages/core/chunked-sender/src/tests/chunkedEnhancer.test.js: -------------------------------------------------------------------------------- 1 | import createChunkedSender from "../chunkedSender"; 2 | import getChunkedEnhancer from "../getChunkedEnhancer"; 3 | 4 | vi.mock("../chunkedSender"); 5 | 6 | describe("chunkedEnhancer tests", () => { 7 | it("should enhance uploader with chunked sender", () => { 8 | const options = { chunkSize: 111 }; 9 | const uploader = { update: vi.fn() }; 10 | 11 | createChunkedSender.mockReturnValueOnce({ send: "chunkedSend" }); 12 | 13 | const enhancer = getChunkedEnhancer(options); 14 | const result = enhancer(uploader, "trigger"); 15 | 16 | expect(result).toBe(uploader); 17 | expect(createChunkedSender).toHaveBeenCalledWith(options, "trigger"); 18 | expect(uploader.update).toHaveBeenCalledWith({ send: "chunkedSend" }); 19 | }); 20 | }); 21 | -------------------------------------------------------------------------------- /packages/core/chunked-sender/types/index.test-d.tsx: -------------------------------------------------------------------------------- 1 | import getChunkedEnhancer, { createChunkedSender } from "./index"; 2 | import createUploader from "@rpldy/uploader"; 3 | import { createBatchItem } from "@rpldy/shared"; 4 | 5 | const testGetEnhancer = (): void => { 6 | const enhancer = getChunkedEnhancer({ chunkSize: 123 }); 7 | 8 | const uploader = createUploader({ 9 | enhancer, 10 | }); 11 | 12 | console.log(uploader.getOptions()); 13 | }; 14 | 15 | const testGetSender = (): void => { 16 | const sender = createChunkedSender({ chunkSize: 123 }); 17 | 18 | sender.send([createBatchItem({ name: "test.png", size: 123, type: "image", lastModified: 1234 }, "b1")], 19 | "test.com", 20 | { 21 | method: "POST", 22 | paramName: "file", 23 | headers: { "x-test": "aaa" } }); 24 | }; 25 | 26 | export { 27 | testGetEnhancer, 28 | testGetSender, 29 | }; 30 | -------------------------------------------------------------------------------- /packages/core/life-events/.npmignore: -------------------------------------------------------------------------------- 1 | src/ 2 | *.story.js 3 | *.stories.js 4 | *.test.js 5 | tests/ 6 | mocks/ 7 | lerna.json 8 | *.log 9 | jest* 10 | .flowconfig 11 | .editorconfig 12 | .eslintignore 13 | .eslintrc.js 14 | .circleci/ 15 | .sb-static/ 16 | .env 17 | .jest-cache/ 18 | .jest-coverage/ 19 | coverage/ 20 | .github/ 21 | 22 | -------------------------------------------------------------------------------- /packages/core/life-events/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "1.10.0", 3 | "name": "@rpldy/life-events", 4 | "description": "events pub/sub management with return values", 5 | "author": "yoav niran (https://github.com/yoavniran)", 6 | "main": "lib/cjs/index.js", 7 | "module": "lib/esm/index.js", 8 | "main:dev": "src/index.js", 9 | "types": "types/index.d.ts", 10 | "license": "MIT", 11 | "keywords": [ 12 | "events", 13 | "pubsub", 14 | "emitter" 15 | ], 16 | "homepage": "https://react-uploady.org", 17 | "repository": { 18 | "type": "git", 19 | "url": "https://github.com/rpldy/react-uploady.git", 20 | "directory": "packages/life-events" 21 | }, 22 | "funding": { 23 | "type": "opencollective", 24 | "url": "https://opencollective.com/react-uploady" 25 | }, 26 | "scripts": { 27 | "build": "node ../../../scripts/build.mjs" 28 | }, 29 | "dependencies": { 30 | "@rpldy/shared": "^1.10.0" 31 | }, 32 | "devDependencies": { 33 | "flow-bin": "^0.272.2" 34 | }, 35 | "publishConfig": { 36 | "access": "public" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /packages/core/life-events/src/consts.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | export const LESYM: symbol = Symbol.for("__le__"); 3 | 4 | export const LE_PACK_SYM: symbol = Symbol.for("__le__pack__"); 5 | -------------------------------------------------------------------------------- /packages/core/life-events/src/defaults.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import { devFreeze } from "@rpldy/shared"; 3 | import type { Options } from "./types"; 4 | 5 | const defaults: Options = devFreeze({ 6 | allowRegisterNonExistent: true, 7 | canAddEvents: true, 8 | canRemoveEvents: true, 9 | collectStats: false, 10 | }); 11 | 12 | export default defaults; 13 | 14 | 15 | -------------------------------------------------------------------------------- /packages/core/life-events/src/index.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | export { default, default as addLife, isLE } from "./lifeEvents"; 3 | export { default as createLifePack } from "./lifePack"; 4 | 5 | export type { 6 | LifeEventsAPI, 7 | EventCallback, 8 | TriggerMethod, 9 | OnAndOnceMethod, 10 | OffMethod, 11 | } from "./types"; 12 | -------------------------------------------------------------------------------- /packages/core/life-events/src/lifePack.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import { LE_PACK_SYM } from "./consts"; 3 | 4 | /** 5 | * create a pack that will only execute when there are registered event handlers 6 | * 7 | * @param creator - a function to return data that will become parameters for the event handler 8 | * array returned means more than 1 parameter. Any other value will be added to an array as a single param 9 | */ 10 | const createLifePack = (creator: () => any): {|resolve: () => Array|} => { 11 | const lp = { 12 | resolve: (): any[] => [].concat(creator()), 13 | }; 14 | 15 | Object.defineProperty(lp, LE_PACK_SYM, { 16 | value: true, 17 | configurable: false, 18 | }); 19 | 20 | return lp; 21 | }; 22 | 23 | export default createLifePack; 24 | -------------------------------------------------------------------------------- /packages/core/life-events/src/tests/lifePack.test.js: -------------------------------------------------------------------------------- 1 | import createLifePack from "../lifePack"; 2 | 3 | describe("lifePack tests", () => { 4 | it("should resolve creator with single param", () => { 5 | const creator = vi.fn(() => "value"); 6 | const lp = createLifePack(creator); 7 | 8 | expect(creator).not.toHaveBeenCalled(); 9 | expect(lp.resolve()).toEqual(["value"]); 10 | }); 11 | 12 | it("should resolve creator with multiple params", () => { 13 | const creator = vi.fn(() => ["value", "value2"]); 14 | const lp = createLifePack(creator); 15 | 16 | expect(creator).not.toHaveBeenCalled(); 17 | expect(lp.resolve()).toEqual(["value", "value2"]); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /packages/core/life-events/src/tests/mocks/rpldy-life-events.mock.js: -------------------------------------------------------------------------------- 1 | import addLife, { createLifePack as orgCreateLifePack } from "@rpldy/life-events"; 2 | 3 | const mockTrigger = vi.fn(); 4 | 5 | vi.mock("@rpldy/life-events", () => { 6 | return { 7 | default: vi.fn((target) => ({ 8 | target, 9 | trigger: mockTrigger, 10 | })), 11 | createLifePack: vi.fn(), 12 | } 13 | }); 14 | 15 | export default addLife; 16 | 17 | export const createLifePack = orgCreateLifePack; 18 | 19 | export { 20 | mockTrigger, 21 | }; 22 | -------------------------------------------------------------------------------- /packages/core/life-events/src/types.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | export type Options = { 4 | //whether clients can register to unspecified events in advance (default: true) 5 | allowRegisterNonExistent?: boolean, 6 | //whether events can be added after initialization (default: true) 7 | canAddEvents?: boolean, 8 | //whether events can be removed (default: true) 9 | canRemoveEvents?: boolean, 10 | //whether to collect registration and trigger stats (default: false) 11 | collectStats?: boolean, 12 | }; 13 | 14 | export type TriggerMethod = (name: any, ...args: any[]) => any; 15 | 16 | export type LifeEventsAPI = { 17 | target: Object, 18 | trigger: TriggerMethod, 19 | addEvent: (name: any) => void, 20 | removeEvent: (name: any) => void, 21 | hasEvent: (name: any) => boolean, 22 | hasEventRegistrations: (name: any) => boolean, 23 | }; 24 | 25 | export type EventCallback = (...args: any[]) => any; 26 | 27 | export type OffMethod = (name: any, cb?: EventCallback) => void; 28 | 29 | export type OnAndOnceMethod = (name: any, cb: EventCallback) => OffMethod; 30 | 31 | export type RegItem = { 32 | name: any, 33 | cb: EventCallback, 34 | once: boolean, 35 | }; 36 | -------------------------------------------------------------------------------- /packages/core/life-events/src/utils.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import { isFunction } from "@rpldy/shared"; 3 | 4 | const validateFunction = (f: any, name: string) => { 5 | if (!isFunction(f)) { 6 | throw new Error(`'${name}' is not a valid function`); 7 | } 8 | }; 9 | 10 | const isUndefined = (val: any): boolean => typeof (val) === "undefined"; 11 | 12 | export { 13 | validateFunction, 14 | isUndefined, 15 | }; 16 | -------------------------------------------------------------------------------- /packages/core/life-events/types/index.test-d.tsx: -------------------------------------------------------------------------------- 1 | import addLife, { createLifePack } from "./index"; 2 | 3 | const testAddLife = (): void => { 4 | const obj = {}; 5 | 6 | const lifeApi = addLife(obj, ["test", "test2"], { 7 | allowRegisterNonExistent: true, 8 | canAddEvents: false, 9 | }); 10 | 11 | lifeApi.target.on("test", (a: number, b: number) => { 12 | console.log("event test triggered", a, b); 13 | }); 14 | 15 | lifeApi.trigger("test", 1, 2); 16 | }; 17 | 18 | const testCreateLifePack = (): void => { 19 | 20 | const creator = () => { 21 | return "value"; 22 | }; 23 | 24 | const lp = createLifePack(creator); 25 | 26 | lp.resolve(); 27 | }; 28 | 29 | export { 30 | testAddLife, 31 | testCreateLifePack, 32 | }; 33 | -------------------------------------------------------------------------------- /packages/core/mock-sender/.npmignore: -------------------------------------------------------------------------------- 1 | src/ 2 | *.story.js 3 | *.stories.js 4 | *.test.js 5 | tests/ 6 | mocks/ 7 | lerna.json 8 | *.log 9 | jest* 10 | .flowconfig 11 | .editorconfig 12 | .eslintignore 13 | .eslintrc.js 14 | .circleci/ 15 | .sb-static/ 16 | .env 17 | .jest-cache/ 18 | .jest-coverage/ 19 | coverage/ 20 | .github/ 21 | 22 | -------------------------------------------------------------------------------- /packages/core/mock-sender/MockSender.storydoc.mdx: -------------------------------------------------------------------------------- 1 | import { Markdown } from "@storybook/blocks"; 2 | import readme from "./README.md?raw"; 3 | 4 | {readme} 5 | -------------------------------------------------------------------------------- /packages/core/mock-sender/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "1.10.0", 3 | "name": "@rpldy/mock-sender", 4 | "description": "mock sender for testing purposes", 5 | "author": "yoav niran (https://github.com/yoavniran)", 6 | "main": "lib/cjs/index.js", 7 | "module": "lib/esm/index.js", 8 | "main:dev": "src/index.js", 9 | "types": "types/index.d.ts", 10 | "license": "MIT", 11 | "homepage": "https://react-uploady.org", 12 | "repository": { 13 | "type": "git", 14 | "url": "https://github.com/rpldy/react-uploady.git", 15 | "directory": "packages/mock-sender" 16 | }, 17 | "funding": { 18 | "type": "opencollective", 19 | "url": "https://opencollective.com/react-uploady" 20 | }, 21 | "scripts": { 22 | "build": "node ../../../scripts/build.mjs" 23 | }, 24 | "dependencies": { 25 | "@rpldy/shared": "^1.10.0" 26 | }, 27 | "devDependencies": { 28 | "@rpldy/sender": "^1.10.0", 29 | "@rpldy/uploader": "^1.10.0", 30 | "flow-bin": "^0.272.2" 31 | }, 32 | "publishConfig": { 33 | "access": "public" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /packages/core/mock-sender/src/consts.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | export const MOCK_SENDER_TYPE = "rpldy-mock-sender"; -------------------------------------------------------------------------------- /packages/core/mock-sender/src/defaults.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import { devFreeze } from "@rpldy/shared"; 3 | 4 | export const MOCK_DEFAULTS: Object = devFreeze({ 5 | delay: 500, 6 | progressIntervals: [10, 25, 50, 75, 99], 7 | }); 8 | -------------------------------------------------------------------------------- /packages/core/mock-sender/src/getMockSenderEnhancer.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import type { UploaderEnhancer, UploaderType, UploaderCreateOptions } from "@rpldy/uploader"; 3 | import createMockSender from "./mockSender"; 4 | import type { MockOptions } from "./types"; 5 | 6 | /** 7 | * an uploader enhancer function to set mock sender instead of current sender 8 | */ 9 | const getMockSenderEnhancer = (options?: MockOptions): UploaderEnhancer => 10 | (uploader: UploaderType): UploaderType => { 11 | const mockSender = createMockSender(options); 12 | uploader.update({ send: mockSender.send }); 13 | return uploader; 14 | }; 15 | 16 | export default getMockSenderEnhancer; 17 | -------------------------------------------------------------------------------- /packages/core/mock-sender/src/index.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import { MOCK_SENDER_TYPE } from "./consts"; 3 | import createMockSender from "./mockSender"; 4 | import getMockSenderEnhancer from "./getMockSenderEnhancer"; 5 | 6 | export default createMockSender; 7 | 8 | export { 9 | MOCK_SENDER_TYPE, 10 | 11 | createMockSender, 12 | getMockSenderEnhancer 13 | }; 14 | 15 | export type { 16 | MockOptions, 17 | } from "./types"; 18 | 19 | -------------------------------------------------------------------------------- /packages/core/mock-sender/src/tests/getMockSenderEnhancer.test.js: -------------------------------------------------------------------------------- 1 | import createMockSender from "../mockSender"; 2 | import getMockSenderEnhancer from "../getMockSenderEnhancer"; 3 | 4 | vi.mock("../mockSender"); 5 | 6 | describe("getMockSenderEnhancer tests", () => { 7 | it("should create enhancer", () => { 8 | createMockSender.mockReturnValueOnce({ send: "mock" }); 9 | 10 | const uploader = { update: vi.fn() }; 11 | const enhancer = getMockSenderEnhancer({ test: true }); 12 | 13 | expect(enhancer(uploader)).toBe(uploader); 14 | expect(uploader.update).toHaveBeenCalledWith({ send: "mock" }); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /packages/core/mock-sender/src/types.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import type { IsSuccessfulCall } from "@rpldy/shared"; 4 | 5 | export type MockOptions = {| 6 | //the time in ms it should take to "upload" (default: 500ms) 7 | delay?: number, 8 | //the file size of the mocked upload, used for progress calculation (default: 1M bytes) 9 | fileSize?: number, 10 | //the mock intervals (percentages) to emit progress events at (default: [10, 25, 50, 75, 100]) 11 | progressIntervals?: number[], 12 | //the mock server response to use (default: {"mock": true, "success": true}) 13 | response?: any, 14 | //the upload request status code (default: 200) 15 | responseStatus?: number, 16 | //callback to use to decide whether upload response is successful or not 17 | isSuccessfulCall?: IsSuccessfulCall 18 | |}; 19 | 20 | export type MandatoryMockOptions = {[key in keyof MockOptions]: $NonMaybeType} 21 | -------------------------------------------------------------------------------- /packages/core/mock-sender/types/index.d.ts: -------------------------------------------------------------------------------- 1 | import { SendMethod } from "@rpldy/sender"; 2 | import { UploaderEnhancer } from "@rpldy/uploader"; 3 | import type { IsSuccessfulCall } from "@rpldy/shared"; 4 | 5 | export interface MockOptions { 6 | delay?: number; 7 | fileSize?: number; 8 | progressIntervals?: number[]; 9 | response?: any; 10 | responseStatus?: number; 11 | isSuccessfulCall?: IsSuccessfulCall; 12 | } 13 | 14 | export type MockSender = { 15 | update: (updated: MockOptions) => void; 16 | send: SendMethod; 17 | }; 18 | 19 | export const createMockSender: (option: MockOptions) => MockSender; 20 | 21 | export const getMockSenderEnhancer: (options?: MockOptions) => UploaderEnhancer; 22 | 23 | export default createMockSender; 24 | -------------------------------------------------------------------------------- /packages/core/raw-uploader/.npmignore: -------------------------------------------------------------------------------- 1 | src/ 2 | *.story.js 3 | *.stories.js 4 | *.test.js 5 | tests/ 6 | mocks/ 7 | lerna.json 8 | *.log 9 | jest* 10 | .flowconfig 11 | .editorconfig 12 | .eslintignore 13 | .eslintrc.js 14 | .circleci/ 15 | .sb-static/ 16 | .env 17 | .jest-cache/ 18 | .jest-coverage/ 19 | coverage/ 20 | .github/ 21 | 22 | -------------------------------------------------------------------------------- /packages/core/raw-uploader/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "1.10.0", 3 | "name": "@rpldy/raw-uploader", 4 | "description": "placeholder package for now", 5 | "author": "yoav niran (https://github.com/yoavniran)", 6 | "main": "lib/cjs/index.js", 7 | "module": "lib/esm/index.js", 8 | "main:dev": "src/index.js", 9 | "types": "types/index.d.ts", 10 | "license": "MIT", 11 | "homepage": "https://react-uploady.org", 12 | "repository": { 13 | "type": "git", 14 | "url": "https://github.com/rpldy/react-uploady.git", 15 | "directory": "packages/raw-uploader" 16 | }, 17 | "funding": { 18 | "type": "opencollective", 19 | "url": "https://opencollective.com/react-uploady" 20 | }, 21 | "keywords": [ 22 | "file", 23 | "upload", 24 | "file upload", 25 | "react", 26 | "hooks" 27 | ], 28 | "scripts": { 29 | "build": "node ../../../scripts/build.mjs" 30 | }, 31 | "dependencies": { 32 | "@rpldy/life-events": "^1.10.0", 33 | "@rpldy/shared": "^1.10.0" 34 | }, 35 | "devDependencies": { 36 | "flow-bin": "^0.272.2" 37 | }, 38 | "publishConfig": { 39 | "access": "public" 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /packages/core/raw-uploader/src/index.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | export type { 4 | UploaderEnhancer, 5 | RawCreateOptions, 6 | UploaderType, 7 | } from "./types"; 8 | -------------------------------------------------------------------------------- /packages/core/raw-uploader/types/index.test-d.ts: -------------------------------------------------------------------------------- 1 | //TODO: move tests here when this package holds the actual uploader implementation 2 | -------------------------------------------------------------------------------- /packages/core/retry/.npmignore: -------------------------------------------------------------------------------- 1 | src/ 2 | *.story.js 3 | *.stories.js 4 | *.test.js 5 | tests/ 6 | mocks/ 7 | lerna.json 8 | *.log 9 | jest* 10 | .flowconfig 11 | .editorconfig 12 | .eslintignore 13 | .eslintrc.js 14 | .circleci/ 15 | .sb-static/ 16 | .env 17 | .jest-cache/ 18 | .jest-coverage/ 19 | coverage/ 20 | .github/ 21 | 22 | -------------------------------------------------------------------------------- /packages/core/retry/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "1.10.0", 3 | "name": "@rpldy/retry", 4 | "description": "adds the capability to retry failed uploads", 5 | "author": "yoav niran (https://github.com/yoavniran)", 6 | "main": "lib/cjs/index.js", 7 | "module": "lib/esm/index.js", 8 | "main:dev": "src/index.js", 9 | "types": "types/index.d.ts", 10 | "license": "MIT", 11 | "homepage": "https://react-uploady.org", 12 | "repository": { 13 | "type": "git", 14 | "url": "https://github.com/rpldy/react-uploady.git", 15 | "directory": "packages/retry" 16 | }, 17 | "funding": { 18 | "type": "opencollective", 19 | "url": "https://opencollective.com/react-uploady" 20 | }, 21 | "scripts": { 22 | "build": "node ../../../scripts/build.mjs" 23 | }, 24 | "dependencies": { 25 | "@rpldy/shared": "^1.10.0", 26 | "@rpldy/simple-state": "^1.10.0", 27 | "@rpldy/uploader": "^1.10.0" 28 | }, 29 | "devDependencies": { 30 | "flow-bin": "^0.272.2" 31 | }, 32 | "publishConfig": { 33 | "access": "public" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /packages/core/retry/src/consts.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | export const RETRY_EXT: symbol = Symbol.for("__upldy-retry__"); 4 | 5 | export const RETRY_EVENT = "RETRY_EVENT"; 6 | -------------------------------------------------------------------------------- /packages/core/retry/src/index.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | export { default, default as retryEnhancer } from "./retry"; 4 | 5 | export { 6 | RETRY_EVENT, 7 | RETRY_EXT, 8 | } from "./consts"; 9 | 10 | export type { 11 | BatchRetryMethod, 12 | RetryMethod 13 | } from "./types"; 14 | 15 | 16 | -------------------------------------------------------------------------------- /packages/core/retry/src/types.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import type { BatchItem, UploadOptions } from "@rpldy/shared"; 3 | 4 | export type State = { 5 | batchIdsMap: { 6 | [string]: string[] 7 | }, 8 | failed: { 9 | [string]: BatchItem 10 | }, 11 | }; 12 | 13 | export type RetryState = { 14 | updateState: ((State) => void) => void, 15 | getState: () => State, 16 | }; 17 | 18 | export type BatchRetryMethod = (batchId: string, options?: UploadOptions) => boolean; 19 | 20 | export type RetryMethod = (itemId?: string, options?: UploadOptions) => boolean; 21 | -------------------------------------------------------------------------------- /packages/core/retry/types/index.d.ts: -------------------------------------------------------------------------------- 1 | import { UploadOptions } from "@rpldy/shared"; 2 | import { UploaderEnhancer } from "@rpldy/uploader"; 3 | 4 | export const retryEnhancer: UploaderEnhancer; 5 | 6 | export const RETRY_EVENT = "RETRY_EVENT"; 7 | 8 | export type RetryMethod = (id?: string, options?: UploadOptions) => boolean; 9 | 10 | export type RetryBatchMethod = (batchId: string, options?: UploadOptions) => boolean; 11 | 12 | export default retryEnhancer; 13 | -------------------------------------------------------------------------------- /packages/core/retry/types/index.test-d.tsx: -------------------------------------------------------------------------------- 1 | import retryEnhancer from "./index"; 2 | import createUploader from "@rpldy/uploader"; 3 | 4 | const testEnhancer = (): void => { 5 | const uploader = createUploader({ 6 | enhancer: retryEnhancer, 7 | }); 8 | 9 | console.log(uploader.getOptions()); 10 | }; 11 | 12 | export { 13 | testEnhancer, 14 | }; 15 | -------------------------------------------------------------------------------- /packages/core/safe-storage/.npmignore: -------------------------------------------------------------------------------- 1 | src/ 2 | *.story.js 3 | *.stories.js 4 | *.test.js 5 | tests/ 6 | mocks/ 7 | lerna.json 8 | *.log 9 | jest* 10 | .flowconfig 11 | .editorconfig 12 | .eslintignore 13 | .eslintrc.js 14 | .circleci/ 15 | .sb-static/ 16 | .env 17 | .jest-cache/ 18 | .jest-coverage/ 19 | coverage/ 20 | .github/ 21 | 22 | -------------------------------------------------------------------------------- /packages/core/safe-storage/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "1.10.0", 3 | "name": "@rpldy/safe-storage", 4 | "description": "safe (dont throw) versions of local and session storage", 5 | "author": "yoav niran (https://github.com/yoavniran)", 6 | "main": "lib/cjs/index.js", 7 | "module": "lib/esm/index.js", 8 | "main:dev": "src/index.js", 9 | "types": "types/index.d.ts", 10 | "license": "MIT", 11 | "homepage": "https://react-uploady.org", 12 | "repository": { 13 | "type": "git", 14 | "url": "https://github.com/rpldy/react-uploady.git", 15 | "directory": "packages/safe-storage" 16 | }, 17 | "funding": { 18 | "type": "opencollective", 19 | "url": "https://opencollective.com/react-uploady" 20 | }, 21 | "scripts": { 22 | "build": "node ../../../scripts/build.mjs" 23 | }, 24 | "dependencies": { 25 | "@rpldy/shared": "^1.10.0" 26 | }, 27 | "devDependencies": { 28 | "flow-bin": "^0.272.2" 29 | }, 30 | "publishConfig": { 31 | "access": "public" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /packages/core/safe-storage/src/index.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import { safeLocalStorage, safeSessionStorage } from "./storage"; 3 | 4 | export { 5 | safeLocalStorage, 6 | safeSessionStorage, 7 | }; 8 | -------------------------------------------------------------------------------- /packages/core/safe-storage/src/storage.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import createSafeStorage from "./safeStorageCreator"; 3 | import type { SafeStorage } from "./types"; 4 | 5 | const safeLocalStorage = (createSafeStorage("localStorage"): SafeStorage); 6 | const safeSessionStorage = (createSafeStorage("sessionStorage"): SafeStorage); 7 | 8 | export { 9 | safeLocalStorage, 10 | safeSessionStorage, 11 | }; 12 | 13 | -------------------------------------------------------------------------------- /packages/core/safe-storage/src/types.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | export type SafeStorage = { 4 | length: number; 5 | getItem(key: string): ?string; 6 | setItem(key: string, data: string): void; 7 | clear(): void; 8 | removeItem(key: string): void; 9 | key(index: number): ?string; 10 | isSupported: boolean; 11 | }; 12 | -------------------------------------------------------------------------------- /packages/core/safe-storage/types/index.d.ts: -------------------------------------------------------------------------------- 1 | export const safeLocalStorage: Storage; 2 | 3 | export const safeSessionStorage: Storage; 4 | 5 | -------------------------------------------------------------------------------- /packages/core/safe-storage/types/index.test-d.ts: -------------------------------------------------------------------------------- 1 | import { safeLocalStorage, safeSessionStorage } from "./index"; 2 | 3 | const testSafeLocalStorage = (): string | null => { 4 | 5 | safeLocalStorage.setItem("test", "value"); 6 | const value = safeLocalStorage.getItem("test"); 7 | safeLocalStorage.removeItem("test"); 8 | 9 | return value; 10 | }; 11 | 12 | const testSafeSessionStorage = (): string | null => { 13 | 14 | safeSessionStorage.setItem("test", "value"); 15 | const value = safeSessionStorage.getItem("test"); 16 | safeSessionStorage.removeItem("test"); 17 | 18 | return value; 19 | }; 20 | 21 | export { 22 | testSafeLocalStorage, 23 | testSafeSessionStorage, 24 | }; -------------------------------------------------------------------------------- /packages/core/sender/.npmignore: -------------------------------------------------------------------------------- 1 | src/ 2 | *.story.js 3 | *.stories.js 4 | *.test.js 5 | tests/ 6 | mocks/ 7 | lerna.json 8 | *.log 9 | jest* 10 | .flowconfig 11 | .editorconfig 12 | .eslintignore 13 | .eslintrc.js 14 | .circleci/ 15 | .sb-static/ 16 | .env 17 | .jest-cache/ 18 | .jest-coverage/ 19 | coverage/ 20 | .github/ 21 | 22 | -------------------------------------------------------------------------------- /packages/core/sender/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "1.10.0", 3 | "name": "@rpldy/sender", 4 | "description": "react-uploady's default XHR sender", 5 | "author": "yoav niran (https://github.com/yoavniran)", 6 | "main": "lib/cjs/index.js", 7 | "module": "lib/esm/index.js", 8 | "main:dev": "src/index.js", 9 | "types": "types/index.d.ts", 10 | "license": "MIT", 11 | "homepage": "https://react-uploady.org", 12 | "repository": { 13 | "type": "git", 14 | "url": "https://github.com/rpldy/react-uploady.git", 15 | "directory": "packages/sender" 16 | }, 17 | "funding": { 18 | "type": "opencollective", 19 | "url": "https://opencollective.com/react-uploady" 20 | }, 21 | "scripts": { 22 | "build": "node ../../../scripts/build.mjs" 23 | }, 24 | "dependencies": { 25 | "@rpldy/shared": "^1.10.0" 26 | }, 27 | "devDependencies": { 28 | "flow-bin": "^0.272.2" 29 | }, 30 | "publishConfig": { 31 | "access": "public" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /packages/core/sender/src/MissingUrlError.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | export default class MissingUrlError extends Error { 3 | constructor(senderType: string) { 4 | super(`${senderType} didn't receive upload URL`); 5 | this.name = "MissingUrlError"; 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/core/sender/src/consts.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | export const XHR_SENDER_TYPE = "rpldy-sender"; -------------------------------------------------------------------------------- /packages/core/sender/src/index.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import getXhrSend from "./xhrSender/xhrSender"; 3 | import { XHR_SENDER_TYPE } from "./consts"; 4 | import type { SendMethod, SendOptions } from "./types"; 5 | 6 | const send: SendMethod = getXhrSend(); 7 | 8 | export default send; 9 | 10 | export { 11 | send, 12 | getXhrSend, 13 | XHR_SENDER_TYPE, 14 | }; 15 | 16 | export { default as MissingUrlError } from "./MissingUrlError"; 17 | 18 | export type { 19 | SendMethod, 20 | OnProgress, 21 | SenderProgressEvent, 22 | SendOptions, 23 | SendResult, 24 | } from "./types"; 25 | 26 | -------------------------------------------------------------------------------- /packages/core/sender/types/index.test-d.tsx: -------------------------------------------------------------------------------- 1 | import send, { OnProgress } from "./index"; 2 | import { createBatchItem } from "@rpldy/shared"; 3 | 4 | const batchItem = createBatchItem({ name: "test.png", size: 123, type: "image", lastModified: 1234 }, "b1"); 5 | 6 | const onProgress: OnProgress = ({ loaded, total }) => { 7 | console.log("uploaded/completed", { loaded, total }); 8 | }; 9 | 10 | const testSend = async (): Promise => { 11 | 12 | const sendResult = send([batchItem], "test.com", { 13 | method: "POST", 14 | forceJsonResponse: true, 15 | paramName: "file", 16 | headers: { 17 | "x-test": "111" 18 | } 19 | }, onProgress); 20 | 21 | const result = await sendResult.request; 22 | 23 | console.log(result.response); 24 | }; 25 | 26 | export { 27 | testSend, 28 | }; 29 | -------------------------------------------------------------------------------- /packages/core/shared/.npmignore: -------------------------------------------------------------------------------- 1 | src/ 2 | *.story.js 3 | *.stories.js 4 | *.test.js 5 | tests/ 6 | mocks/ 7 | lerna.json 8 | *.log 9 | jest* 10 | .flowconfig 11 | .editorconfig 12 | .eslintignore 13 | .eslintrc.js 14 | .circleci/ 15 | .sb-static/ 16 | .env 17 | .jest-cache/ 18 | .jest-coverage/ 19 | coverage/ 20 | .github/ 21 | 22 | -------------------------------------------------------------------------------- /packages/core/shared/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "1.10.0", 3 | "name": "@rpldy/shared", 4 | "description": "internal set of utils+types for react-uploady", 5 | "author": "yoav niran (https://github.com/yoavniran)", 6 | "main": "lib/cjs/index.js", 7 | "module": "lib/esm/index.js", 8 | "main:dev": "src/index.js", 9 | "types": "types/index.d.ts", 10 | "license": "MIT", 11 | "homepage": "https://react-uploady.org", 12 | "repository": { 13 | "type": "git", 14 | "url": "https://github.com/rpldy/react-uploady.git", 15 | "directory": "packages/shared" 16 | }, 17 | "funding": { 18 | "type": "opencollective", 19 | "url": "https://opencollective.com/react-uploady" 20 | }, 21 | "scripts": { 22 | "build": "node ../../../scripts/build.mjs" 23 | }, 24 | "devDependencies": { 25 | "flow-bin": "^0.272.2" 26 | }, 27 | "dependencies": { 28 | "invariant": "^2.2.4", 29 | "just-throttle": "^4.2.0" 30 | }, 31 | "publishConfig": { 32 | "access": "public" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /packages/core/shared/src/consts.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | export const DEBUG_LOG_KEY = "__rpldy-logger-debug__"; 3 | -------------------------------------------------------------------------------- /packages/core/shared/src/enums.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | enum BATCH_STATES { 4 | PENDING ="pending", 5 | ADDED = "added", 6 | PROCESSING = "processing", 7 | UPLOADING = "uploading", 8 | CANCELLED = "cancelled", 9 | FINISHED = "finished", 10 | ABORTED = "aborted", 11 | ERROR = "error", 12 | } 13 | 14 | enum FILE_STATES { 15 | PENDING = "pending", 16 | ADDED = "added", 17 | UPLOADING = "uploading", 18 | CANCELLED = "cancelled", 19 | FINISHED = "finished", 20 | ERROR = "error", 21 | ABORTED = "aborted", 22 | } 23 | 24 | export { 25 | BATCH_STATES, 26 | FILE_STATES, 27 | }; 28 | -------------------------------------------------------------------------------- /packages/core/shared/src/logger.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import { DEBUG_LOG_KEY } from "./consts"; 3 | import hasWindow from "./utils/hasWindow"; 4 | 5 | let isDebug: ?boolean = null; 6 | 7 | const isDebugOn = (): boolean | string => { 8 | if (typeof isDebug !== "boolean") { 9 | isDebug = (hasWindow() && 10 | (("location" in window && !!~window.location.search.indexOf("rpldy_debug=true")) || 11 | window[DEBUG_LOG_KEY] === true)); 12 | } 13 | 14 | return !!isDebug; 15 | }; 16 | 17 | const setDebug = (debugOn: boolean) => { 18 | if (hasWindow()) { 19 | window[DEBUG_LOG_KEY] = debugOn; 20 | } 21 | 22 | isDebug = debugOn ? true : null; 23 | }; 24 | 25 | const debugLog = (...args: any[]) => { 26 | if (isDebugOn()) { 27 | // eslint-disable-next-line no-console 28 | console.log(...args); 29 | } 30 | }; 31 | 32 | export { 33 | isDebugOn, 34 | setDebug, 35 | debugLog, 36 | }; 37 | -------------------------------------------------------------------------------- /packages/core/shared/src/request/XhrPromise.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | class XhrPromise extends Promise { 4 | xhr: XMLHttpRequest; 5 | 6 | constructor(fn: any, req: XMLHttpRequest) { 7 | super(fn); 8 | this.xhr = req; 9 | } 10 | } 11 | 12 | export default XhrPromise; 13 | -------------------------------------------------------------------------------- /packages/core/shared/src/request/index.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import request from "./request"; 3 | 4 | export { default as parseResponseHeaders } from "./parseResponseHeaders"; 5 | export { default as XhrPromise } from "./XhrPromise"; 6 | 7 | export default request; 8 | 9 | -------------------------------------------------------------------------------- /packages/core/shared/src/request/parseResponseHeaders.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import { debugLog } from "../logger"; 3 | import type { Headers } from "../types"; 4 | 5 | const parseResponseHeaders = (xhr: XMLHttpRequest): ?Headers => { 6 | let resHeaders; 7 | 8 | try { 9 | resHeaders = xhr.getAllResponseHeaders().trim() 10 | .split(/[\r\n]+/) 11 | .reduce((res, line: string) => { 12 | const [key, val] = line.split(": "); 13 | res[key] = val; 14 | return res; 15 | }, ({}: { [string]: any })); 16 | } catch (ex) { // eslint-disable-line 17 | debugLog("uploady.request: failed to read response headers", xhr); 18 | } 19 | 20 | return resHeaders; 21 | }; 22 | 23 | export default parseResponseHeaders; 24 | -------------------------------------------------------------------------------- /packages/core/shared/src/request/request.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import XhrPromise from "./XhrPromise"; 3 | import type { RequestOptions } from "../types"; 4 | 5 | const setHeaders = (req: XMLHttpRequest, headers: Object): ?Headers => { 6 | if (headers) { 7 | Object.keys(headers).forEach((name) => { 8 | if (headers[name] !== undefined) { 9 | req.setRequestHeader(name, headers[name]); 10 | } 11 | }); 12 | } 13 | }; 14 | 15 | const request = (url: string, data?: mixed, options: ?RequestOptions = {}): XhrPromise => { 16 | const req = new XMLHttpRequest(); 17 | 18 | return new XhrPromise( 19 | (resolve, reject) => { 20 | req.onerror = () => reject(req); 21 | req.ontimeout = () => reject(req); 22 | req.onabort = () => reject(req); 23 | req.onload = () => resolve(req); 24 | 25 | req.open((options?.method || "GET"), url); 26 | setHeaders(req, options?.headers); 27 | req.withCredentials = !!options?.withCredentials; 28 | 29 | options?.preSend?.(req); 30 | 31 | req.send(data); 32 | }, req); 33 | }; 34 | 35 | export default request; 36 | -------------------------------------------------------------------------------- /packages/core/shared/src/request/tests/parseResponseHeaders.test.js: -------------------------------------------------------------------------------- 1 | import parseResponseHeaders from "../parseResponseHeaders"; 2 | 3 | describe("parseResponseHeaders tests", () => { 4 | const getAllResponseHeaders = vi.fn(() => `content-type: application/json 5 | x-header: test`); 6 | 7 | it("should parse headers", () => { 8 | const headers = parseResponseHeaders({ 9 | getAllResponseHeaders 10 | }); 11 | 12 | expect(headers).toEqual({ 13 | "content-type": "application/json", 14 | "x-header": "test", 15 | }); 16 | }); 17 | 18 | it("should fail silently", () => { 19 | getAllResponseHeaders.mockImplementationOnce(() => { 20 | throw new Error("bla"); 21 | }); 22 | 23 | const headers = parseResponseHeaders({ 24 | getAllResponseHeaders 25 | }); 26 | 27 | expect(headers).toBeUndefined(); 28 | }); 29 | }); 30 | -------------------------------------------------------------------------------- /packages/core/shared/src/triggerCancellable.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import type { Trigger, TriggerCancellableOutcome } from "./types"; 3 | 4 | const triggerCancellable = (trigger: Trigger, event?: string, ...args?: mixed[]): TriggerCancellableOutcome => { 5 | const doTrigger = (event: string, ...args?: mixed[]): Promise => 6 | new Promise((resolve, reject) => { 7 | const results: Promise[] = trigger(event, ...args); 8 | 9 | if (results && results.length) { 10 | Promise.all(results) 11 | .catch(reject) 12 | .then((resolvedResults) => 13 | resolvedResults && resolve(!!~resolvedResults 14 | .findIndex((r: any) => r === false))); 15 | } else { 16 | resolve(false); 17 | } 18 | }); 19 | 20 | return event ? doTrigger(event, ...args) : doTrigger; 21 | }; 22 | 23 | export default triggerCancellable; 24 | -------------------------------------------------------------------------------- /packages/core/shared/src/utils/clone.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import merge, { isMergeObj } from "./merge"; 3 | 4 | type MergeFn = (target: Object, ...sources: Object[]) => Object; 5 | 6 | /** 7 | * does deep clone to the passed object, returning a new object 8 | * @param obj 9 | * @param mergeFn the merge function to use (default: utils/merge) 10 | * @returns {Object} 11 | */ 12 | const clone = (obj: Object, mergeFn: MergeFn = merge): Object => 13 | isMergeObj(obj) ? 14 | mergeFn((Array.isArray(obj) ? [] : {}), obj) : 15 | obj; 16 | 17 | export default clone; 18 | -------------------------------------------------------------------------------- /packages/core/shared/src/utils/devFreeze.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import isProduction from "./isProduction"; 3 | 4 | const devFreeze = (obj: Object): Object => 5 | isProduction() ? obj : Object.freeze(obj); 6 | 7 | export default devFreeze; 8 | -------------------------------------------------------------------------------- /packages/core/shared/src/utils/hasWindow.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | const hasWindow = (): boolean => { 4 | return typeof window === "object" && !!window.document; 5 | }; 6 | 7 | export default hasWindow; 8 | -------------------------------------------------------------------------------- /packages/core/shared/src/utils/index.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import isFunction from "./isFunction"; 3 | import isSamePropInArrays from "./isSamePropInArrays"; 4 | import devFreeze from "./devFreeze"; 5 | import merge, { getMerge } from "./merge"; 6 | import clone from "./clone"; 7 | import pick from "./pick"; 8 | import isPlainObject from "./isPlainObject"; 9 | import hasWindow from "./hasWindow"; 10 | import isProduction from "./isProduction"; 11 | import isPromise from "./isPromise"; 12 | import scheduleIdleWork from "./scheduleIdleWork"; 13 | import isEmpty from "./isEmpty"; 14 | 15 | export { 16 | isFunction, 17 | isSamePropInArrays, 18 | devFreeze, 19 | merge, 20 | getMerge, 21 | clone, 22 | pick, 23 | isPlainObject, 24 | hasWindow, 25 | isProduction, 26 | isPromise, 27 | scheduleIdleWork, 28 | isEmpty, 29 | }; 30 | -------------------------------------------------------------------------------- /packages/core/shared/src/utils/isEmpty.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | type Empty = null | void; 4 | 5 | function isEmpty (val: any): val is Empty { 6 | return (val === null || val === undefined); 7 | } 8 | 9 | export default isEmpty; 10 | -------------------------------------------------------------------------------- /packages/core/shared/src/utils/isFunction.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | function isFunction(f: any): f is (...args: any[]) => any { 4 | return typeof (f) === "function"; 5 | } 6 | 7 | module.exports = isFunction; 8 | -------------------------------------------------------------------------------- /packages/core/shared/src/utils/isPlainObject.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | const isPlainObject = (obj: Object): boolean => { 3 | const proto = Object.getPrototypeOf(Object(obj)); 4 | 5 | return !!obj && 6 | typeof obj === "object" && 7 | (proto?.constructor.name === "Object" || proto === null) && 8 | //$FlowExpectedError[method-unbinding] 9 | !Object.prototype.hasOwnProperty.call(obj, "__proto__"); 10 | }; 11 | 12 | export default isPlainObject; 13 | -------------------------------------------------------------------------------- /packages/core/shared/src/utils/isProduction.js: -------------------------------------------------------------------------------- 1 | function isProduction() { 2 | return process.env.NODE_ENV === "production"; 3 | } 4 | 5 | module.exports = isProduction; 6 | -------------------------------------------------------------------------------- /packages/core/shared/src/utils/isPromise.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | function isPromise(obj: any): implies obj is Promise { 4 | return !!obj && typeof obj === "object" && typeof obj.then === "function"; 5 | } 6 | 7 | export default isPromise; 8 | 9 | -------------------------------------------------------------------------------- /packages/core/shared/src/utils/isSamePropInArrays.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | const getPropsExtractor = (prop: string | string[]) => { 4 | const props = [].concat(prop); 5 | 6 | return (arr: Object[]) => 7 | arr.map((i) => props.map((p) => i[p]).join()); 8 | }; 9 | 10 | /* 11 | stringifies props together - will return true for same type of value (ex: function) 12 | even if refs are different 13 | */ 14 | const isSamePropInArrays = (arr1: Object[], arr2: Object[], prop: string | string[]): boolean => { 15 | let diff = true; 16 | const propsExtractor = getPropsExtractor(prop); 17 | 18 | if (arr1 && arr2 && arr1.length === arr2.length) { 19 | const props1 = propsExtractor(arr1), 20 | props2 = propsExtractor(arr2); 21 | 22 | diff = !!props1.find((p, i) => p !== props2[i]); 23 | } 24 | 25 | return !diff; 26 | }; 27 | 28 | 29 | export default isSamePropInArrays; 30 | -------------------------------------------------------------------------------- /packages/core/shared/src/utils/pick.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | const pick = (obj: Object, props: string[]): ?Object => 4 | obj && Object.keys(obj).reduce((res, key) => { 5 | if (~props.indexOf(key)) { 6 | res[key] = obj[key]; 7 | } 8 | return res; 9 | }, ({}: { [string]: any })); 10 | 11 | export default pick; 12 | -------------------------------------------------------------------------------- /packages/core/shared/src/utils/scheduleIdleWork.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import hasWindow from "./hasWindow"; 3 | 4 | const supportsIdle = (hasWindow() && window.requestIdleCallback); 5 | const scheduler = supportsIdle ? window.requestIdleCallback : setTimeout; 6 | const clear = supportsIdle ? window.cancelIdleCallback : clearTimeout; 7 | 8 | type ClearSchedule = () => void; 9 | 10 | const scheduleIdleWork = (fn: Function, timeout: number = 0): ClearSchedule => { 11 | //$FlowIssue[incompatible-call] flow doesnt understand we only pass object to requestIdle... 12 | const handler = scheduler(fn, supportsIdle ? { timeout } : timeout); 13 | 14 | return () => clear(handler); 15 | }; 16 | 17 | export default scheduleIdleWork; 18 | -------------------------------------------------------------------------------- /packages/core/shared/src/utils/tests/devFreeze.test.js: -------------------------------------------------------------------------------- 1 | import devFreeze from "../devFreeze"; 2 | 3 | describe("devFreeze tests", () => { 4 | let env; 5 | 6 | beforeAll(() => { 7 | env = process.env.NODE_ENV; 8 | }); 9 | 10 | afterAll(() => { 11 | process.env.NODE_ENV = env; 12 | }); 13 | 14 | it("should freeze when not in production", () => { 15 | const obj = {}; 16 | const frozen = devFreeze(obj); 17 | expect(() => { 18 | frozen.test = true; 19 | }).toThrow(); 20 | }); 21 | 22 | it("should not freeze when in production", () => { 23 | 24 | process.env.NODE_ENV = "production"; 25 | 26 | const obj = {}; 27 | const frozen = devFreeze(obj); 28 | frozen.test = true; 29 | expect(frozen.test).toBe(true); 30 | }); 31 | 32 | }); 33 | -------------------------------------------------------------------------------- /packages/core/shared/src/utils/tests/isFunction.test.js: -------------------------------------------------------------------------------- 1 | import isFunction from "../isFunction"; 2 | 3 | describe("isFunction tests", () => { 4 | 5 | it("should return true for function", () => { 6 | 7 | const fn1 = () => { 8 | }; 9 | 10 | function fn2() { 11 | } 12 | 13 | expect(isFunction(fn1)).toBe(true); 14 | expect(isFunction(fn2)).toBe(true); 15 | }); 16 | 17 | it("should return false for anything not a function", () => { 18 | 19 | expect(isFunction({})).toBe(false); 20 | expect(isFunction(null)).toBe(false); 21 | expect(isFunction(false)).toBe(false); 22 | expect(isFunction("a")).toBe(false); 23 | expect(isFunction(true)).toBe(false); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /packages/core/shared/src/utils/tests/isPlainObject.test.js: -------------------------------------------------------------------------------- 1 | import isPlainObject from "../isPlainObject"; 2 | 3 | describe("isPlainObject tests", () => { 4 | it.each([ 5 | [false, JSON.parse(`{"__proto__":{"pollutedKey":123}}`)], 6 | [false, true], 7 | [false, false], 8 | [false, 0], 9 | [false, 1], 10 | [false, [1,2,3]], 11 | [false, []], 12 | [false, ()=>{}], 13 | [false, function(){}], 14 | [false, new Map()], 15 | [false, new Set()], 16 | [true, {}], 17 | [true, Object({})], 18 | [true, Object.create(null)], 19 | [true, new Object({})], 20 | ])("should return %s for %s", (result, val) => { 21 | expect(isPlainObject(val)).toBe(result); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /packages/core/shared/src/utils/tests/isPromise.test.js: -------------------------------------------------------------------------------- 1 | import isPromise from "../isPromise"; 2 | 3 | describe("isPromise tests", () => { 4 | it.each([ 5 | [null, false], 6 | [undefined, false], 7 | [false, false], 8 | ["dasd", false], 9 | [10, false], 10 | [{}, false], 11 | [{ test: true }, false], 12 | [{ then: true }, false], 13 | [new Promise((resolve) => { resolve(); }), true], 14 | [Promise.resolve(false), true], 15 | ]) 16 | ("test isPromise with - %s should be: %s", (obj, result) => { 17 | expect(isPromise(obj)).toBe(result); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /packages/core/shared/src/utils/tests/pick.test.js: -------------------------------------------------------------------------------- 1 | import pick from "../pick"; 2 | 3 | describe("pick tests", () => { 4 | it("should return null for null", () => { 5 | expect(pick(null)).toBeNull(); 6 | }); 7 | 8 | it("should return empty obj for no props", () => { 9 | expect(pick({ test: true }, [])).toEqual({}); 10 | }); 11 | 12 | it("should return requested props", () => { 13 | expect(pick({ 14 | foo: "aaa", 15 | bar: "bbb", 16 | test: true, 17 | more: { level: 2 } 18 | }, ["foo", "more"])).toEqual({ 19 | foo: "aaa", 20 | more: { level: 2 } 21 | }); 22 | }); 23 | 24 | it("should not allow prototype pollution", () => { 25 | const b = JSON.parse(`{"__proto__":{"pollutedKey":123}, "foo": "bar"}`); 26 | 27 | const res = pick(b, ["foo"]); 28 | 29 | expect(res).toEqual({ foo: "bar" }); 30 | expect(res.pollutedKey).toBeUndefined(); 31 | expect({}.pollutedKey).toBeUndefined(); 32 | }); 33 | }); 34 | -------------------------------------------------------------------------------- /packages/core/shared/types/index.test-d.tsx: -------------------------------------------------------------------------------- 1 | import { createBatchItem } from "./index"; 2 | 3 | 4 | const testCreateBatchItem = (): void => { 5 | 6 | const batchItemFile = createBatchItem({ name: "test.png", size: 123, type: "image", lastModified: 1234 }, "b1"); 7 | 8 | console.log(batchItemFile.id, batchItemFile.batchId, batchItemFile.file.name); 9 | 10 | const batchItemUrl = createBatchItem("https://my-file.com", "b2"); 11 | 12 | console.log(batchItemUrl.id, batchItemUrl.batchId, batchItemUrl.url); 13 | }; 14 | 15 | export { 16 | testCreateBatchItem, 17 | }; 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /packages/core/simple-state/.npmignore: -------------------------------------------------------------------------------- 1 | src/ 2 | *.story.js 3 | *.stories.js 4 | *.test.js 5 | tests/ 6 | mocks/ 7 | lerna.json 8 | *.log 9 | jest* 10 | .flowconfig 11 | .editorconfig 12 | .eslintignore 13 | .eslintrc.js 14 | .circleci/ 15 | .sb-static/ 16 | .env 17 | .jest-cache/ 18 | .jest-coverage/ 19 | coverage/ 20 | .github/ 21 | 22 | -------------------------------------------------------------------------------- /packages/core/simple-state/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "1.10.0", 3 | "name": "@rpldy/simple-state", 4 | "description": "deep proxy object, so it's only updateable through an update method", 5 | "author": "yoav niran (https://github.com/yoavniran)", 6 | "main": "lib/cjs/index.js", 7 | "module": "lib/esm/index.js", 8 | "main:dev": "src/index.js", 9 | "types": "types/index.d.ts", 10 | "license": "MIT", 11 | "homepage": "https://react-uploady.org", 12 | "repository": { 13 | "type": "git", 14 | "url": "https://github.com/rpldy/react-uploady.git", 15 | "directory": "packages/simple-state" 16 | }, 17 | "funding": { 18 | "type": "opencollective", 19 | "url": "https://opencollective.com/react-uploady" 20 | }, 21 | "scripts": { 22 | "build": "node ../../../scripts/build.mjs" 23 | }, 24 | "dependencies": { 25 | "@rpldy/shared": "^1.10.0" 26 | }, 27 | "devDependencies": { 28 | "flow-bin": "^0.272.2" 29 | }, 30 | "publishConfig": { 31 | "access": "public" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /packages/core/simple-state/src/consts.js: -------------------------------------------------------------------------------- 1 | 2 | export const PROXY_SYM = Symbol.for("__rpldy-sstt-proxy__"); 3 | 4 | export const STATE_SYM = Symbol.for("__rpldy-sstt-state__"); 5 | -------------------------------------------------------------------------------- /packages/core/simple-state/src/index.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | export { default, default as createState, unwrap } from "./createState"; 4 | 5 | export { 6 | isProxy, 7 | isProxiable, 8 | } from "./utils"; 9 | 10 | -------------------------------------------------------------------------------- /packages/core/simple-state/src/tests/mocks/getTestData.mock.js: -------------------------------------------------------------------------------- 1 | export default () => ({ 2 | arr: [1, 2, 3], 3 | sub: { 4 | foo: "bar", 5 | more: { 6 | test: true 7 | }, 8 | }, 9 | children: [ 10 | { 11 | id: 1, 12 | name: "child-a" 13 | }, 14 | { 15 | id: 2, 16 | name: "child-b" 17 | } 18 | ], 19 | }); 20 | -------------------------------------------------------------------------------- /packages/core/simple-state/src/types.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | export type SimpleState = { 4 | state: T, 5 | update: ((T) => void) => T, 6 | unwrap: (?Object) => T | Object, 7 | } -------------------------------------------------------------------------------- /packages/core/simple-state/src/utils.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import { PROXY_SYM } from "./consts"; 3 | import { isPlainObject, isProduction, hasWindow } from "@rpldy/shared"; 4 | 5 | const isProxy = (obj: Object): boolean => 6 | !isProduction() && !!obj && 7 | !!~Object.getOwnPropertySymbols(obj).indexOf(PROXY_SYM); 8 | 9 | //check if object is File or react-native file object (it wont by instanceof File in react-native) 10 | const isNativeFile = (obj: any) => (hasWindow() && obj instanceof File) || (obj.name && obj.size && obj.uri); 11 | 12 | const isProxiable = (obj: any): boolean => 13 | Array.isArray(obj) || (isPlainObject(obj) && !isNativeFile(obj)); 14 | 15 | export { 16 | isProxy, 17 | isProxiable, 18 | }; 19 | -------------------------------------------------------------------------------- /packages/core/simple-state/types/index.d.ts: -------------------------------------------------------------------------------- 1 | 2 | export interface SimpleState { 3 | state: T; 4 | update: (updater: (state: T) => void) => T; 5 | unwrap: (proxy?: Record) => T | Record; 6 | } 7 | 8 | declare const createState: (obj: Record) => SimpleState; 9 | 10 | export const unwrap: (proxy: unknown) => unknown; 11 | 12 | export const isProxy: (proxy: unknown) => boolean; 13 | 14 | export const isProxiable: (obj: unknown) => boolean; 15 | 16 | export default createState; 17 | -------------------------------------------------------------------------------- /packages/core/simple-state/types/index.test-d.ts: -------------------------------------------------------------------------------- 1 | import createState from "./index"; 2 | 3 | interface State { 4 | foo: string, 5 | bob: string, 6 | } 7 | 8 | const testCreateState = (): void => { 9 | 10 | const { state, update, unwrap } = createState({ 11 | foo: "bar", 12 | bob: "mcintyre", 13 | }); 14 | 15 | console.log(state.foo); 16 | 17 | update((state)=> { 18 | state.bob = "alice"; 19 | }); 20 | 21 | const org = unwrap(); 22 | 23 | console.log(org); 24 | }; 25 | 26 | export { 27 | testCreateState, 28 | }; -------------------------------------------------------------------------------- /packages/core/tus-sender/.npmignore: -------------------------------------------------------------------------------- 1 | src/ 2 | *.story.js 3 | *.stories.js 4 | *.test.js 5 | tests/ 6 | mocks/ 7 | lerna.json 8 | *.log 9 | jest* 10 | .flowconfig 11 | .editorconfig 12 | .eslintignore 13 | .eslintrc.js 14 | .circleci/ 15 | .sb-static/ 16 | .env 17 | .jest-cache/ 18 | .jest-coverage/ 19 | coverage/ 20 | .github/ 21 | 22 | -------------------------------------------------------------------------------- /packages/core/tus-sender/TusSender.storydoc.mdx: -------------------------------------------------------------------------------- 1 | import { Markdown } from "@storybook/blocks"; 2 | import readme from "./README.md?raw"; 3 | 4 | {readme} 5 | -------------------------------------------------------------------------------- /packages/core/tus-sender/src/consts.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import { devFreeze } from "@rpldy/shared"; 3 | 4 | export const TUS_EXT: symbol = Symbol.for("__upldy-tus__"); 5 | 6 | export const TUS_SENDER_TYPE = "rpldy-tus-sender"; 7 | 8 | export const SUCCESS_CODES = [200, 201, 204]; 9 | 10 | export const KNOWN_EXTENSIONS = { 11 | CREATION: "creation", 12 | CREATION_WITH_UPLOAD: "creation-with-upload", 13 | TERMINATION: "termination", 14 | CONCATENATION: "concatenation", 15 | CREATION_DEFER_LENGTH: "creation-defer-length", 16 | }; 17 | 18 | export const FD_STORAGE_PREFIX = "rpldy_tus_fd_"; 19 | 20 | export const TUS_EVENTS: Object = devFreeze({ 21 | RESUME_START: "RESUME_START" 22 | }); 23 | -------------------------------------------------------------------------------- /packages/core/tus-sender/src/defaults.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import { devFreeze } from "@rpldy/shared"; 3 | import { CHUNKED_DEFAULT_OPTIONS } from "@rpldy/chunked-sender"; 4 | 5 | export const DEFAULT_OPTIONS: Object = devFreeze({ 6 | ...CHUNKED_DEFAULT_OPTIONS, 7 | featureDetection: false, 8 | featureDetectionUrl: null, 9 | version: "1.0.0", 10 | resume: true, 11 | overrideMethod: false, 12 | deferLength: false, 13 | sendDataOnCreate: false, 14 | storagePrefix: "__rpldy-tus__", 15 | lockedRetryDelay: 2000, 16 | forgetOnSuccess: false, 17 | ignoreModifiedDateInStorage: false, 18 | }); 19 | -------------------------------------------------------------------------------- /packages/core/tus-sender/src/getTusEnhancer.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import createTusSender from "./tusSender"; 3 | import { TUS_EXT } from "./consts"; 4 | 5 | import type { UploaderType, UploaderEnhancer, UploaderCreateOptions } from "@rpldy/uploader"; 6 | import type { TriggerMethod } from "@rpldy/life-events"; 7 | import type { TusOptions } from "./types"; 8 | 9 | const getTusEnhancer = (options?: TusOptions): UploaderEnhancer => { 10 | //return uploader enhancer 11 | return (uploader: UploaderType, trigger: TriggerMethod): UploaderType => { 12 | const sender = createTusSender(uploader, options, trigger); 13 | uploader.update({ send: sender.send }); 14 | 15 | uploader.registerExtension(TUS_EXT, { 16 | getOptions: sender.getOptions, 17 | }); 18 | 19 | return uploader; 20 | }; 21 | }; 22 | 23 | export default getTusEnhancer; 24 | -------------------------------------------------------------------------------- /packages/core/tus-sender/src/index.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import getTusEnhancer from "./getTusEnhancer"; 3 | 4 | export { 5 | CHUNKING_SUPPORT 6 | } from "@rpldy/chunked-sender"; 7 | 8 | export { 9 | getTusEnhancer, 10 | }; 11 | 12 | export { 13 | clearResumables 14 | } from "./resumableStore"; 15 | 16 | export { 17 | TUS_SENDER_TYPE, 18 | TUS_EXT, 19 | TUS_EVENTS, 20 | } from "./consts"; 21 | 22 | export default getTusEnhancer; 23 | 24 | export type { 25 | TusOptions, 26 | ResumeStartEventData, 27 | ResumeStartEventResponse, 28 | } from "./types"; 29 | -------------------------------------------------------------------------------- /packages/core/tus-sender/src/tests/getTusEnhancer.test.js: -------------------------------------------------------------------------------- 1 | import createTusSender from "../tusSender"; 2 | import getTusEnhancer from "../getTusEnhancer"; 3 | import { TUS_EXT } from "../consts"; 4 | 5 | vi.mock("../tusSender"); 6 | 7 | describe("getTusEnhancer tests", () => { 8 | 9 | it("should enhance uploader", () => { 10 | const options = { parallel: 2 }; 11 | 12 | const send = vi.fn(), 13 | getOptions = vi.fn(); 14 | 15 | createTusSender.mockReturnValueOnce({ 16 | send, 17 | getOptions 18 | }); 19 | 20 | const enhancer = getTusEnhancer(options); 21 | 22 | const uploader = { 23 | update: vi.fn(), 24 | registerExtension: vi.fn(), 25 | }; 26 | 27 | enhancer(uploader, "trigger"); 28 | 29 | expect(uploader.update).toHaveBeenCalledWith({ send }); 30 | expect(createTusSender).toHaveBeenCalledWith(uploader, options, "trigger"); 31 | 32 | expect(uploader.registerExtension).toHaveBeenCalledWith(TUS_EXT, expect.any(Object)); 33 | 34 | uploader.registerExtension.mock.calls[0][1].getOptions(); 35 | expect(getOptions).toHaveBeenCalled(); 36 | }); 37 | 38 | }); 39 | -------------------------------------------------------------------------------- /packages/core/tus-sender/src/tests/tusState.mock.js: -------------------------------------------------------------------------------- 1 | 2 | export default (initState = {}) => { 3 | let state = { 4 | options: { 5 | version: "1", 6 | ...initState?.options, 7 | }, 8 | featureDetection: {}, 9 | ...initState 10 | }; 11 | 12 | return { 13 | getState: vi.fn(() => state), 14 | updateState: vi.fn((updater) => updater(state)), 15 | } 16 | }; 17 | -------------------------------------------------------------------------------- /packages/core/tus-sender/src/tusSender/index.js: -------------------------------------------------------------------------------- 1 | import createTusSender from "./createTusSender"; 2 | export default createTusSender; 3 | -------------------------------------------------------------------------------- /packages/core/tus-sender/src/tusSender/initTusUpload/createStateItemData.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import type { BatchItem } from "@rpldy/shared"; 3 | import type { State, TusOptions, TusState } from "../../types"; 4 | 5 | const createStateItemData = ( 6 | item: BatchItem, 7 | tusState: TusState, 8 | options: TusOptions, 9 | parallelIdentifier: ?string, 10 | orgItemId: ?string 11 | ) => { 12 | tusState.updateState((state: State) => { 13 | state.items[item.id] = { 14 | id: item.id, 15 | uploadUrl: null, 16 | size: item.file.size, 17 | offset: 0, 18 | 19 | //will be populated only for items that represent a parallel part: 20 | parallelIdentifier, 21 | orgItemId, 22 | }; 23 | }); 24 | }; 25 | 26 | export default createStateItemData; 27 | -------------------------------------------------------------------------------- /packages/core/tus-sender/src/tusSender/initTusUpload/index.js: -------------------------------------------------------------------------------- 1 | export { default as initTusUpload } from "./initTusUpload"; 2 | export { default as initParallelTusUpload } from "./initParallelTusUpload"; 3 | -------------------------------------------------------------------------------- /packages/core/tus-sender/src/tusSender/tests/hadnleEvents-no-chunking.test.js: -------------------------------------------------------------------------------- 1 | import handleEvents from "../handleEvents"; 2 | 3 | vi.mock("@rpldy/chunked-sender", async () => { 4 | const org = await vi.importActual("@rpldy/chunked-sender"); 5 | 6 | return { 7 | ...org, 8 | CHUNKING_SUPPORT: false, 9 | }; 10 | }); 11 | 12 | describe("handleEvents without chunking support tests", () => { 13 | const uploader = { 14 | on: vi.fn(), 15 | }; 16 | 17 | const chunkedSender = { 18 | on: vi.fn(), 19 | }; 20 | 21 | it("should do nothing with no chunk support", () => { 22 | handleEvents(uploader, null, chunkedSender); 23 | expect(uploader.on).not.toHaveBeenCalled(); 24 | expect(chunkedSender.on).not.toHaveBeenCalled(); 25 | }); 26 | }); 27 | -------------------------------------------------------------------------------- /packages/core/tus-sender/src/tusSender/tests/tusSend-no-chunking.test.js: -------------------------------------------------------------------------------- 1 | import xhrSend from "@rpldy/sender"; 2 | import getTusSend from "../tusSend"; 3 | 4 | vi.mock("@rpldy/chunked-sender", async () => { 5 | const org = await vi.importActual("@rpldy/chunked-sender"); 6 | return { 7 | ...org, 8 | CHUNKING_SUPPORT: false, 9 | }; 10 | }); 11 | 12 | 13 | describe("tusSend - no chunking support tests", () => { 14 | const chunkedSender = { send: vi.fn() }; 15 | 16 | it("should use xhrSender for no chunk support", () => { 17 | const send = getTusSend(chunkedSender, null); 18 | expect(send).toBe(xhrSend); 19 | }); 20 | }); 21 | -------------------------------------------------------------------------------- /packages/core/tus-sender/src/utils.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import { merge } from "@rpldy/shared"; 4 | import { DEFAULT_OPTIONS } from "./defaults"; 5 | import type { TusOptions } from "./types"; 6 | 7 | const getMandatoryOptions = (options: ?TusOptions): TusOptions => 8 | merge({}, DEFAULT_OPTIONS, options); 9 | 10 | export { 11 | getMandatoryOptions, 12 | }; 13 | -------------------------------------------------------------------------------- /packages/core/tus-sender/types/index.test-d.ts: -------------------------------------------------------------------------------- 1 | import getTusEnhancer from "./index"; 2 | import createUploader from "@rpldy/uploader"; 3 | 4 | const testGetTusEnhancer = (): void => { 5 | const enhancer = getTusEnhancer({ 6 | deferLength: true, 7 | parallel: 2, 8 | forgetOnSuccess: true, 9 | }); 10 | 11 | const uploader = createUploader({ 12 | enhancer, 13 | }); 14 | 15 | console.log(uploader.getOptions()); 16 | }; 17 | 18 | export { 19 | testGetTusEnhancer, 20 | }; -------------------------------------------------------------------------------- /packages/core/uploader/.npmignore: -------------------------------------------------------------------------------- 1 | src/ 2 | *.story.js 3 | *.stories.js 4 | *.test.js 5 | tests/ 6 | mocks/ 7 | lerna.json 8 | *.log 9 | jest* 10 | .flowconfig 11 | .editorconfig 12 | .eslintignore 13 | .eslintrc.js 14 | .circleci/ 15 | .sb-static/ 16 | .env 17 | .jest-cache/ 18 | .jest-coverage/ 19 | coverage/ 20 | .github/ 21 | 22 | -------------------------------------------------------------------------------- /packages/core/uploader/Uploader.storydoc.mdx: -------------------------------------------------------------------------------- 1 | import { Markdown } from "@storybook/blocks"; 2 | import readme from "./README.md?raw"; 3 | 4 | {readme} 5 | -------------------------------------------------------------------------------- /packages/core/uploader/src/composeEnhancers.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import type { UploaderType, UploaderEnhancer } from "@rpldy/raw-uploader"; 3 | 4 | const composeEnhancers = (...enhancers: UploaderEnhancer[]): ((uploader: UploaderType, ...args: Array) => UploaderType) => 5 | (uploader: UploaderType, ...args: any[]) => 6 | enhancers.reduce((enhanced: UploaderType, e: UploaderEnhancer) => 7 | e(enhanced, ...args) || enhanced, uploader); 8 | 9 | export default composeEnhancers; 10 | -------------------------------------------------------------------------------- /packages/core/uploader/src/consts.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import { devFreeze, FILE_STATES } from "@rpldy/shared"; 3 | 4 | export const UPLOADER_EVENTS: Object = devFreeze({ 5 | BATCH_ADD: "BATCH-ADD", 6 | BATCH_START: "BATCH-START", 7 | BATCH_PROGRESS: "BATCH_PROGRESS", 8 | BATCH_FINISH: "BATCH-FINISH", 9 | BATCH_ABORT: "BATCH-ABORT", 10 | BATCH_CANCEL: "BATCH-CANCEL", 11 | BATCH_ERROR: "BATCH-ERROR", 12 | BATCH_FINALIZE: "BATCH-FINALIZE", 13 | 14 | ITEM_START: "FILE-START", 15 | ITEM_CANCEL: "FILE-CANCEL", 16 | ITEM_PROGRESS: "FILE-PROGRESS", 17 | ITEM_FINISH: "FILE-FINISH", 18 | ITEM_ABORT: "FILE-ABORT", 19 | ITEM_ERROR: "FILE-ERROR", 20 | ITEM_FINALIZE: "FILE-FINALIZE", 21 | 22 | REQUEST_PRE_SEND: "REQUEST_PRE_SEND", 23 | 24 | ALL_ABORT: "ALL_ABORT", 25 | }); 26 | 27 | export const PROGRESS_DELAY = 50; 28 | 29 | export const SENDER_EVENTS: Object = devFreeze({ 30 | ITEM_PROGRESS: "ITEM_PROGRESS", 31 | BATCH_PROGRESS: "BATCH_PROGRESS", 32 | }); 33 | 34 | export const ITEM_FINALIZE_STATES = [ 35 | FILE_STATES.FINISHED, 36 | FILE_STATES.ERROR, 37 | FILE_STATES.CANCELLED, 38 | FILE_STATES.ABORTED 39 | ]; 40 | -------------------------------------------------------------------------------- /packages/core/uploader/src/defaults.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import { devFreeze } from "@rpldy/shared"; 3 | 4 | export const DEFAULT_PARAM_NAME = "file"; 5 | 6 | export const DEFAULT_FILTER = (input: any, i: number, arr: any[]): Promise | boolean => true; 7 | 8 | export const DEFAULT_OPTIONS: Object = devFreeze({ 9 | autoUpload: true, 10 | clearPendingOnAdd: false, 11 | inputFieldName: "file", 12 | concurrent: false, 13 | maxConcurrent: 2, 14 | grouped: false, 15 | maxGroupSize: 5, 16 | method: "POST", 17 | params: {}, 18 | fileFilter: DEFAULT_FILTER, 19 | forceJsonResponse: false, 20 | withCredentials: false, 21 | destination: {}, 22 | send: null, 23 | sendWithFormData: true, 24 | formDataAllowUndefined: false, 25 | fastAbortThreshold: 100, 26 | }); 27 | -------------------------------------------------------------------------------- /packages/core/uploader/src/index.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import createUploader from "./uploader"; 3 | import composeEnhancers from "./composeEnhancers"; 4 | import { UPLOADER_EVENTS } from "./consts"; 5 | import { DEFAULT_OPTIONS } from "./defaults"; 6 | 7 | export default createUploader; 8 | 9 | export { 10 | UPLOADER_EVENTS, 11 | DEFAULT_OPTIONS, 12 | 13 | composeEnhancers, 14 | createUploader, 15 | }; 16 | 17 | export * from "@rpldy/sender"; 18 | 19 | export { 20 | FILE_STATES, 21 | BATCH_STATES, 22 | } from "@rpldy/shared"; 23 | 24 | export type { 25 | TriggerMethod 26 | } from "@rpldy/life-events"; 27 | 28 | export type { 29 | UploaderType, 30 | UploaderEnhancer, 31 | RawCreateOptions, 32 | } from "@rpldy/raw-uploader"; 33 | 34 | export type { 35 | UploaderCreateOptions, 36 | UploadyUploaderType 37 | } from "./types"; 38 | -------------------------------------------------------------------------------- /packages/core/uploader/src/queue/index.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | export { default } from "./uploaderQueue"; 3 | -------------------------------------------------------------------------------- /packages/core/uploader/src/tests/mocks/rpldy-uploader.mock.js: -------------------------------------------------------------------------------- 1 | import { UPLOADER_EVENTS } from "../../consts"; 2 | import { DEFAULT_OPTIONS } from "../../defaults"; 3 | 4 | const uploader = { 5 | add: vi.fn(), 6 | upload: vi.fn(), 7 | clearPending: vi.fn(), 8 | getOptions: vi.fn(), 9 | on: vi.fn(), 10 | once: vi.fn(), 11 | off: vi.fn(), 12 | update: vi.fn(), 13 | abort: vi.fn(), 14 | abortBatch: vi.fn(), 15 | registerExtension: vi.fn(), 16 | getExtension: vi.fn(), 17 | }; 18 | 19 | const createUploader = vi.fn(() => uploader); 20 | 21 | // createUploader.UPLOADER_EVENTS = UPLOADER_EVENTS; 22 | // createUploader.DEFAULT_OPTIONS = DEFAULT_OPTIONS; 23 | 24 | vi.doMock("@rpldy/uploader", () => ({ 25 | default: createUploader, 26 | UPLOADER_EVENTS, 27 | DEFAULT_OPTIONS, 28 | })); 29 | 30 | export { 31 | uploader, 32 | createUploader, 33 | UPLOADER_EVENTS, 34 | DEFAULT_OPTIONS, 35 | } 36 | -------------------------------------------------------------------------------- /packages/native/native-uploady/.npmignore: -------------------------------------------------------------------------------- 1 | src/ 2 | *.story.js 3 | *.stories.js 4 | *.test.js 5 | tests/ 6 | mocks/ 7 | lerna.json 8 | *.log 9 | jest* 10 | .flowconfig 11 | .editorconfig 12 | .eslintignore 13 | .eslintrc.js 14 | .circleci/ 15 | .sb-static/ 16 | .env 17 | .jest-cache/ 18 | .jest-coverage/ 19 | coverage/ 20 | .github/ 21 | 22 | -------------------------------------------------------------------------------- /packages/native/native-uploady/NativeUploady.storydoc.mdx: -------------------------------------------------------------------------------- 1 | import { Markdown } from "@storybook/blocks"; 2 | import readme from "./README.md?raw"; 3 | 4 | {readme} 5 | -------------------------------------------------------------------------------- /packages/native/native-uploady/src/NativeUploady.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import React from "react"; 3 | import { NoDomUploady } from "@rpldy/shared-ui"; 4 | 5 | import type { Node } from "react"; 6 | import type { NativeUploadyProps } from "./types"; 7 | 8 | const NativeUploady = (props: NativeUploadyProps): Node => ; 9 | 10 | export default NativeUploady; 11 | -------------------------------------------------------------------------------- /packages/native/native-uploady/src/index.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import NativeUploady from "./NativeUploady"; 3 | 4 | export { NativeUploady }; 5 | export default NativeUploady; 6 | 7 | export { 8 | UploadyContext, 9 | NoDomUploady, 10 | assertContext, 11 | useUploadOptions, 12 | 13 | useBatchAddListener, 14 | useBatchStartListener, 15 | useBatchProgressListener, 16 | useBatchFinishListener, 17 | useBatchCancelledListener, 18 | useBatchAbortListener, 19 | 20 | useItemStartListener, 21 | useItemFinishListener, 22 | useItemProgressListener, 23 | useItemCancelListener, 24 | useItemErrorListener, 25 | useItemAbortListener, 26 | useItemFinalizeListener, 27 | 28 | useRequestPreSend, 29 | 30 | useAbortAll, 31 | useAbortBatch, 32 | useAbortItem, 33 | } from "@rpldy/shared-ui"; 34 | 35 | export type * from "./types"; 36 | -------------------------------------------------------------------------------- /packages/native/native-uploady/src/tests/NativeUploady.test.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { NoDomUploady } from "@rpldy/shared-ui"; 3 | import NativeUploady from "../index"; 4 | 5 | vi.mock("@rpldy/shared-ui", () => ({ 6 | NoDomUploady: vi.fn(() =>
), 7 | })); 8 | 9 | describe("NativeUploady tests", () => { 10 | it("should render NativeUploady", () => { 11 | render(); 12 | 13 | expect(NoDomUploady).toHaveBeenCalledWith({ debug: true, autoUpload: true }, {}); 14 | }); 15 | }); 16 | -------------------------------------------------------------------------------- /packages/native/native-uploady/src/types.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import type { NoDomUploadyProps } from "@rpldy/shared-ui"; 3 | 4 | export type NativeUploadyProps = {| 5 | ...NoDomUploadyProps, 6 | |} 7 | -------------------------------------------------------------------------------- /packages/native/native-uploady/types/index.d.ts: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { NoDomUploadyProps } from "@rpldy/shared-ui"; 3 | 4 | export * from "@rpldy/shared-ui"; 5 | 6 | export const NativeUploady: React.ComponentType; 7 | 8 | export default NativeUploady; 9 | -------------------------------------------------------------------------------- /packages/native/native-uploady/types/index.test-d.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import NativeUploady from "./"; 3 | import { useUploadOptions } from "@rpldy/shared-ui"; 4 | 5 | const ListOfUploadOptions = () => { 6 | const options = useUploadOptions(); 7 | 8 | return
    9 | {JSON.stringify(options)} 10 |
; 11 | }; 12 | 13 | const testNativeUploady = (): JSX.Element => { 14 | return 15 | 16 | ; 17 | }; 18 | 19 | export { 20 | testNativeUploady, 21 | }; 22 | -------------------------------------------------------------------------------- /packages/ui/chunked-uploady/.npmignore: -------------------------------------------------------------------------------- 1 | src/ 2 | *.story.js 3 | *.stories.js 4 | *.test.js 5 | tests/ 6 | mocks/ 7 | lerna.json 8 | *.log 9 | jest* 10 | .flowconfig 11 | .editorconfig 12 | .eslintignore 13 | .eslintrc.js 14 | .circleci/ 15 | .sb-static/ 16 | .env 17 | .jest-cache/ 18 | .jest-coverage/ 19 | coverage/ 20 | .github/ 21 | 22 | -------------------------------------------------------------------------------- /packages/ui/chunked-uploady/ChunkedUploady.storydoc.mdx: -------------------------------------------------------------------------------- 1 | import { Markdown } from "@storybook/blocks"; 2 | import readme from "./README.md?raw"; 3 | 4 | {readme} 5 | -------------------------------------------------------------------------------- /packages/ui/chunked-uploady/src/chunkEventListenerHooks.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import { generateUploaderEventHook } from "@rpldy/shared-ui"; 3 | import { CHUNK_EVENTS } from "@rpldy/chunked-sender"; 4 | 5 | import type { ChunkStartListenerHook, ChunkFinishListenerHook } from "./types"; 6 | 7 | const useChunkStartListener: ChunkStartListenerHook = generateUploaderEventHook(CHUNK_EVENTS.CHUNK_START, false); 8 | 9 | const useChunkFinishListener: ChunkFinishListenerHook = generateUploaderEventHook(CHUNK_EVENTS.CHUNK_FINISH, false); 10 | 11 | export { 12 | useChunkStartListener, 13 | useChunkFinishListener, 14 | }; 15 | -------------------------------------------------------------------------------- /packages/ui/chunked-uploady/src/index.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import ChunkedUploady from "./ChunkedUploady"; 3 | 4 | export default ChunkedUploady; 5 | 6 | export { 7 | ChunkedUploady 8 | }; 9 | 10 | export { CHUNK_EVENTS } from "@rpldy/chunked-sender"; 11 | 12 | export * from "@rpldy/uploady"; 13 | 14 | export * from "./chunkEventListenerHooks"; 15 | 16 | export type { 17 | ChunkedUploadyProps, 18 | } from "./types"; 19 | -------------------------------------------------------------------------------- /packages/ui/chunked-uploady/src/tests/ChunkedUploady-no-hunking.test.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { logWarning } from "@rpldy/shared-ui/src/tests/mocks/rpldy-ui-shared.mock"; 3 | import "@rpldy/uploady/src/tests/mocks/rpldy-uploady.mock"; 4 | import getChunkedEnhancer from "@rpldy/chunked-sender"; 5 | import ChunkedUploady from "../ChunkedUploady"; 6 | 7 | vi.mock("@rpldy/chunked-sender", () => ({ 8 | default: vi.fn(), 9 | CHUNKING_SUPPORT: false 10 | })); 11 | 12 | 13 | describe("ChunkedUploady tests without chunking support", () => { 14 | const chunkedEnhancer = (uploader) => uploader; 15 | 16 | beforeAll(() => { 17 | getChunkedEnhancer.mockImplementation(() => chunkedEnhancer); 18 | }); 19 | 20 | it("should render Uploady when no chunk support", () => { 21 | render(); 22 | 23 | expect(logWarning).toHaveBeenCalledWith(false, expect.any(String)); 24 | expect(getChunkedEnhancer).not.toHaveBeenCalled(); 25 | }); 26 | }); 27 | -------------------------------------------------------------------------------- /packages/ui/chunked-uploady/src/tests/chunkEventListenerHooks.test.js: -------------------------------------------------------------------------------- 1 | import { CHUNK_EVENTS } from "@rpldy/chunked-sender"; 2 | import { generateUploaderEventHook } from "@rpldy/shared-ui"; 3 | import "../chunkEventListenerHooks"; 4 | 5 | vi.mock("@rpldy/shared-ui"); 6 | 7 | describe("eventListenerHooks tests", () => { 8 | describe("generateUploaderEventHook tests", () => { 9 | it.each([ 10 | CHUNK_EVENTS.CHUNK_START, 11 | CHUNK_EVENTS.CHUNK_FINISH, 12 | ])("should generate chunk event hooks for: %s", (event) => { 13 | expect(generateUploaderEventHook).toHaveBeenCalledWith(event, false); 14 | }); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /packages/ui/chunked-uploady/src/types.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import type { UploadyProps } from "@rpldy/uploady"; 3 | import type { ChunkedOptions, ChunkFinishEventData, ChunkStartEventData } from "@rpldy/chunked-sender"; 4 | import type { SendOptions } from "@rpldy/sender"; 5 | 6 | export type StartEventResponse = void | boolean | { 7 | url?: string, 8 | sendOptions?: SendOptions 9 | }; 10 | 11 | export type ChunkStartListenerHook = (cb: (data: ChunkStartEventData) => StartEventResponse | Promise) => void; 12 | 13 | export type ChunkFinishListenerHook = (cb: (data: ChunkFinishEventData) => void | Promise) => void; 14 | 15 | export type ChunkedUploadyProps = {| 16 | ...UploadyProps, 17 | ...$Exact, 18 | |}; 19 | -------------------------------------------------------------------------------- /packages/ui/chunked-uploady/types/index.d.ts: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { UploadyProps } from "@rpldy/uploady"; 3 | import { ChunkedOptions, ChunkFinishEventData, ChunkStartEventData } from "@rpldy/chunked-sender"; 4 | import { SendOptions } from "@rpldy/sender"; 5 | 6 | export * from "@rpldy/uploady"; 7 | 8 | export interface ChunkedUploadyProps extends UploadyProps, ChunkedOptions {} 9 | 10 | export const ChunkedUploady: React.ComponentType; 11 | 12 | export type StartEventResponse = void | boolean | { 13 | url?: string, 14 | sendOptions?: SendOptions 15 | }; 16 | 17 | export const useChunkStartListener: (cb: (data: ChunkStartEventData ) => StartEventResponse | Promise) => void; 18 | 19 | export const useChunkFinishListener: (cb: (data: ChunkFinishEventData) => void | Promise) => void; 20 | 21 | export default ChunkedUploady; 22 | -------------------------------------------------------------------------------- /packages/ui/retry-hooks/.npmignore: -------------------------------------------------------------------------------- 1 | src/ 2 | *.story.js 3 | *.stories.js 4 | *.test.js 5 | tests/ 6 | mocks/ 7 | lerna.json 8 | *.log 9 | jest* 10 | .flowconfig 11 | .editorconfig 12 | .eslintignore 13 | .eslintrc.js 14 | .circleci/ 15 | .sb-static/ 16 | .env 17 | .jest-cache/ 18 | .jest-coverage/ 19 | coverage/ 20 | .github/ 21 | 22 | -------------------------------------------------------------------------------- /packages/ui/retry-hooks/RetryHooks.storydoc.mdx: -------------------------------------------------------------------------------- 1 | import { Markdown } from "@storybook/blocks"; 2 | import readme from "./README.md?raw"; 3 | 4 | {readme} 5 | -------------------------------------------------------------------------------- /packages/ui/retry-hooks/src/consts.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | export const NO_EXT = "Uploady - retry was not registered. Make sure you used the enhancer"; 4 | -------------------------------------------------------------------------------- /packages/ui/retry-hooks/src/index.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import retryEnhancer, { RETRY_EVENT } from "@rpldy/retry"; 3 | import useRetry from "./useRetry"; 4 | import useBatchRetry from "./useBatchRetry"; 5 | import useRetryListener from "./useRetryListener"; 6 | 7 | export default retryEnhancer; 8 | 9 | export { 10 | RETRY_EVENT, 11 | 12 | retryEnhancer, 13 | useRetry, 14 | useBatchRetry, 15 | useRetryListener, 16 | }; 17 | 18 | export type { 19 | RetryEventData, 20 | RetryEventCallback, 21 | RetryListenerHook, 22 | } from "./types"; 23 | -------------------------------------------------------------------------------- /packages/ui/retry-hooks/src/tests/useBatchRetry.test.js: -------------------------------------------------------------------------------- 1 | import { invariant } from "@rpldy/shared/src/tests/mocks/rpldy-shared.mock"; 2 | import { UploadyContext } from "@rpldy/shared-ui/src/tests/mocks/rpldy-ui-shared.mock"; 3 | import { NO_EXT } from "../consts"; 4 | import useBatchRetry from "../useBatchRetry"; 5 | 6 | describe("useBatchRetry tests", () => { 7 | beforeEach(() => { 8 | invariant.mockReset(); 9 | }); 10 | 11 | it("should throw if ext not registered", async() => { 12 | invariant.mockImplementation(() => { 13 | throw new Error("bah"); 14 | }); 15 | 16 | expect(() => { 17 | renderHookWithError(useBatchRetry); 18 | }).toThrow("bah"); 19 | 20 | expect(invariant).toHaveBeenCalledWith(undefined, NO_EXT); 21 | }); 22 | 23 | it("should return batchRetry from context", () => { 24 | UploadyContext.getExtension.mockReturnValueOnce({ 25 | retryBatch: "test" 26 | }); 27 | 28 | const { result } = renderHook(useBatchRetry); 29 | 30 | expect(result.current).toBe("test"); 31 | }); 32 | }); 33 | -------------------------------------------------------------------------------- /packages/ui/retry-hooks/src/tests/useRetry.test.js: -------------------------------------------------------------------------------- 1 | import { invariant } from "@rpldy/shared/src/tests/mocks/rpldy-shared.mock"; 2 | import { UploadyContext } from "@rpldy/shared-ui/src/tests/mocks/rpldy-ui-shared.mock"; 3 | import { NO_EXT } from "../consts"; 4 | import useRetry from "../useRetry"; 5 | 6 | describe("useRetry tests", () => { 7 | beforeEach(() => { 8 | invariant.mockReset(); 9 | }); 10 | 11 | it("should throw if ext not registered", () => { 12 | invariant.mockImplementation(() => { 13 | throw new Error("bah"); 14 | }); 15 | 16 | expect(() => { 17 | renderHookWithError(useRetry); 18 | }).toThrow("bah"); 19 | 20 | expect(invariant).toHaveBeenCalledWith(undefined, NO_EXT); 21 | }); 22 | 23 | it("should return retry from context", () => { 24 | UploadyContext.getExtension.mockReturnValueOnce({ 25 | retry: "test" 26 | }); 27 | 28 | const { result } = renderHook(useRetry); 29 | 30 | expect(result.current).toBe("test"); 31 | }); 32 | }); 33 | -------------------------------------------------------------------------------- /packages/ui/retry-hooks/src/tests/useRetryListener.test.js: -------------------------------------------------------------------------------- 1 | import { generateUploaderEventHook } from "@rpldy/shared-ui"; 2 | import { RETRY_EVENT } from "@rpldy/retry"; 3 | import "../useRetryListener"; 4 | 5 | vi.mock("@rpldy/shared-ui", () => ({ 6 | generateUploaderEventHook: vi.fn(), 7 | })); 8 | 9 | describe("useRetryListener hook test", () => { 10 | it("should generate retry even listener hook", () => { 11 | expect(generateUploaderEventHook).toHaveBeenCalledWith(RETRY_EVENT, false); 12 | }); 13 | }); 14 | -------------------------------------------------------------------------------- /packages/ui/retry-hooks/src/types.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import type { BatchItem, UploadOptions } from "@rpldy/shared"; 3 | 4 | export type RetryEventData = { 5 | items: BatchItem[]; 6 | options: UploadOptions | void; 7 | }; 8 | 9 | export type RetryEventCallback = (data: RetryEventData) => void; 10 | 11 | export type RetryListenerHook = (cb: RetryEventCallback) => void; 12 | -------------------------------------------------------------------------------- /packages/ui/retry-hooks/src/useBatchRetry.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import { invariant } from "@rpldy/shared"; 3 | import { useUploadyContext } from "@rpldy/shared-ui"; 4 | import { RETRY_EXT, type BatchRetryMethod } from "@rpldy/retry"; 5 | import { NO_EXT } from "./consts"; 6 | 7 | const useBatchRetry = (): BatchRetryMethod => { 8 | const context = useUploadyContext(); 9 | const ext = context.getExtension(RETRY_EXT); 10 | 11 | invariant(ext, NO_EXT); 12 | 13 | return ext.retryBatch; 14 | }; 15 | 16 | export default useBatchRetry; 17 | -------------------------------------------------------------------------------- /packages/ui/retry-hooks/src/useRetry.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import { invariant } from "@rpldy/shared"; 3 | import { useUploadyContext } from "@rpldy/shared-ui"; 4 | import { RETRY_EXT, type RetryMethod } from "@rpldy/retry"; 5 | import { NO_EXT } from "./consts"; 6 | 7 | const useRetry = (): RetryMethod => { 8 | const context = useUploadyContext(); 9 | const ext = context.getExtension(RETRY_EXT); 10 | 11 | invariant(ext, NO_EXT); 12 | 13 | return ext.retry; 14 | }; 15 | 16 | export default useRetry; 17 | -------------------------------------------------------------------------------- /packages/ui/retry-hooks/src/useRetryListener.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import { generateUploaderEventHook } from "@rpldy/shared-ui"; 3 | import { RETRY_EVENT } from "@rpldy/retry"; 4 | 5 | import type { RetryListenerHook } from "./types"; 6 | 7 | const useRetryListener = (generateUploaderEventHook(RETRY_EVENT, false): RetryListenerHook); 8 | 9 | export default useRetryListener; 10 | -------------------------------------------------------------------------------- /packages/ui/retry-hooks/types/index.d.ts: -------------------------------------------------------------------------------- 1 | import { BatchItem, UploadOptions } from "@rpldy/shared"; 2 | import retryEnhancer, { 3 | RETRY_EVENT, 4 | RetryMethod, 5 | RetryBatchMethod, 6 | } from "@rpldy/retry"; 7 | 8 | export default retryEnhancer; 9 | 10 | export { 11 | RETRY_EVENT, 12 | RetryMethod, 13 | RetryBatchMethod, 14 | retryEnhancer, 15 | }; 16 | 17 | export const useRetry: () => RetryMethod; 18 | 19 | export const useBatchRetry: () => RetryBatchMethod; 20 | 21 | export type RetryEventData = { 22 | items: BatchItem[]; 23 | options: UploadOptions | void; 24 | }; 25 | 26 | export type RetryEventCallback = (data: RetryEventData) => void; 27 | 28 | export const useRetryListener: (cb: RetryEventCallback) => void; 29 | -------------------------------------------------------------------------------- /packages/ui/retry-hooks/types/index.test-d.tsx: -------------------------------------------------------------------------------- 1 | import { BatchItem } from "@rpldy/shared"; 2 | import * as React from "react"; 3 | import { 4 | useRetry, 5 | useBatchRetry, 6 | useRetryListener, 7 | RetryBatchMethod, 8 | RetryMethod 9 | } from "./index"; 10 | 11 | const TestRetryHooks: React.FC = () => { 12 | const retry: RetryMethod = useRetry(); 13 | const retryBatch: RetryBatchMethod = useBatchRetry(); 14 | 15 | useRetryListener(({ items, options }) => { 16 | console.log(`retrying ${items.length} items`); 17 | items.forEach((item: BatchItem) => console.log("retrying item: " + item.id)); 18 | console.log(options); 19 | }); 20 | 21 | retry("item-1"); 22 | retryBatch("b1", { destination: { url: "another.url" } }); 23 | 24 | return
; 25 | }; 26 | 27 | const testRetryHooks = (): JSX.Element => { 28 | return ; 29 | }; 30 | 31 | export { 32 | testRetryHooks, 33 | }; 34 | -------------------------------------------------------------------------------- /packages/ui/shared/.npmignore: -------------------------------------------------------------------------------- 1 | src/ 2 | *.story.js 3 | *.stories.js 4 | *.test.js 5 | tests/ 6 | mocks/ 7 | lerna.json 8 | *.log 9 | jest* 10 | .flowconfig 11 | .editorconfig 12 | .eslintignore 13 | .eslintrc.js 14 | .circleci/ 15 | .sb-static/ 16 | .env 17 | .jest-cache/ 18 | .jest-coverage/ 19 | coverage/ 20 | .github/ 21 | 22 | -------------------------------------------------------------------------------- /packages/ui/shared/src/NoDomUploady.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import React, { useMemo } from "react"; 3 | import { logger } from "@rpldy/shared"; 4 | import UploadyContext, { createContextApi } from "./UploadyContext"; 5 | import useUploader from "./hooks/useUploader"; 6 | 7 | import type { Node } from "react"; 8 | import type { NoDomUploadyProps } from "./types"; 9 | 10 | const NoDomUploady = (props: NoDomUploadyProps): Node => { 11 | const { 12 | listeners, 13 | debug, 14 | children, 15 | inputRef, 16 | ...uploadOptions 17 | } = props; 18 | 19 | logger.setDebug(!!debug); 20 | logger.debugLog("@@@@@@ Uploady Rendering @@@@@@", props); 21 | 22 | const uploader = useUploader(uploadOptions, listeners); 23 | 24 | const api = useMemo(() => 25 | createContextApi(uploader, inputRef), 26 | [uploader, inputRef] 27 | ); 28 | 29 | return 30 | {children} 31 | ; 32 | }; 33 | 34 | export default NoDomUploady; 35 | -------------------------------------------------------------------------------- /packages/ui/shared/src/assertContext.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import { invariant } from "@rpldy/shared"; 3 | import { getIsVersionRegisteredAndDifferent, getRegisteredVersion } from "./uploadyVersion"; 4 | 5 | import type { UploadyContextType } from "./types"; 6 | 7 | export const ERROR_MSG = "Uploady - Valid UploadyContext not found. Make sure you render inside "; 8 | export const DIFFERENT_VERSION_ERROR_MSG = `Uploady - Valid UploadyContext not found. 9 | You may be using packages of different Uploady versions. and all other packages using the context provider must be of the same version: %s`; 10 | 11 | const assertContext = (context: ?UploadyContextType): UploadyContextType => { 12 | invariant( 13 | !getIsVersionRegisteredAndDifferent(), 14 | DIFFERENT_VERSION_ERROR_MSG, 15 | getRegisteredVersion(), 16 | ); 17 | 18 | invariant( 19 | context && context.hasUploader(), 20 | ERROR_MSG 21 | ); 22 | 23 | return context; 24 | }; 25 | 26 | export default assertContext; 27 | -------------------------------------------------------------------------------- /packages/ui/shared/src/consts.js: -------------------------------------------------------------------------------- 1 | export const UPLOAD_OPTIONS_COMP = Symbol.for("rpldy_component"); 2 | -------------------------------------------------------------------------------- /packages/ui/shared/src/hocs/withBatchStartUpdate.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import { UPLOADER_EVENTS } from "@rpldy/uploader"; 3 | import { createRequestUpdateHoc } from "./createRequestUpdateHoc"; 4 | 5 | import type { BatchItem, Batch } from "@rpldy/shared"; 6 | import type { UploaderCreateOptions } from "@rpldy/uploader"; 7 | import type { RequestUpdateHoc } from "./createRequestUpdateHoc"; 8 | 9 | type BatchStartRequestData = { batch: Batch, items: BatchItem[], options: UploaderCreateOptions }; 10 | 11 | const withBatchStartUpdate: RequestUpdateHoc = createRequestUpdateHoc({ 12 | eventType: UPLOADER_EVENTS.BATCH_START, 13 | getIsValidEventData: (id, batch: Batch) => batch.id === id, 14 | getRequestData: (batch, batchOptions) => 15 | ({ batch, items: batch.items, options: batchOptions }), 16 | }); 17 | 18 | export default withBatchStartUpdate; 19 | -------------------------------------------------------------------------------- /packages/ui/shared/src/hocs/withRequestPreSendUpdate.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import { UPLOADER_EVENTS } from "@rpldy/uploader"; 3 | import { createRequestUpdateHoc } from "./createRequestUpdateHoc"; 4 | 5 | import type { BatchItem } from "@rpldy/shared"; 6 | import type { UploaderCreateOptions } from "@rpldy/uploader"; 7 | import type { RequestUpdateHoc } from "./createRequestUpdateHoc"; 8 | 9 | type PreSendRequestData = { items: BatchItem[], options: UploaderCreateOptions }; 10 | 11 | const withRequestPreSendUpdate: RequestUpdateHoc = createRequestUpdateHoc({ 12 | eventType: UPLOADER_EVENTS.REQUEST_PRE_SEND, 13 | getIsValidEventData: (id, { items }) => !!items.find((item) => item.id === id), 14 | getRequestData: ({ items, options }) => ({ items, options }), 15 | }); 16 | 17 | export default withRequestPreSendUpdate; 18 | -------------------------------------------------------------------------------- /packages/ui/shared/src/hooks/tests/useAbortAll.test.js: -------------------------------------------------------------------------------- 1 | import useUploadyContext from "../useUploadyContext"; 2 | import useAbortAll from "../useAbortAll"; 3 | 4 | vi.mock("../useUploadyContext"); 5 | 6 | describe("useAbortItem tests", () => { 7 | const context = { 8 | abort: vi.fn() 9 | }; 10 | 11 | beforeAll(() => { 12 | useUploadyContext.mockReturnValue(context); 13 | }); 14 | 15 | beforeEach(() => { 16 | clearViMocks(context); 17 | }); 18 | 19 | it("should return abort item", () => { 20 | const { result } = renderHook(useAbortAll); 21 | 22 | result.current(); 23 | 24 | expect(context.abort).toHaveBeenCalled(); 25 | }); 26 | }); 27 | -------------------------------------------------------------------------------- /packages/ui/shared/src/hooks/tests/useAbortBatch.test.js: -------------------------------------------------------------------------------- 1 | import useUploadyContext from "../useUploadyContext"; 2 | import useAbortBatch from "../useAbortBatch"; 3 | 4 | vi.mock("../useUploadyContext"); 5 | 6 | describe("useAbortItem tests", () => { 7 | const context = { 8 | abortBatch: vi.fn() 9 | }; 10 | 11 | beforeAll(() => { 12 | useUploadyContext.mockReturnValue(context); 13 | }); 14 | 15 | beforeEach(() => { 16 | clearViMocks(context); 17 | }); 18 | 19 | it("should return abort item", () => { 20 | const { result } = renderHook(useAbortBatch); 21 | 22 | result.current("123"); 23 | 24 | expect(context.abortBatch).toHaveBeenCalledWith("123"); 25 | }); 26 | }); 27 | -------------------------------------------------------------------------------- /packages/ui/shared/src/hooks/tests/useAbortItem.test.js: -------------------------------------------------------------------------------- 1 | import useUploadyContext from "../useUploadyContext"; 2 | import useAbortItem from "../useAbortItem"; 3 | 4 | vi.mock("../useUploadyContext"); 5 | 6 | describe("useAbortItem tests", () => { 7 | const context = { 8 | abort: vi.fn() 9 | }; 10 | 11 | beforeAll(() => { 12 | useUploadyContext.mockReturnValue(context); 13 | }); 14 | 15 | beforeEach(() => { 16 | clearViMocks(context); 17 | }); 18 | 19 | it("should return abort item", () => { 20 | const { result } = renderHook(useAbortItem); 21 | 22 | result.current("123"); 23 | 24 | expect(context.abort).toHaveBeenCalledWith("123"); 25 | }); 26 | }); 27 | -------------------------------------------------------------------------------- /packages/ui/shared/src/hooks/tests/useUploadyContext.test.js: -------------------------------------------------------------------------------- 1 | import assertContext from "../../assertContext"; 2 | import useUploadyContext from "../useUploadyContext"; 3 | 4 | vi.mock("../../assertContext"); 5 | 6 | describe("useUploadyContext tests", () => { 7 | beforeAll(()=>{ 8 | assertContext.mockReturnValue("context"); 9 | }); 10 | 11 | it("should set options on context", () => { 12 | const { result } = renderHook(useUploadyContext); 13 | 14 | expect(result.current).toBe("context"); 15 | }); 16 | }); 17 | 18 | 19 | -------------------------------------------------------------------------------- /packages/ui/shared/src/hooks/useAbortAll.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import { useCallback } from "react"; 3 | import useUploadyContext from "./useUploadyContext"; 4 | 5 | const useAbortAll: () => () => void = () => { 6 | const context = useUploadyContext(); 7 | 8 | return useCallback( 9 | () => context.abort(), 10 | [context] 11 | ); 12 | }; 13 | 14 | export default useAbortAll; 15 | -------------------------------------------------------------------------------- /packages/ui/shared/src/hooks/useAbortBatch.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import { useCallback } from "react"; 3 | import useUploadyContext from "./useUploadyContext"; 4 | 5 | const useAbortBatch: () => (id: string) => void = () => { 6 | const context = useUploadyContext(); 7 | 8 | return useCallback( 9 | (id: string) => context.abortBatch(id), 10 | [context] 11 | ); 12 | }; 13 | 14 | export default useAbortBatch; 15 | -------------------------------------------------------------------------------- /packages/ui/shared/src/hooks/useAbortItem.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import { useCallback } from "react"; 3 | import useUploadyContext from "./useUploadyContext"; 4 | 5 | const useAbortItem = (): ((id: string) => any) => { 6 | const context = useUploadyContext(); 7 | 8 | return useCallback( 9 | (id: string) => context.abort(id), 10 | [context] 11 | ); 12 | }; 13 | 14 | export default useAbortItem; 15 | -------------------------------------------------------------------------------- /packages/ui/shared/src/hooks/useUploadOptions.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import useUploadyContext from "./useUploadyContext"; 3 | 4 | import type { UploaderCreateOptions } from "@rpldy/uploader"; 5 | 6 | const useUploadOptions = (options?: UploaderCreateOptions): UploaderCreateOptions => { 7 | const context = useUploadyContext(); 8 | 9 | if (options) { 10 | context.setOptions(options); 11 | } 12 | 13 | return context.getOptions(); 14 | }; 15 | 16 | export default useUploadOptions; 17 | -------------------------------------------------------------------------------- /packages/ui/shared/src/hooks/useUploadyContext.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import { useContext } from "react"; 3 | import UploadyContext from "../UploadyContext"; 4 | import assertContext from "../assertContext"; 5 | 6 | import type { UploadyContextType } from "../types.js"; 7 | 8 | const useUploadyContext = (): UploadyContextType => 9 | assertContext(useContext(UploadyContext)); 10 | 11 | export default useUploadyContext; 12 | -------------------------------------------------------------------------------- /packages/ui/shared/src/tests/mocks/UploadyContext.mock.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | const UploadyContextMock = { 4 | showFileUpload: vi.fn(), 5 | getOptions: vi.fn(), 6 | setOptions: vi.fn(), 7 | setExternalFileInput: vi.fn(), 8 | getInternalFileInput: vi.fn(), 9 | upload: vi.fn(), 10 | getExtension: vi.fn(), 11 | on: vi.fn(), 12 | off: vi.fn(), 13 | Provider: vi.fn(({ children }) => { 14 | return
{children}
; 15 | }), 16 | createContextApi: vi.fn(), 17 | }; 18 | 19 | export { 20 | UploadyContextMock 21 | }; 22 | -------------------------------------------------------------------------------- /packages/ui/shared/src/uploadyVersion.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import { hasWindow } from "@rpldy/shared"; 3 | 4 | export const GLOBAL_VERSION_SYM: symbol = Symbol.for("_rpldy-version_"); 5 | 6 | const getVersion = (): string => process.env.BUILD_TIME_VERSION || ""; 7 | 8 | const getGlobal = () => 9 | /* istanbul ignore next */ 10 | hasWindow() ? window : (globalThis || process); 11 | 12 | const getRegisteredVersion = (): string => { 13 | const global = getGlobal(); 14 | return (global: Object)[GLOBAL_VERSION_SYM]; 15 | }; 16 | 17 | const registerUploadyContextVersion = (): void => { 18 | const global =getGlobal(); 19 | (global: Object)[GLOBAL_VERSION_SYM] = getVersion(); 20 | }; 21 | 22 | const getIsVersionRegisteredAndDifferent = (): boolean => { 23 | const registeredVersion = getRegisteredVersion(); 24 | return !!registeredVersion && registeredVersion !== getVersion(); 25 | }; 26 | 27 | export { 28 | getVersion, 29 | getRegisteredVersion, 30 | registerUploadyContextVersion, 31 | getIsVersionRegisteredAndDifferent, 32 | }; 33 | -------------------------------------------------------------------------------- /packages/ui/shared/src/utils.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import { isProduction } from "@rpldy/shared"; 3 | import { UPLOAD_OPTIONS_COMP } from "./consts"; 4 | 5 | const logWarning = (condition: ?any, msg: string) => { 6 | 7 | if (!isProduction() && !condition) { 8 | // eslint-disable-next-line no-console 9 | console.warn(msg); 10 | } 11 | }; 12 | 13 | const markAsUploadOptionsComponent = (Component: React.ComponentType): void => { 14 | Component[UPLOAD_OPTIONS_COMP] = true; 15 | }; 16 | 17 | const getIsUploadOptionsComponent = (Component: any): boolean => 18 | Component[UPLOAD_OPTIONS_COMP] === true || 19 | Component.target?.[UPLOAD_OPTIONS_COMP] === true || 20 | Component.render?.[UPLOAD_OPTIONS_COMP] === true; 21 | 22 | export { 23 | logWarning, 24 | markAsUploadOptionsComponent, 25 | getIsUploadOptionsComponent, 26 | }; 27 | -------------------------------------------------------------------------------- /packages/ui/tus-uploady/.npmignore: -------------------------------------------------------------------------------- 1 | src/ 2 | *.story.js 3 | *.stories.js 4 | *.test.js 5 | tests/ 6 | mocks/ 7 | lerna.json 8 | *.log 9 | jest* 10 | .flowconfig 11 | .editorconfig 12 | .eslintignore 13 | .eslintrc.js 14 | .circleci/ 15 | .sb-static/ 16 | .env 17 | .jest-cache/ 18 | .jest-coverage/ 19 | coverage/ 20 | .github/ 21 | 22 | -------------------------------------------------------------------------------- /packages/ui/tus-uploady/TusUploady.storydoc.mdx: -------------------------------------------------------------------------------- 1 | import { Markdown } from "@storybook/blocks"; 2 | import readme from "./README.md?raw"; 3 | 4 | {readme} 5 | -------------------------------------------------------------------------------- /packages/ui/tus-uploady/src/consts.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | export const NO_EXT = "Uploady - tus was not registered. Make sure you used the enhancer"; 4 | -------------------------------------------------------------------------------- /packages/ui/tus-uploady/src/index.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import TusUploady from "./TusUploady"; 3 | 4 | export default TusUploady; 5 | 6 | export { 7 | TusUploady, 8 | }; 9 | 10 | export { default as useClearResumableStore } from "./useClearResumableStore"; 11 | export { default as useTusResumeStartListener } from "./useTusResumeStartListener"; 12 | 13 | export * from "@rpldy/uploady"; 14 | 15 | export type { 16 | TusUploadyProps, 17 | } from "./types"; 18 | -------------------------------------------------------------------------------- /packages/ui/tus-uploady/src/tests/TusUploady.withoutChunkSupport.test.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { logWarning } from "@rpldy/shared-ui/src/tests/mocks/rpldy-ui-shared.mock"; 3 | import "@rpldy/uploady/src/tests/mocks/rpldy-uploady.mock"; 4 | import TusUploady from "../TusUploady"; 5 | import { getTusEnhancer } from "@rpldy/tus-sender"; 6 | 7 | vi.mock("@rpldy/tus-sender", () => ({ 8 | getTusEnhancer: vi.fn(), 9 | CHUNKING_SUPPORT: false 10 | })); 11 | 12 | describe("test TusUploady without chucking support", () => { 13 | it("should render Uploady when no chunk support", () => { 14 | render(); 15 | 16 | expect(logWarning).toHaveBeenCalledWith(false, expect.any(String)); 17 | expect(getTusEnhancer).not.toHaveBeenCalled(); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /packages/ui/tus-uploady/src/tests/useTusResumeStartListener.test.js: -------------------------------------------------------------------------------- 1 | import { generateUploaderEventHook } from "@rpldy/shared-ui"; 2 | import { TUS_EVENTS } from "@rpldy/tus-sender"; 3 | import "../useTusResumeStartListener"; 4 | 5 | vi.mock("@rpldy/shared-ui"); 6 | 7 | describe("TUS eventListenerHooks tests", () => { 8 | it.each([ 9 | TUS_EVENTS.RESUME_START 10 | ])("should generate TUS hooks for: %s", (event) => { 11 | expect(generateUploaderEventHook).toHaveBeenCalledWith(event); 12 | }); 13 | }); 14 | -------------------------------------------------------------------------------- /packages/ui/tus-uploady/src/types.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import type { UploadyProps } from "@rpldy/shared-ui"; 3 | import type { TusOptions, ResumeStartEventData } from "@rpldy/tus-sender"; 4 | 5 | export type TusUploadyProps = {| 6 | ...UploadyProps, 7 | ...$Exact, 8 | |}; 9 | 10 | export type ResumeStartEventResponse = void | boolean | { 11 | url?: string, 12 | resumeHeaders?: Object, 13 | }; 14 | 15 | export type TusResumeStartListenerHook = (cb: (data: ResumeStartEventData ) => ResumeStartEventResponse) => void; 16 | -------------------------------------------------------------------------------- /packages/ui/tus-uploady/src/useClearResumableStore.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import { invariant } from "@rpldy/shared"; 3 | import { useUploadyContext } from "@rpldy/shared-ui"; 4 | import { clearResumables, TUS_EXT } from "@rpldy/tus-sender"; 5 | import { NO_EXT } from "./consts"; 6 | 7 | const useClearResumableStore = (): (() => void) => { 8 | const context = useUploadyContext(); 9 | const ext = context.getExtension(TUS_EXT); 10 | invariant(ext, NO_EXT); 11 | 12 | return () => clearResumables(ext.getOptions()); 13 | }; 14 | 15 | export default useClearResumableStore; 16 | -------------------------------------------------------------------------------- /packages/ui/tus-uploady/src/useTusResumeStartListener.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import { generateUploaderEventHook } from "@rpldy/shared-ui"; 3 | import { TUS_EVENTS } from "@rpldy/tus-sender"; 4 | 5 | import type { TusResumeStartListenerHook } from "./types"; 6 | 7 | const useTusResumeStartListener: TusResumeStartListenerHook = 8 | generateUploaderEventHook(TUS_EVENTS.RESUME_START); 9 | 10 | export default useTusResumeStartListener; 11 | -------------------------------------------------------------------------------- /packages/ui/tus-uploady/types/index.d.ts: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { 3 | TusOptions, 4 | TusResumeStartEventData, 5 | TusResumeStartEventResponse 6 | } from "@rpldy/tus-sender"; 7 | import { UploadyProps } from "@rpldy/shared-ui"; 8 | 9 | export * from "@rpldy/uploady"; 10 | 11 | export interface TusUploadyProps extends UploadyProps, TusOptions {} 12 | 13 | export const TusUploady: React.ComponentType; 14 | 15 | export default TusUploady; 16 | 17 | export type ClearResumableStore = () => void; 18 | 19 | export const useClearResumableStore: () => ClearResumableStore; 20 | 21 | export const useTusResumeStartListener: (cb: (data: TusResumeStartEventData) => TusResumeStartEventResponse) => void; 22 | 23 | export { 24 | TusOptions, 25 | TusResumeStartEventData, 26 | TusResumeStartEventResponse, 27 | }; 28 | -------------------------------------------------------------------------------- /packages/ui/upload-button/.npmignore: -------------------------------------------------------------------------------- 1 | src/ 2 | *.story.js 3 | *.stories.js 4 | *.test.js 5 | tests/ 6 | mocks/ 7 | lerna.json 8 | *.log 9 | jest* 10 | .flowconfig 11 | .editorconfig 12 | .eslintignore 13 | .eslintrc.js 14 | .circleci/ 15 | .sb-static/ 16 | .env 17 | .jest-cache/ 18 | .jest-coverage/ 19 | coverage/ 20 | .github/ 21 | 22 | -------------------------------------------------------------------------------- /packages/ui/upload-button/UploadButton.storydoc.mdx: -------------------------------------------------------------------------------- 1 | import { Markdown } from "@storybook/blocks"; 2 | import readme from "./README.md?raw"; 3 | 4 | {readme} 5 | -------------------------------------------------------------------------------- /packages/ui/upload-button/src/UploadButton.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import React, { forwardRef } from "react"; 3 | import asUploadButton from "./asUploadButton"; 4 | import type { ComponentType } from "react"; 5 | 6 | const UploadButton = (asUploadButton(forwardRef( 7 | (props: any, ref: React.RefSetter | ((null | HTMLButtonElement) => mixed)) => 8 |