diff --git a/.changeset/README.md b/.changeset/README.md new file mode 100644 index 000000000..e5b6d8d6a --- /dev/null +++ b/.changeset/README.md @@ -0,0 +1,8 @@ +# Changesets + +Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works +with multi-package repos, or single-package repos to help you version and publish your code. You can +find the full documentation for it [in our repository](https://github.com/changesets/changesets) + +We have a quick list of common questions to get you started engaging with this project in +[our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md) diff --git a/.changeset/config.json b/.changeset/config.json new file mode 100644 index 000000000..24882d98d --- /dev/null +++ b/.changeset/config.json @@ -0,0 +1,33 @@ +{ + "$schema": "https://unpkg.com/@changesets/config@3.1.1/schema.json", + "changelog": "@changesets/cli/changelog", + "commit": false, + "fixed": [], + "linked": [], + "access": "public", + "baseBranch": "main", + "updateInternalDependencies": "patch", + "___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH": { + "onlyUpdatePeerDependentsWhenOutOfRange": true + }, + "ignore": [ + "angular", + "@uppy-dev/dev", + "example-angular", + "example-aws-companion", + "example-aws-nodejs", + "example-aws-php", + "example-cdn", + "example-companion", + "example-companion-custom-provider", + "example-companion-digitalocean-spaces", + "example-react", + "example-sveltekit", + "example-transloadit", + "example-vue", + "example-xhr-bundle", + "example-xhr-node", + "example-xhr-php", + "example-xhr-python" + ] +} diff --git a/.cursor/rules/headless-components.mdc b/.cursor/rules/headless-components.mdc new file mode 100644 index 000000000..976630919 --- /dev/null +++ b/.cursor/rules/headless-components.mdc @@ -0,0 +1,114 @@ +--- +description: +globs: packages/@uppy/components/src/** +alwaysApply: false +--- +# Headless components + +You are an expert at making headless UI components in Preact, similar to libraries like shadcn, except we don't rely on packages like radix. + +## Goal + +Making headless components in Preact for the open source Uppy file uploader and framework specific hooks. We want to give flexbility to users of this library by rendering sensible UI defaults and hooks that abstract Uppy functionality to completely build your own UI. + +Another way to give flexibility is to add data attributes selectively to some HTML elements. These can be used with CSS selectors by users of this library to conditionally apply styles: + +```html + +``` + +## How to build these components + +It's important to understand that an automated build script (bin/build-components.mjs) generates framework-specific wrappers for these Preact components. + +Here is an example from the React wrapper created by the script. + +```tsx +import { useEffect, useRef, useContext, createElement as h } from 'react' +import { + Dropzone as PreactDropzone, + type DropzoneProps, +} from '@uppy/components' +import { h as preactH } from 'preact' +import { render as preactRender } from 'preact/compat' +import { UppyContext } from './UppyContextProvider.js' + +export default function Dropzone(props: Omit) { + const ref = useRef(null) + const ctx = useContext(UppyContext) + + useEffect(() => { + if (ref.current) { + preactRender( + preactH(PreactDropzone, { + ...props, + ctx, + } satisfies DropzoneProps), + ref.current, + ) + } + }, [ctx, props]) + + return
+} +``` + +You don't have to worry about how these wrappers are created but it's important to know when building the Preact components that you will always receive a `ctx` prop to work with. This is its type: + +```ts +import type Uppy from '@uppy/core' +export type UploadStatus = + | 'init' + | 'ready' + | 'uploading' + | 'paused' + | 'error' + | 'complete' +export type UppyContext = { + uppy: Uppy | undefined + status: UploadStatus + progress: number +} +``` + +## Styling + +Styling is done with Tailwind CSS 4.x. There is no Tailwind config file. + +**IMPORTANT**: all classes have the `uppy:` prefix. Example: `bg-red-500` should become `uppy:bg-red-500`. + +Use `clsx` for conditional styles. + +```js +import clsx from 'clsx'; +// or +import { clsx } from 'clsx'; + +// Strings (variadic) +clsx('foo', true && 'bar', 'baz'); +//=> 'foo bar baz' + +// Objects +clsx({ foo:true, bar:false, baz:isTrue() }); +//=> 'foo baz' + +// Objects (variadic) +clsx({ foo:true }, { bar:false }, null, { '--foobar':'hello' }); +//=> 'foo --foobar' + +// Arrays +clsx(['foo', 0, false, 'bar']); +//=> 'foo bar' + +// Arrays (variadic) +clsx(['foo'], ['', 0, false, 'bar'], [['baz', [['hello'], 'there']]]); +//=> 'foo bar baz hello there' + +// Kitchen sink (with nesting) +clsx('foo', [1 && 'bar', { baz:false, bat:null }, ['hello', ['world']]], 'cya'); +//=> 'foo bar hello world cya' +``` \ No newline at end of file diff --git a/.cursor/rules/svelte.mdc b/.cursor/rules/svelte.mdc new file mode 100644 index 000000000..15f07d627 --- /dev/null +++ b/.cursor/rules/svelte.mdc @@ -0,0 +1,78 @@ +--- +description: +globs: packages/@uppy/svelte/**,examples/sveltekit/** +alwaysApply: false +--- +I'm using svelte 5 instead of svelte 4 here is an overview of the changes. + +## Overview of Changes + +Svelte 5 introduces runes, a set of advanced primitives for controlling reactivity. The runes replace certain non-runes features and provide more explicit control over state and effects. + +Snippets, along with render tags, help create reusable chunks of markup inside your components, reducing duplication and enhancing maintainability. + +## Event Handlers in Svelte 5 + +In Svelte 5, event handlers are treated as standard HTML properties rather than Svelte-specific directives, simplifying their use and integrating them more closely with the rest of the properties in the component. + +### Svelte 4 vs. Svelte 5: + +**Before (Svelte 4):** +```html + + +``` + +**After (Svelte 5):** +```html + + + + + + + +``` + +## Key Differences: + +1. **Reactivity is Explicit**: + - Svelte 5 uses `$state()` to explicitly mark reactive variables + - `$derived()` replaces `$:` for computed values + - `$effect()` replaces `$: {}` blocks for side effects + +2. **Event Handling is Standardized**: + - Svelte 4: `on:click={handler}` + - Svelte 5: `onclick={handler}` + +3. **Import Runes**: + - All runes must be imported from 'svelte': `import { $state, $effect, $derived, $props, $slots } from 'svelte';` + +4. **No More Event Modifiers**: + - Svelte 4: `on:click|preventDefault={handler}` + - Svelte 5: `onclick={e => { e.preventDefault(); handler(e); }}` + +This creates clearer, more maintainable components compared to Svelte 4's previous syntax by making reactivity explicit and using standardized web platform features. \ No newline at end of file diff --git a/.cursor/rules/uppy-core.mdc b/.cursor/rules/uppy-core.mdc new file mode 100644 index 000000000..862b6dab0 --- /dev/null +++ b/.cursor/rules/uppy-core.mdc @@ -0,0 +1,149 @@ +--- +description: Read this when you are about to use the Uppy class from @uppy/core +globs: +alwaysApply: false +--- +# Uppy Core Summary + +If you need to reference or work with the `Uppy` class from `@uppy/core`, here is an high-level overview of the class. + +## `Uppy` Class + +A modular file uploader. Manages plugins, state, events, and file handling. + +**Generics:** + +* `M`: Metadata object shape associated with files (`Meta`). +* `B`: Body object shape associated with files (`Body`). + +**Key Public Properties:** + +* `opts: NonNullableUppyOptions`: The fully resolved Uppy options. +* `store: Store>`: The state management store (defaults to `DefaultStore`). +* `i18n: I18n`: The translation function. +* `i18nArray: Translator['translateArray']`: The array translation function. +* `locale: Locale`: The active locale object. + +**Constructor:** + +* `constructor(opts?: UppyOptionsWithOptionalRestrictions)` + +**Core Public Methods:** + +* `use>(Plugin: T, ...args: OmitFirstArg>): this`: Adds a plugin instance. +* `getPlugin = UnknownPlugin>(id: string): T | undefined`: Retrieves a plugin instance by ID. +* `iteratePlugins(method: (plugin: UnknownPlugin) => void): void`: Executes a function on all installed plugins. +* `removePlugin(instance: UnknownPlugin): void`: Removes a plugin instance. +* `addFile(file: File | MinimalRequiredUppyFile): UppyFile['id']`: Adds a single file. +* `addFiles(fileDescriptors: MinimalRequiredUppyFile[]): void`: Adds multiple files. +* `removeFile(fileID: string): void`: Removes a file by ID. +* `removeFiles(fileIDs: string[]): void`: Removes multiple files by ID. +* `getFile(fileID: string): UppyFile`: Retrieves a file object by ID. +* `getFiles(): UppyFile[]`: Retrieves all file objects as an array. +* `setOptions(newOpts: MinimalRequiredOptions): void`: Updates Uppy options. +* `setState(patch?: Partial>): void`: Updates the Uppy state. +* `getState(): State`: Retrieves the current Uppy state. +* `setFileState(fileID: string, state: Partial>): void`: Updates the state for a specific file. +* `setMeta(data: Partial): void`: Merges metadata into the global `state.meta` and all file `meta` objects. +* `setFileMeta(fileID: string, data: Partial): void`: Merges metadata into a specific file's `meta` object. +* `upload(): Promise> | undefined>`: Starts the upload process for all new files. +* `retryUpload(fileID: string): Promise | undefined>`: Retries a failed upload for a specific file. +* `retryAll(): Promise | undefined>`: Retries all failed uploads. +* `cancelAll(): void`: Cancels all uploads and removes all files. +* `pauseResume(fileID: string): boolean | undefined`: Toggles pause/resume state for a resumable upload. +* `pauseAll(): void`: Pauses all resumable uploads. +* `resumeAll(): void`: Resumes all paused uploads. +* `info(message: string | { message: string; details?: string | Record }, type?: LogLevel, duration?: number): void`: Displays an informational message via UI plugins. +* `hideInfo(): void`: Hides the oldest info message. +* `log(message: unknown, type?: 'error' | 'warning'): void`: Logs a message using the configured logger. +* `on>(event: K, callback: UppyEventMap[K]): this`: Registers an event listener. +* `off>(event: K, callback: UppyEventMap[K]): this`: Unregisters an event listener. +* `emit>(event: T, ...args: Parameters[T]>): void`: Emits an event. +* `destroy(): void`: Uninstalls all plugins and cleans up the Uppy instance. + +**Internal Lifecycle Methods (Called by Uppy):** + +* `addPreProcessor(fn: Processor): void` +* `removePreProcessor(fn: Processor): boolean` +* `addPostProcessor(fn: Processor): void` +* `removePostProcessor(fn: Processor): boolean` +* `addUploader(fn: Processor): void` +* `removeUploader(fn: Processor): boolean` + +--- + +## Key Associated Types + +* **`UppyFile`**: Represents a file within Uppy. + * `id: string`: Unique file ID. + * `source?: string`: Source plugin ID. + * `name: string`: File name. + * `type?: string`: MIME type. + * `data: Blob | File`: The actual file data. + * `meta: M & { name: string, type: string }`: User and Uppy metadata. + * `size: number | null`: File size in bytes. + * `isRemote: boolean`: If the file is from a remote source (Companion). + * `remote?: { requestClientId: string, ... }`: Remote source details. + * `progress: FileProgressStarted | FileProgressNotStarted`: Upload progress state. + * `error?: string`: Error message if upload failed. + * `isPaused?: boolean`: If upload is paused (for resumable uploads). + * `isGhost?: boolean`: If file is restored without data (Golden Retriever). + * `response?: { status: number, body: B, uploadURL?: string, ... }`: Upload response details. + * `preview?: string`: URL to a preview image. + * `[key: string]: any`: Extensible with plugin-specific state. + +* **`State`**: The main Uppy state object. + * `files: { [fileID: string]: UppyFile }`: Object map of files by ID. + * `currentUploads: { [uploadID: string]: CurrentUpload }`: Map of active uploads. + * `capabilities: { uploadProgress: boolean, individualCancellation: boolean, resumableUploads: boolean, ... }`: Detected/configured capabilities. + * `totalProgress: number`: Overall upload progress (0-100). + * `meta: M`: Global metadata. + * `info: Array<{ type: LogLevel, message: string, details?: string | Record }>`: Info messages for UI display. + * `error: string | null`: Global error message. + * `allowNewUpload: boolean`: Whether new uploads can be started. + * `plugins: { [pluginID: string]: Record }`: State managed by plugins. + * `recoveredState: null | { files: ..., currentUploads: ... }`: State recovered by Golden Retriever. + +* **`UppyOptions`**: Options for the Uppy constructor. + * `id?: string`: Instance ID (default: 'uppy'). + * `autoProceed?: boolean`: Start upload automatically after files are added (default: false). + * `allowMultipleUploadBatches?: boolean`: Allow adding files while an upload is in progress (default: true). + * `debug?: boolean`: Enable debug logging (default: false). + * `restrictions: Restrictions`: File restrictions (type, size, number). + * `meta?: M`: Initial global metadata. + * `onBeforeFileAdded?: (currentFile: UppyFile, files: { ... }) => UppyFile | boolean | undefined`: Hook before a file is added. + * `onBeforeUpload?: (files: { ... }) => { ... } | boolean`: Hook before an upload starts. + * `locale?: Locale`: Custom locale strings. + * `store?: Store>`: Custom state store. + * `logger?: { debug, warn, error }`: Custom logger. + * `infoTimeout?: number`: Duration for info messages (ms). + +* **`Restrictions`**: File restriction options. + * `maxFileSize?: number | null`: Max individual file size (bytes). + * `minFileSize?: number | null`: Min individual file size (bytes). + * `maxTotalFileSize?: number | null`: Max total size of all files (bytes). + * `maxNumberOfFiles?: number | null`: Max number of files allowed. + * `minNumberOfFiles?: number | null`: Min number of files required. + * `allowedFileTypes?: string[] | null`: Allowed MIME types or extensions (e.g., `['image/*', '.pdf']`). + * `requiredMetaFields?: string[]`: Meta fields that must be present before upload. + +* **`UppyEventMap`**: Map of event names to their callback signatures (includes events like `file-added`, `upload-progress`, `upload-success`, `complete`, `error`, `restriction-failed`, etc.). + +* **`UploadResult`**: The object returned when an upload completes. + * `successful?: UppyFile[]`: Array of successfully uploaded files. + * `failed?: UppyFile[]`: Array of files that failed to upload. + * `uploadID?: string`: The ID of the upload batch. + +* **`BasePlugin<...> `**: The base class for all Uppy plugins. + * `id: string`: Unique plugin ID. + * `type: string`: Plugin type ('acquirer', 'modifier', 'uploader', 'presenter', 'orchestrator', 'logger'). + * `uppy: Uppy`: Reference to the Uppy instance. + * `install(): void`: Called when the plugin is added. + * `uninstall(): void`: Called when the plugin is removed. + * `update(state: Partial>): void`: Called on every Uppy state update. + +* **`Processor`**: Type for pre/post/upload processor functions `(fileIDs: string[], uploadID: string) => Promise | void`. + +* **`LogLevel`**: `'info' | 'warning' | 'error' | 'success'`. + +* **`PartialTree`**: Type used by Provider plugins (like Google Drive) to represent folder structures (`(PartialTreeFile | PartialTreeFolder)[]`). diff --git a/.env.example b/.env.example index 7df30462f..a3bbe74b7 100644 --- a/.env.example +++ b/.env.example @@ -15,6 +15,9 @@ COMPANION_PREAUTH_SECRET=development2 # NOTE: Only enable this in development. Enabling it in production is a security risk COMPANION_ALLOW_LOCAL_URLS=true +COMPANION_ENABLE_URL_ENDPOINT=true +COMPANION_ENABLE_GOOGLE_PICKER_ENDPOINT=true + # to enable S3 COMPANION_AWS_KEY="YOUR AWS KEY" COMPANION_AWS_SECRET="YOUR AWS SECRET" @@ -89,3 +92,10 @@ VITE_TRANSLOADIT_TEMPLATE=*** VITE_TRANSLOADIT_SERVICE_URL=https://api2.transloadit.com # Fill in if you want requests sent to Transloadit to be signed: # VITE_TRANSLOADIT_SECRET=*** + +# For Google Photos Picker and Google Drive Picker: +VITE_GOOGLE_PICKER_CLIENT_ID=*** + +# For Google Drive Picker +VITE_GOOGLE_PICKER_API_KEY=*** +VITE_GOOGLE_PICKER_APP_ID=*** diff --git a/.eslintignore b/.eslintignore deleted file mode 100644 index 26bf2ac7b..000000000 --- a/.eslintignore +++ /dev/null @@ -1,8 +0,0 @@ -node_modules -lib -dist -coverage -test/lib/** -test/endtoend/*/build -examples/svelte-example/public/build/ -bundle-legacy.js diff --git a/.eslintrc.js b/.eslintrc.js deleted file mode 100644 index 367b80a87..000000000 --- a/.eslintrc.js +++ /dev/null @@ -1,523 +0,0 @@ -/* eslint-disable quote-props */ - -'use strict' - -const svgPresentationAttributes = [ - 'alignment-baseline', 'baseline-shift', 'class', 'clip', 'clip-path', 'clip-rule', 'color', 'color-interpolatio', 'color-interpolatio-filters', 'color-profile', 'color-rendering', 'cursor', 'direction', 'display', 'dominant-baseline', 'enable-background', 'fill', 'fill-opacity', 'fill-rule', 'filter', 'flood-color', 'flood-opacity', 'font-family', 'font-size', 'font-size-adjust', 'font-stretch', 'font-style', 'font-variant', 'font-weight', 'glyph-orientation-horizontal', 'glyph-orientation-vertical', 'image-rendering', 'kerning', 'letter-spacing', 'lighting-color', 'marker-end', 'marker-mid', 'marker-start', 'mask', 'opacity', 'overflow', 'pointer-events', 'shape-rendering', 'stop-color', 'stop-opacity', 'stroke', 'stroke-dasharray', 'stroke-dashoffset', 'stroke-linecap', 'stroke-linejoin', 'stroke-miterlimit', 'stroke-opacity', 'stroke-width', 'text-anchor', 'text-decoration', 'text-rendering', 'transform', 'transform-origin', 'unicode-bidi', 'vector-effect', 'visibility', 'word-spacing', 'writing-mod', -] - -module.exports = { - root: true, - extends: ['transloadit', 'prettier'], - env: { - es6: true, - jest: true, - node: true, - // extra: - browser: true, - }, - globals: { - globalThis: true, - hexo: true, - window: true, - }, - plugins: [ - '@babel/eslint-plugin', - 'jest', - 'markdown', - 'node', - 'prefer-import', - 'promise', - 'react', - // extra: - 'compat', - 'jsdoc', - 'no-only-tests', - 'unicorn', - ], - parser: '@babel/eslint-parser', - parserOptions: { - sourceType: 'script', - ecmaVersion: 2022, - ecmaFeatures: { - jsx: true, - }, - }, - rules: { - // transloadit rules we are actually ok with in the uppy repo - 'import/extensions': 'off', - 'object-shorthand': ['error', 'always'], - 'strict': 'off', - 'key-spacing': 'off', - 'max-classes-per-file': ['error', 2], - 'react/no-unknown-property': ['error', { - ignore: svgPresentationAttributes, - }], - - // Special rules for CI: - ...(process.env.CI && { - // Some imports are available only after a full build, which we don't do on CI. - 'import/no-unresolved': 'off', - }), - - // rules we want to enforce - 'array-callback-return': 'error', - 'func-names': 'error', - 'import/no-dynamic-require': 'error', - 'import/no-extraneous-dependencies': 'error', - 'max-len': 'error', - 'no-empty': 'error', - 'no-bitwise': 'error', - 'no-continue': 'error', - 'no-lonely-if': 'error', - 'no-nested-ternary': 'error', - 'no-restricted-properties': 'error', - 'no-return-assign': 'error', - 'no-underscore-dangle': 'error', - 'no-unused-expressions': 'error', - 'no-unused-vars': 'error', - 'no-useless-concat': 'error', - 'no-var': 'error', - 'node/handle-callback-err': 'error', - 'prefer-destructuring': 'error', - 'prefer-spread': 'error', - 'unicorn/prefer-node-protocol': 'error', - - 'react/button-has-type': 'error', - 'react/forbid-prop-types': 'error', - 'react/no-access-state-in-setstate': 'error', - 'react/no-array-index-key': 'error', - 'react/no-deprecated': 'error', - 'react/no-this-in-sfc': 'error', - 'react/no-will-update-set-state': 'error', - 'react/prefer-stateless-function': 'error', - 'react/require-default-props': ['error', { - forbidDefaultForRequired: true, - functions: 'ignore', - }], - 'react/sort-comp': 'error', - 'react/static-property-placement': 'off', - 'react/style-prop-object': 'error', - - // accessibility - 'jsx-a11y/alt-text': 'error', - 'jsx-a11y/anchor-has-content': 'error', - 'jsx-a11y/click-events-have-key-events': 'error', - 'jsx-a11y/control-has-associated-label': 'error', - 'jsx-a11y/label-has-associated-control': 'error', - 'jsx-a11y/media-has-caption': 'error', - 'jsx-a11y/mouse-events-have-key-events': 'error', - 'jsx-a11y/no-interactive-element-to-noninteractive-role': 'error', - 'jsx-a11y/no-noninteractive-element-interactions': 'error', - 'jsx-a11y/no-static-element-interactions': 'error', - - // compat - 'compat/compat': ['error'], - - // jsdoc - 'jsdoc/check-alignment': 'error', - 'jsdoc/check-examples': 'off', // cannot yet be supported for ESLint 8, see https://github.com/eslint/eslint/issues/14745 - 'jsdoc/check-param-names': 'error', - 'jsdoc/check-syntax': 'error', - 'jsdoc/check-tag-names': ['error', { jsxTags: true }], - 'jsdoc/check-types': 'error', - 'jsdoc/valid-types': 'error', - 'jsdoc/check-indentation': ['off'], - }, - - settings: { - 'import/core-modules': ['tsd'], - react: { - pragma: 'h', - }, - jsdoc: { - mode: 'typescript', - }, - polyfills: [ - 'Promise', - 'fetch', - 'Object.assign', - 'document.querySelector', - ], - }, - - overrides: [ - { - files: [ - '*.jsx', - '*.tsx', - 'packages/@uppy/react-native/**/*.js', - ], - parser: 'espree', - parserOptions: { - sourceType: 'module', - ecmaFeatures: { - jsx: true, - }, - }, - rules: { - 'no-restricted-globals': [ - 'error', - { - name: '__filename', - message: 'Use import.meta.url instead', - }, - { - name: '__dirname', - message: 'Not available in ESM', - }, - { - name: 'exports', - message: 'Not available in ESM', - }, - { - name: 'module', - message: 'Not available in ESM', - }, - { - name: 'require', - message: 'Use import instead', - }, - { - name: 'JSX', - message: 'Use h.JSX.Element, ComponentChild, or ComponentChildren from Preact', - }, - { - name: 'React', - message: 'Import the value instead of relying on global.React.', - }, - ], - 'import/extensions': ['error', 'ignorePackages'], - }, - }, - { - files: [ - '*.mjs', - 'e2e/clients/**/*.js', - 'examples/aws-companion/*.js', - 'examples/aws-php/*.js', - 'examples/bundled/*.js', - 'examples/custom-provider/client/*.js', - 'examples/digitalocean-spaces/*.js', - 'examples/multiple-instances/*.js', - 'examples/node-xhr/*.js', - 'examples/php-xhr/*.js', - 'examples/python-xhr/*.js', - 'examples/react-example/*.js', - 'examples/redux/*.js', - 'examples/transloadit/*.js', - 'examples/transloadit-markdown-bin/*.js', - 'examples/xhr-bundle/*.js', - 'private/dev/*.js', - 'private/release/*.js', - 'private/remark-lint-uppy/*.js', - 'packages/@uppy/!(companion|react-native)/**/*.js', - ], - parser: 'espree', - parserOptions: { - sourceType: 'module', - ecmaFeatures: { - jsx: false, - }, - }, - rules: { - 'import/named': 'off', // Disabled because that rule tries and fails to parse JSX dependencies. - 'import/no-named-as-default': 'off', // Disabled because that rule tries and fails to parse JSX dependencies. - 'import/no-named-as-default-member': 'off', // Disabled because that rule tries and fails to parse JSX dependencies. - 'no-restricted-globals': [ - 'error', - { - name: '__filename', - message: 'Use import.meta.url instead', - }, - { - name: '__dirname', - message: 'Not available in ESM', - }, - { - name: 'exports', - message: 'Not available in ESM', - }, - { - name: 'module', - message: 'Not available in ESM', - }, - { - name: 'require', - message: 'Use import instead', - }, - { - name: 'JSX', - message: 'Use h.JSX.Element, ComponentChild, or ComponentChildren from Preact', - }, - { - name: 'React', - message: 'Import the value instead of relying on global.React.', - }, - ], - 'import/extensions': ['error', 'ignorePackages'], - }, - }, - { - files: ['packages/uppy/*.mjs'], - rules: { - 'import/first': 'off', - 'import/newline-after-import': 'off', - 'import/no-extraneous-dependencies': ['error', { - devDependencies: true, - }], - }, - }, - { - files: [ - 'packages/@uppy/*/types/*.d.ts', - ], - rules : { - 'import/no-unresolved': 'off', - 'max-classes-per-file': 'off', - 'no-use-before-define': 'off', - }, - }, - { - files: [ - 'packages/@uppy/dashboard/src/components/**/*.jsx', - ], - rules: { - 'react/destructuring-assignment': 'off', - }, - }, - { - files: [ - // Those need looser rules, and cannot be made part of the stricter rules above. - // TODO: update those to more modern code when switch to ESM is complete - 'examples/react-native-expo/*.js', - 'examples/svelte-example/**/*.js', - 'examples/vue/**/*.js', - 'examples/vue3/**/*.js', - ], - rules: { - 'no-unused-vars': [ - 'error', - { - 'varsIgnorePattern': 'React', - }, - ], - }, - parserOptions: { - sourceType: 'module', - }, - }, - { - files: ['./packages/@uppy/companion/**/*.js'], - rules: { - 'no-underscore-dangle': 'off', - }, - }, - { - files: [ - '*.test.js', - '*.test.ts', - 'test/endtoend/*.js', - 'bin/**.js', - ], - rules: { - 'compat/compat': ['off'], - }, - }, - { - files: [ - 'bin/**.js', - 'bin/**.mjs', - 'examples/**/*.cjs', - 'examples/**/*.js', - 'packages/@uppy/companion/test/**/*.js', - 'test/**/*.js', - 'test/**/*.ts', - '*.test.js', - '*.test.ts', - '*.test-d.ts', - '*.test-d.tsx', - 'postcss.config.js', - '.eslintrc.js', - 'private/**/*.js', - 'private/**/*.mjs', - ], - rules: { - 'no-console': 'off', - 'import/no-extraneous-dependencies': ['error', { - devDependencies: true, - }], - }, - }, - - { - files: [ - 'packages/@uppy/locales/src/*.js', - 'packages/@uppy/locales/template.js', - ], - rules: { - camelcase: ['off'], - 'quote-props': ['error', 'as-needed', { 'numbers': true }], - }, - }, - - { - files: ['test/endtoend/*/*.mjs', 'test/endtoend/*/*.ts'], - rules: { - // we mostly import @uppy stuff in these files. - 'import/no-extraneous-dependencies': ['off'], - }, - }, - { - files: ['test/endtoend/*/*.js'], - env: { - mocha: true, - }, - }, - - { - files: ['packages/@uppy/react/src/**/*.js'], - rules: { - 'import/no-extraneous-dependencies': ['error', { - peerDependencies: true, - }], - }, - }, - - { - files: ['**/*.md', '*.md'], - processor: 'markdown/markdown', - }, - { - files: ['docs/**/*.md/*.js'], - parserOptions: { - sourceType: 'module', - }, - }, - { - files: ['**/*.md/*.js', '**/*.md/*.javascript'], - excludedFiles: ["docs/**/*"], - parserOptions: { - sourceType: 'module', - }, - rules: { - 'react/destructuring-assignment': 'off', - 'no-restricted-globals': [ - 'error', - { - name: '__filename', - message: 'Use import.meta.url instead', - }, - { - name: '__dirname', - message: 'Not available in ESM', - }, - { - name: 'exports', - message: 'Not available in ESM', - }, - { - name: 'module', - message: 'Not available in ESM', - }, - { - name: 'require', - message: 'Use import instead', - }, - ], - }, - }, - { - files: ['**/*.ts', '**/*.md/*.ts', '**/*.md/*.typescript', '**/*.tsx', '**/*.md/*.tsx'], - excludedFiles: ['examples/angular-example/**/*.ts', 'packages/@uppy/angular/**/*.ts'], - parser: '@typescript-eslint/parser', - settings: { - 'import/resolver': { - node: { - extensions: ['.js', '.jsx', '.ts', '.tsx'], - }, - }, - }, - plugins: ['@typescript-eslint'], - extends: [ - 'eslint:recommended', - 'plugin:@typescript-eslint/eslint-recommended', - 'plugin:@typescript-eslint/recommended', - ], - rules: { - 'no-extra-semi': 'off', - 'no-restricted-syntax': ['error', { - selector: 'ImportDeclaration[importKind="type"][source.value=/^@uppy\\x2F[a-z-0-9]+\\x2F/]:not([source.value=/^@uppy\\x2Futils\\x2F/]):not([source.value=/\\.js$/])', - message: 'Use ".js" file extension for import type declarations from a different package', - }, { - selector: 'ImportDeclaration[importKind="type"][source.value=/^\\.\\.?\\x2F.+\\.js$/]', - message: 'Do not use ".js" file extension for relative import type declarations', - }, { - selector: 'ImportDeclaration[source.value=/^@uppy\\x2Futils\\x2Flib\\x2F.+\\.[mc]?[jt]sx?$/]', - message: 'Do not use file extension when importing from @uppy/utils', - }], - 'import/extensions': ['error', 'ignorePackages'], - 'import/prefer-default-export': 'off', - '@typescript-eslint/no-empty-function': 'off', - '@typescript-eslint/no-explicit-any': 'off', - '@typescript-eslint/no-extra-semi': 'off', - '@typescript-eslint/no-namespace': 'off', - '@typescript-eslint/no-non-null-assertion': 'off', - }, - }, - { - files: ['packages/@uppy/*/src/**/*.ts'], - excludedFiles: ['packages/@uppy/**/*.test.ts', 'packages/@uppy/core/src/mocks/*.ts'], - rules: { - '@typescript-eslint/explicit-module-boundary-types': 'error', - }, - }, - { - files: ['**/*.md/*.*'], - rules: { - 'import/no-extraneous-dependencies': 'off', - 'import/no-unresolved': 'off', - 'no-console': 'off', - 'no-undef': 'off', - 'no-unused-vars': 'off', - }, - }, - { - files: ['**/react/*.md/*.js', '**/react.md/*.js', '**/react-*.md/*.js', '**/react/**/*.test-d.tsx'], - settings: { - react: { pragma: 'React' }, - }, - }, - { - files: ['**/react/**/*.test-d.tsx'], - rules: { - 'import/extensions': 'off', - 'import/no-useless-path-segments': 'off', - 'no-alert': 'off', - 'no-inner-declarations': 'off', - 'no-lone-blocks': 'off', - 'no-unused-expressions': 'off', - 'no-unused-vars': 'off', - }, - }, - { - files: ['e2e/**/*.ts'], - extends: ['plugin:cypress/recommended'], - }, - { - files: ['e2e/**/*.ts', 'e2e/**/*.js', 'e2e/**/*.jsx', 'e2e/**/*.mjs'], - rules: { - 'import/no-extraneous-dependencies': 'off', - 'no-console': 'off', - 'no-only-tests/no-only-tests': 'error', - 'no-unused-expressions': 'off', - }, - }, - { - files: ["packages/@uppy/vue/**"], - rules: { - 'react-hooks/rules-of-hooks': 'off', - }, - }, - ], -} diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index b20655dea..22ea1f915 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -7,7 +7,7 @@ instructions. After you have successfully forked the repository, clone it locally. ```sh -git clone https://github.com/transloadit/uppy.git +git clone https://github.com/[your-username]/uppy.git cd uppy ``` @@ -24,24 +24,37 @@ corepack enable ## Development -First of all, install Uppy dependencies: +Install dependencies: ```bash yarn install ``` -### Basic - -To run a basic development version of Uppy, run: +Build all packages: ```bash -yarn dev +yarn build ``` -and go to http://localhost:5174 (or whatever link the yarn command outputted). -As you edit Uppy code, the browser will live reload the changes. +### Development Commands -### Companion +- `yarn dev` - Start development server at http://localhost:5174 +- `yarn dev:with-companion` - Start dev server with Companion for cloud integrations +- `yarn start:companion` - Start only Companion server at http://localhost:3020 +- `yarn build:watch` - Build packages in watch mode +- `yarn test` - Run tests for all packages +- `yarn test:watch` - Run tests in watch mode +- `yarn typecheck` - Run TypeScript type checking +- `yarn check` - Run Biome linting and formatting + +### Headless components + +When adding a new component to `@uppy/components`, you have to run `yarn migrate:components` from root +to migrate the Preact components to React, Svelte, and Vue. + +This is not needed for changing existing components. + +## Companion If you’d like to work on features that the basic development version of Uppy doesn’t support, such as Uppy integrations with Instagram/Google Drive/Facebook @@ -62,12 +75,6 @@ This would get the Companion instance running on `http://localhost:3020`. It uses [nodemon](https://github.com/remy/nodemon) so it will automatically restart when files are changed. -### Live example - -An example server is running at , which is deployed -with -[Kubernetes](https://github.com/transloadit/uppy/blob/main/packages/%40uppy/companion/KUBERNETES.md) - ### How the Authentication and Token mechanism works This section describes how Authentication works between Companion and Providers. @@ -101,415 +108,6 @@ Authenticates and Uploads from Dropbox through Companion: - Companion reports progress to Uppy, as if it were a local upload. - Completed! -### Requiring files - -- If we are `require()`ing a file from the same subpackage, we can freely use - relative imports as long as the required file is under the `src` directory - (for example to import `@uppy/dashboard/src/utils/hi.js` from - `@uppy/dashboard/src/index.js`, use `require('./utils/hi.js')`). -- But if we want to `require()` some file from another subpackage - we should - use global @uppy requires, and they should always be in the form of - `@uppy/:packageName/(lib instead of src)/(same path).js` - -## Tests - -### Unit tests - -Unit tests are using Jest and can be run with: - -```bash -yarn test:unit -``` - -### End-to-End tests - -We use [Cypress](https://www.cypress.io/) for our e2e test suite. Be sure to -checkout -“[Writing your first test](https://docs.cypress.io/guides/getting-started/writing-your-first-test#Add-a-test-file)” -and the -“[Introduction to Cypress](https://docs.cypress.io/guides/core-concepts/introduction-to-cypress#Cypress-Can-Be-Simple-Sometimes)”. -You should also be aware of the -“[Best Practices](https://docs.cypress.io/guides/references/best-practices)”. - -To get started make sure you have your `.env` set up. Copy the contents of -`.env.example` to a file named `.env` and add the values relevant for the -test(s) you are trying to run. - -To start the testing suite run: - -``` -yarn e2e -``` - -This will run Cypress in watch-mode, and it will pick up and rebuild any changes -to JS files. If you need to change other files (like CSS for example), you need -to run the respective `yarn build:*` scripts. - -Alternatively the following command is the same as the above, except it doesn’t -run `build` first: - -``` -yarn e2e:skip-build -``` - -To generate the boilerplate for a new test run: - -``` -yarn e2e:generate -``` - -## Releases - -Releases are managed by GitHub Actions, here’s an overview of the process to -release a new Uppy version: - -- Run `yarn release` on your local machine. -- Follow the instructions and select what packages to release. **Warning:** - skipping packages results in those changes being “lost”, meaning they won’t be - picked up in the changelog automatically next release. Always try to release - all. -- Before committing, check if the generated files look good. -- When asked to edit the next CHANGELOG, only include changes related to the - package(s) you selected for release. -- Push to the Transloadit repository using the command given by the tool. Do not - open a PR yourself, the GitHub Actions will create one and assign you to it. -- Wait for all the GitHub Actions checks to pass. If one fails, try to figure - out why. Do not go ahead without consulting the rest of the team. -- Review the PR thoroughly, and if everything looks good to you, approve the PR. - Do not merge it manually! -- After the PR is automatically merged, the demos on transloadit.com should also - be updated. Check that some things work locally: - - the demos in the demo section work (try one that uses an import robot, and - one that you need to upload to) - - the demos on the homepage work and can import from Google Drive, Instagram, - Dropbox, etc. - -If you don’t have access to the transloadit.com source code ping @arturi or -@goto-bus-stop and we’ll pick it up. :sparkles: - -### Releasing hotfix patch - -#### Companion hotfix - -First checkout the tag of the version you want to patch: - -```bash -git checkout @uppy/companion@x.y.z -``` - -Now create a branch for your hotfix: - -```bash -git checkout -b x.y.z-hotfix -``` - -Run yarn to make sure all packages are consistent: - -```bash -corepack yarn -``` - -Now navigate to the Companion workspace: - -```bash -cd packages/@uppy/companion -``` - -**Now cherry pick your desired commits**. - -Next edit `CHANGELOG.md` and then commit it: - -```bash -git add CHANGELOG.md -git commit -m 'Update changelog' -``` - -Now let’s create the version & tag: - -```bash -mkdir -p .git && npm version --workspaces-update=false --tag-version-prefix='@uppy/companion@' patch -``` - -**Important:** Build Companion lib folder - -```bash -yarn run build -``` - -Run a “dry-run” first: - -```bash -corepack yarn pack -``` - -If the earlier command succeeded, let’s publish! - -```bash -corepack yarn npm publish --access public --tag=none -``` - -Now we can push our branch and tags. - -```bash -git push && git push --tags -``` - -#### Hotfix other packages - -For other Uppy packages, the process should be like Companion, but hasn’t been -documented yet. Make sure to remember to run `yarn` as well as building the -package first, then you can release it. If you do release any other packages, -please update this doc. - -## CSS guidelines - -The CSS standards followed in this project closely resemble those from -[Medium’s CSS Guidelines](https://gist.github.com/fat/a47b882eb5f84293c4ed). If -something is not mentioned here, follow their guidelines. - -### Naming conventions - -This project uses naming conventions adopted from the SUIT CSS framework. -[Read about them here](https://github.com/suitcss/suit/blob/master/doc/naming-conventions.md). - -To quickly summarize: - -#### Utilities - -Syntax: `u-[sm-|md-|lg-]` - -```css -.u-utilityName -.u-floatLeft -.u-lg-col6 -``` - -#### Components - -Syntax: `[-][-descendentName][--modifierName]` - -```css -.twt-Button /* Namespaced component */ -.MyComponent /* Components pascal cased */ -.Button--default /* Modified button style */ -.Button--large - -.Tweet -.Tweet-header /* Descendents */ -.Tweet-bodyText - -.Accordion.is-collapsed /* State of component */ -.Accordion.is-expanded -``` - -### SASS - -This project uses SASS, with some limitations on nesting. One-level-deep nesting -is allowed, but nesting may not extend a selector by using the `&` operator. For -example: - -```sass -/* BAD */ -.Button { - &--disabled { - ... - } -} - -/* GOOD */ -.Button { - ... -} - -.Button--disabled { - ... -} -``` - -### Mobile-first responsive approach - -Style to the mobile breakpoint with your selectors, then use `min-width` media -queries to add any styles to the tablet or desktop breakpoints. - -### Selector, rule ordering - -- All selectors are sorted alphabetically and by type. -- HTML elements go above classes and IDs in a file. -- Rules are sorted alphabetically. - -```scss -/* BAD */ -.wrapper { - width: 940px; - margin: auto; -} - -h1 { - color: red; -} - -.article { - width: 100%; - padding: 32px; -} - -/* GOOD */ -h1 { - color: red; -} - -.article { - padding: 32px; - width: 100%; -} - -.wrapper { - margin: auto; - width: 940px; -} -``` - -## Adding a new integration - -Before opening a pull request for the new integration, open an issue to discuss -said integration with the Uppy team. After discussing the integration, you can -get started on it. First off, you need to construct the basic components for -your integration. The following components are the current standard: - -- `Dashboard`: Inline Dashboard (`inline: true`) -- `DashboardModal`: Dashboard as a modal -- `DragDrop` -- `ProgressBar` -- `StatusBar` - -All these components should function as references to the normal component. -Depending on how the framework you’re using handles references to the DOM, your -approach to creating these may be different. For example, in React, you can -assign a property of the component to the reference of a component -([see here](https://github.com/transloadit/uppy/blob/425f9ecfbc8bc48ce6b734e4fc14fa60d25daa97/packages/%40uppy/react/src/Dashboard.js#L47-L54)). -This may differ in your framework, but from what we’ve found, the concepts are -generally pretty similar. - -If you’re familiar with React, Vue or soon Svelte, it might be useful to read -through the code of those integrations, as they lay out a pretty good structure. -After the basic components have been built, here are a few more important tasks -to get done: - -- Add TypeScript support in some capacity (if possible) -- Write documentation -- Add an example -- Configuring the build system - -### Common issues - -Before going into these tasks, here are a few common gotchas that you should be -aware of. - -#### Dependencies - -Your `package.json` should resemble something like this: - -```json -{ - "name": "@uppy/framework", - "dependencies": { - "@uppy/dashboard": "workspace:^", - "@uppy/drag-drop": "workspace:^", - "@uppy/progress-bar": "workspace:^", - "@uppy/status-bar": "workspace:^", - "@uppy/utils": "workspace:^", - "prop-types": "^15.6.1" - }, - "peerDependencies": { - "@uppy/core": "workspace:^" - }, - "publishConfig": { - "access": "public" - } -} -``` - -The most important part about this is that `@uppy/core` is a peer dependency. If -your framework complains about `@uppy/core` not being resolved, you can also add -it as a dev dependency - -### Adding TypeScript Support - -This section won’t be too in-depth, because TypeScript depends on your -framework. As general advice, prefer using `d.ts` files and vanilla JavaScript -over TypeScript files. This is circumstantial, but it makes handling the build -system a lot easier when TypeScript doesn’t have to transpiled. The version of -typescript in the monorepo is `4.1`. - -### Writing docs - -Generally, documentation for integrations can be broken down into a few pieces -that apply to every component, and then documentation for each component. The -structure should look something like this: - -- Installation -- Initializing Uppy (may vary depending on how the framework handles reactivity) -- Usage -- _For each component_ - - Loading CSS - - Props - -It may be easier to copy the documentation of earlier integrations and change -the parts that need to be changed rather than writing this from scratch. -Preferably, keep the documentation to one page. For the front-matter, write -something like: - -```markdown -title: Framework Name type: docs module: "@uppy/framework" order: 0 category: -"Other Integrations" -``` - -This data is used to generate Uppy’s website. - -Any change of the documentation that involves a security best practice must -substantiated with an external reference. See -[#3565](https://github.com/transloadit/uppy/issues/3565). - -### Adding an example - -You can likely use whatever code generation tool for your framework (ex. -`create-react-app`) to create this example. Make sure you add the same version -of `@uppy/core` to this as your peer dependency required, or you may run into -strange issues. Try to include all the components are some of their -functionality. -[The React example](https://github.com/transloadit/uppy/blob/main/examples/react-example/App.js) -is a great... well example of how to do this well. - -### Integrating the build system - -The biggest part of this is understanding Uppy’s build system. The high level -description is that `babel` goes through almost all the packages and transpiles -all the Javascript files in the `src` directory to more compatible JavaScript in -the `lib` folder. If you’re using vanilla JavaScript for your integration (like -React and Vue do), then you can use this build system and use the files -generated as your entry points. - -If you’re using some kind of more abstract file format (like Svelte), then you -probably want do to a few things: add the directory name to -[this `IGNORE` regex](https://github.com/transloadit/uppy/blob/425f9ecfbc8bc48ce6b734e4fc14fa60d25daa97/bin/build-lib.js#L15); -add all your build dependencies to the root `package.json` (try to keep this -small); add a new `build:framework` script to the root `package.json`. This -script usually looks something like this: - -```json -{ - "scripts": { - "build:framework": "cd framework && yarn run build" - } -} -``` - -Then, add this script to the `build:js` script. Try running the `build:js` -script and make sure it does not error. It may also be of use to make sure that -global dependencies aren’t being used (ex. not having rollup locally and relying -on a global install), as these dependencies won’t be present on the machine’s -handling building. - ## I18n and locales For more information about how to contribute to translations, see diff --git a/.github/stale.yml b/.github/stale.yml deleted file mode 100644 index 2eac2d395..000000000 --- a/.github/stale.yml +++ /dev/null @@ -1,17 +0,0 @@ -# Number of days of inactivity before an issue becomes stale -daysUntilStale: 365 -# Number of days of inactivity before a stale issue is closed -daysUntilClose: false -# Issues with these labels will never be considered stale -exemptLabels: - - Keep open - - 🔐 Security -# Label to use when marking an issue as stale -staleLabel: Stale -# Comment to post when marking an issue as stale. Set to `false` to disable -markComment: > - This issue has been automatically marked as stale because it has not had - recent activity. If the issue is still relevant, please upvote or leave a - comment. Thank you for your contribution! -# Comment to post when closing a stale issue. Set to `false` to disable -closeComment: false diff --git a/.github/workflows/bundlers.yml b/.github/workflows/bundlers.yml index 8f96efd6d..5c2aaa471 100644 --- a/.github/workflows/bundlers.yml +++ b/.github/workflows/bundlers.yml @@ -1,4 +1,4 @@ -name: Test different bundlers with Uppy +name: Bundlers on: push: @@ -24,7 +24,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout sources - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Get yarn cache directory path id: yarn-cache-dir-path run: @@ -42,14 +42,12 @@ jobs: node-version: lts/* - name: Install dependencies run: - corepack yarn workspaces focus $(corepack yarn workspaces list --json - | jq -r .name | awk '/^@uppy-example/{ next } { if ($0!="uppy.io") - print $0 }') + corepack yarn install env: # https://docs.cypress.io/guides/references/advanced-installation#Skipping-installation CYPRESS_INSTALL_BINARY: 0 - - name: Build lib - run: corepack yarn run build:lib + - name: Build + run: corepack yarn run build - name: Make Uppy bundle use local version run: | node <<'EOF' @@ -97,12 +95,13 @@ jobs: - name: Add Rollup as a dev dependency run: >- npm i --save-dev @rollup/plugin-commonjs @rollup/plugin-node-resolve - rollup@${{matrix.bundler-version}} + @rollup/plugin-json rollup@${{matrix.bundler-version}} - run: npx rollup --version - name: Create Rollup config file run: >- echo ' import cjs from "@rollup/plugin-commonjs"; import { nodeResolve - } from "@rollup/plugin-node-resolve"; + } from "@rollup/plugin-node-resolve"; import json from + "@rollup/plugin-json"; export default { input: "./lib/index.js", @@ -110,6 +109,7 @@ jobs: file: "/dev/null", }, plugins: [ + json(), cjs(), nodeResolve({ browser: true, exportConditions: ["browser"] }), ], @@ -136,10 +136,26 @@ jobs: run: npm i --save-dev webpack-cli webpack@${{matrix.bundler-version}} - run: npx webpack --version - name: Create Webpack config file - run: - echo 'export default - {mode:"production",target:"web",entry:"./lib/index.js"}' > - webpack.config.js + run: | + echo 'export default { + mode: "production", + target: "web", + entry: "./lib/index.js", + resolve: { + fallback: { + fs: false, + path: false, + stream: false, + util: false, + assert: false, + constants: false, + crypto: false, + http: false, + https: false, + url: false + } + } + }' > webpack.config.js - name: Bundle run: npx webpack diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 634718fc7..8be34256e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -6,10 +6,6 @@ on: paths-ignore: - '**.md' - 'assets/**' - - 'e2e/**' - - 'examples/**' - - 'private/**' - - 'website/**' - '.github/**' - '!.github/workflows/ci.yml' pull_request: @@ -17,11 +13,6 @@ on: types: [opened, synchronize, reopened] paths-ignore: - '**.md' - - 'assets/**' - - 'e2e/**' - - 'examples/**' - - 'private/**' - - 'website/**' - '.github/**' - '!.github/workflows/ci.yml' @@ -30,14 +21,14 @@ env: jobs: unit_tests: - name: Unit tests + name: Tests runs-on: ubuntu-latest strategy: matrix: - node-version: [18.x, 20.x] + node-version: [lts/*] steps: - name: Checkout sources - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Get yarn cache directory path id: yarn-cache-dir-path run: @@ -56,21 +47,25 @@ jobs: node-version: ${{matrix.node-version}} - name: Install dependencies run: - corepack yarn workspaces focus $(corepack yarn workspaces list --json - | jq -r .name | awk '/^@uppy-example/{ next } { if ($0!="uppy.io") - print $0 }') - env: - # https://docs.cypress.io/guides/references/advanced-installation#Skipping-installation - CYPRESS_INSTALL_BINARY: 0 + corepack yarn install + - name: Install Playwright Browsers + run: corepack yarn workspace @uppy/dashboard playwright install --with-deps + - name: Build + run: corepack yarn run build - name: Run tests - run: corepack yarn run test:unit + run: corepack yarn run test + env: + COMPANION_DATADIR: ./output + COMPANION_DOMAIN: localhost:3020 + COMPANION_PROTOCOL: http + COMPANION_REDIS_URL: redis://localhost:6379 types: - name: Type tests + name: Types runs-on: ubuntu-latest steps: - name: Checkout sources - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Get yarn cache directory path id: yarn-cache-dir-path run: @@ -89,11 +84,24 @@ jobs: node-version: lts/* - name: Install dependencies run: - corepack yarn workspaces focus $(corepack yarn workspaces list --json - | jq -r .name | awk '/^@uppy-example/{ next } { if ($0!="uppy.io") - print $0 }') - env: - # https://docs.cypress.io/guides/references/advanced-installation#Skipping-installation - CYPRESS_INSTALL_BINARY: 0 - - name: Attempt building TS packages - run: corepack yarn run build:ts + corepack yarn install + - run: corepack yarn run typecheck + + lint_js: + name: Lint + runs-on: ubuntu-latest + env: + SKIP_YARN_COREPACK_CHECK: true + steps: + - name: Checkout sources + uses: actions/checkout@v5 + + - name: Install Node.js + uses: actions/setup-node@v4 + with: + node-version: lts/* + - name: Install dependencies + run: + corepack yarn workspaces focus @uppy-dev/build + - name: Run linter + run: corepack yarn run check:ci diff --git a/.github/workflows/companion-deploy.yml b/.github/workflows/companion-deploy.yml index 5046f82a6..fd08e232d 100644 --- a/.github/workflows/companion-deploy.yml +++ b/.github/workflows/companion-deploy.yml @@ -20,7 +20,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout sources - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Set SHA commit in version run: (cd packages/@uppy/companion && node -e 'const @@ -46,24 +46,24 @@ jobs: COMPOSE_DOCKER_CLI_BUILD: 0 steps: - name: Checkout sources - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Docker meta id: docker_meta - uses: docker/metadata-action@8e5442c4ef9f78752691e2d8f8d19755c6f78e81 # v5.5.1 + uses: docker/metadata-action@902fa8ec7d6ecbf8d84d538b9b233a880e428804 # v5.7.0 with: images: transloadit/companion tags: | type=edge type=raw,value=latest,enable=false - - uses: docker/setup-qemu-action@49b3bc8e6bdd4a60e6116a5414239cba5943d3cf # v3.2.0 + - uses: docker/setup-qemu-action@29109295f81e9208d7d86ff1c6c12d2833863392 # v3.6.0 - uses: docker/setup-buildx-action@v3 - name: Log in to DockerHub - uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0 + uses: docker/login-action@184bdaa0721073962dff0199f1fb9940f07167d1 # v3.5.0 with: username: ${{secrets.DOCKER_USERNAME}} password: ${{secrets.DOCKER_PASSWORD}} - name: Build and push - uses: docker/build-push-action@5cd11c3a4ced054e52742c5fd54dca954e0edd85 # v6.7.0 + uses: docker/build-push-action@471d1dc4e07e5cdedd4c2171150001c434f0b7a4 # v6.15.0 with: push: true context: . @@ -77,12 +77,16 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout sources - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Alter dockerfile run: | sed -i 's/^EXPOSE 3020$/EXPOSE $PORT/g' Dockerfile + # https://github.com/AkhileshNS/heroku-deploy/issues/188 + - name: Install Heroku CLI + run: | + curl https://cli-assets.heroku.com/install.sh | sh - name: Deploy to heroku - uses: akhileshns/heroku-deploy@581dd286c962b6972d427fcf8980f60755c15520 # v3.13.15 + uses: akhileshns/heroku-deploy@e3eb99d45a8e2ec5dca08735e089607befa4bf28 # v3.14.15 with: heroku_api_key: ${{secrets.HEROKU_API_KEY}} heroku_app_name: companion-demo diff --git a/.github/workflows/companion.yml b/.github/workflows/companion.yml deleted file mode 100644 index 2c8e000f3..000000000 --- a/.github/workflows/companion.yml +++ /dev/null @@ -1,51 +0,0 @@ -name: Companion -on: - push: - branches: [main] - paths: - - yarn.lock - - 'packages/@uppy/companion/**' - - '.github/workflows/companion.yml' - pull_request: - # We want all branches so we configure types to be the GH default again - types: [opened, synchronize, reopened] - paths: - - yarn.lock - - 'packages/@uppy/companion/**' - - '.github/workflows/companion.yml' - -env: - YARN_ENABLE_GLOBAL_CACHE: false - -jobs: - test: - name: Unit tests - runs-on: ubuntu-latest - strategy: - matrix: - node-version: [18.x, 20.x, latest] - steps: - - name: Checkout sources - uses: actions/checkout@v4 - - name: Get yarn cache directory path - id: yarn-cache-dir-path - run: - echo "dir=$(corepack yarn config get cacheFolder)" >> $GITHUB_OUTPUT - - - uses: actions/cache@v4 - id: yarn-cache # use this to check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`) - with: - path: ${{ steps.yarn-cache-dir-path.outputs.dir }} - key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} - restore-keys: | - ${{ runner.os }}-yarn- - - name: Install Node.js - uses: actions/setup-node@v4 - with: - node-version: ${{matrix.node-version}} - - name: Install dependencies - run: corepack yarn workspaces focus @uppy/companion - - name: Run tests - run: corepack yarn run test:companion - - name: Run type checks in focused workspace - run: corepack yarn run build:companion diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index 37c45ccbd..00b0dcc6c 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -1,4 +1,5 @@ -name: End-to-end tests +name: Output + on: push: branches: [main] @@ -10,8 +11,8 @@ on: - 'website/**' - '.github/**' - '!.github/workflows/e2e.yml' - pull_request_target: - types: [opened, synchronize, reopened, labeled] + pull_request: + types: [opened, synchronize, reopened] paths-ignore: - '**.md' - '**.d.ts' @@ -19,19 +20,6 @@ on: - 'private/**' - 'website/**' - '.github/**' - pull_request: - types: [opened, synchronize, reopened] - paths: - - .github/workflows/e2e.yml - -concurrency: - group: - ${{ github.workflow }}--${{ github.event.pull_request.head.repo.full_name || - github.repository }} -- ${{ github.head_ref || github.ref }} - cancel-in-progress: - # For PRs coming from forks, we need the previous job to run until the end - # to be sure it can remove the `safe to test` label before it affects the next run. - ${{ github.event.pull_request.head.repo.full_name == github.repository }} permissions: pull-requests: write @@ -40,30 +28,18 @@ env: jobs: compare_diff: + name: Diff lib folders runs-on: ubuntu-latest env: DIFF_BUILDER: true outputs: diff: ${{ steps.diff.outputs.OUTPUT_DIFF }} - is_accurate_diff: ${{ steps.diff.outputs.IS_ACCURATE_DIFF }} steps: - name: Checkout sources - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: fetch-depth: 2 - ref: - ${{ github.event.pull_request && format('refs/pull/{0}/merge', - github.event.pull_request.number) || github.sha }} - - name: Check if there are "unsafe" changes - id: build_chain_changes - # If there are changes in JS script that generates the output, we cannot - # test them here without human review to make sure they don't contain - # someting "nasty". - run: | - EOF=$(dd if=/dev/urandom bs=15 count=1 status=none | base64) - echo "MIGHT_CONTAIN_OTHER_CHANGES<<$EOF" >> "$GITHUB_OUTPUT" - git --no-pager diff HEAD^ --name-only bin package.json yarn.lock babel.config.js >> "$GITHUB_OUTPUT" - echo "$EOF" >> "$GITHUB_OUTPUT" + ref: ${{ github.event.pull_request && format('refs/pull/{0}/merge', github.event.pull_request.number) || github.sha }} - run: git reset HEAD^ --hard - name: Get yarn cache directory path id: yarn-cache-dir-path @@ -81,50 +57,21 @@ jobs: uses: actions/setup-node@v4 with: node-version: lts/* - - name: Install dependencies - run: - corepack yarn workspaces focus $(corepack yarn workspaces list --json - | jq -r .name | awk '/^@uppy-example/{ next } { if ($0!="uppy.io") - print $0 }') + - run: corepack yarn install env: # https://docs.cypress.io/guides/references/advanced-installation#Skipping-installation CYPRESS_INSTALL_BINARY: 0 - - run: corepack yarn build:js:typeless + - run: corepack yarn build - name: Store output file run: tar cf /tmp/previousVersion.tar packages/@uppy/*/lib - name: Fetch source from the PR - if: steps.build_chain_changes.outputs.MIGHT_CONTAIN_OTHER_CHANGES == '' run: | git checkout FETCH_HEAD -- packages - echo 'IS_ACCURATE_DIFF=true' >> "$GITHUB_ENV" - - name: Fetch source from the PR - if: - steps.build_chain_changes.outputs.MIGHT_CONTAIN_OTHER_CHANGES != '' && - (!github.event.pull_request || (github.event.action == 'labeled' && - github.event.label.name == 'safe to test' && - github.event.pull_request.state == 'open') || - (github.event.pull_request.head.repo.full_name == github.repository && - github.event.event_name != 'labeled')) - run: | - git reset FETCH_HEAD --hard - corepack yarn workspaces focus $(\ - corepack yarn workspaces list --json | \ - jq -r .name | \ - awk '/^@uppy-example/{ next } { if ($0!="uppy.io") print $0 }'\ - ) - echo 'IS_ACCURATE_DIFF=true' >> "$GITHUB_ENV" + - run: corepack yarn install --no-immutable env: # https://docs.cypress.io/guides/references/advanced-installation#Skipping-installation CYPRESS_INSTALL_BINARY: 0 - - name: Fetch source from the PR - if: - steps.build_chain_changes.outputs.MIGHT_CONTAIN_OTHER_CHANGES != '' && - github.event.pull_request.head.repo.full_name != github.repository && - (github.event.action != 'labeled' || github.event.label.name != 'safe - to test') - run: | - git checkout FETCH_HEAD -- packages - - run: corepack yarn build:js:typeless + - run: corepack yarn build - name: Store output file run: tar cf /tmp/newVersion.tar packages/@uppy/*/lib - name: Setup git @@ -154,7 +101,6 @@ jobs: echo "OUTPUT_DIFF<<$EOF" >> "$GITHUB_OUTPUT" cd /tmp/uppy && git --no-pager diff >> "$GITHUB_OUTPUT" echo "$EOF" >> "$GITHUB_OUTPUT" - echo "IS_ACCURATE_DIFF=$IS_ACCURATE_DIFF" >> "$GITHUB_OUTPUT" - name: Add/update comment if: github.event.pull_request uses: marocchino/sticky-pull-request-comment@v2 @@ -166,145 +112,4 @@ jobs: ${{ steps.diff.outputs.OUTPUT_DIFF || 'No diff' }} ``` - ${{ env.IS_ACCURATE_DIFF != 'true' && format(fromJson('"The following build files have been modified and might affect the actual diff:\n\n```\n{0}\n```"'), steps.build_chain_changes.outputs.MIGHT_CONTAIN_OTHER_CHANGES) || '' }} - - - name: Remove 'safe to test' label if cancelled - if: - cancelled() && github.event.pull_request && - github.event.pull_request.head.repo.full_name != github.repository - run: gh pr edit "$NUMBER" --remove-label 'safe to test' - env: - NUMBER: ${{ github.event.pull_request.number }} - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - toggle-pending-e2e-label: - # Add the 'pending end-to-end tests' label for PRs that come from forks. - # For those PRs, we want to review the code before running e2e tests. - # See https://securitylab.github.com/research/github-actions-preventing-pwn-requests/. - needs: [compare_diff] - if: - github.event.pull_request.state == 'open' && - github.event.pull_request.head.repo.full_name != github.repository - runs-on: ubuntu-latest - steps: - - name: Add label - if: - (needs.compare_diff.outputs.diff != '' || - !needs.compare_diff.outputs.is_accurate_diff) && (github.event.action - != 'labeled' || github.event.label.name != 'safe to test') - env: - PR_URL: ${{ github.event.pull_request.html_url }} - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: - gh pr edit "$PR_URL" --repo ${{ github.repository }} --add-label - 'pending end-to-end tests' - - name: Remove label - if: - needs.compare_diff.outputs.diff == '' && - needs.compare_diff.outputs.is_accurate_diff && github.event.action == - 'labeled' && github.event.label.name == 'safe to test' - env: - PR_URL: ${{ github.event.pull_request.html_url }} - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: - gh pr edit "$PR_URL" --remove-label 'safe to test' --remove-label - 'pending end-to-end tests' - - e2e: - needs: [compare_diff] - if: - ${{ needs.compare_diff.outputs.diff != '' && (!github.event.pull_request - || (github.event.action == 'labeled' && github.event.label.name == 'safe - to test' && github.event.pull_request.state == 'open') || - (github.event.pull_request.head.repo.full_name == github.repository && - github.event.event_name != 'labeled')) }} - name: Browser tests - runs-on: ubuntu-latest - steps: - - name: Checkout sources - uses: actions/checkout@v4 - with: - ref: ${{ github.event.pull_request.head.sha || github.sha }} - - name: Get yarn cache directory path - id: yarn-cache-dir-path - run: - echo "dir=$(corepack yarn config get cacheFolder)" >> $GITHUB_OUTPUT - - - uses: actions/cache@v4 - id: yarn-cache # use this to check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`) - with: - path: ${{ steps.yarn-cache-dir-path.outputs.dir }} - key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} - restore-keys: | - ${{ runner.os }}-yarn- - - - name: Create cache folder for Cypress - id: cypress-cache-dir-path - run: echo "dir=$(mktemp -d)" >> $GITHUB_OUTPUT - - uses: actions/cache@v4 - with: - path: ${{ steps.cypress-cache-dir-path.outputs.dir }} - key: ${{ runner.os }}-cypress - - name: Install Node.js - uses: actions/setup-node@v4 - with: - node-version: lts/* - - - name: Start Redis - uses: supercharge/redis-github-action@ea9b21c6ecece47bd99595c532e481390ea0f044 # 1.8.0 - with: - redis-version: 7 - - - name: Install dependencies - run: corepack yarn install --immutable - env: - # https://docs.cypress.io/guides/references/advanced-installation#Binary-cache - CYPRESS_CACHE_FOLDER: ${{ steps.cypress-cache-dir-path.outputs.dir }} - - name: Build Uppy packages - run: corepack yarn build - - name: Run end-to-end browser tests - run: corepack yarn run e2e:ci - env: - COMPANION_DATADIR: ./output - COMPANION_DOMAIN: localhost:3020 - COMPANION_PROTOCOL: http - COMPANION_REDIS_URL: redis://localhost:6379 - COMPANION_UNSPLASH_KEY: ${{secrets.COMPANION_UNSPLASH_KEY}} - COMPANION_UNSPLASH_SECRET: ${{secrets.COMPANION_UNSPLASH_SECRET}} - COMPANION_AWS_KEY: ${{secrets.COMPANION_AWS_KEY}} - COMPANION_AWS_SECRET: ${{secrets.COMPANION_AWS_SECRET}} - COMPANION_AWS_BUCKET: ${{secrets.COMPANION_AWS_BUCKET}} - COMPANION_AWS_REGION: ${{secrets.COMPANION_AWS_REGION}} - VITE_COMPANION_URL: http://localhost:3020 - VITE_TRANSLOADIT_KEY: ${{secrets.TRANSLOADIT_KEY}} - VITE_TRANSLOADIT_SECRET: ${{secrets.TRANSLOADIT_SECRET}} - VITE_TRANSLOADIT_TEMPLATE: ${{secrets.TRANSLOADIT_TEMPLATE}} - VITE_TRANSLOADIT_SERVICE_URL: ${{secrets.TRANSLOADIT_SERVICE_URL}} - # https://docs.cypress.io/guides/references/advanced-installation#Binary-cache - CYPRESS_CACHE_FOLDER: ${{ steps.cypress-cache-dir-path.outputs.dir }} - - name: Upload videos in case of failure - uses: actions/upload-artifact@v4 - if: failure() - with: - name: videos-and-screenshots - path: | - e2e/cypress/videos/ - e2e/cypress/screenshots/ - - name: Remove labels - # Remove the 'pending end-to-end tests' label if tests ran successfully - if: - github.event.pull_request && - contains(github.event.pull_request.labels.*.name, 'pending end-to-end - tests') - run: gh pr edit "$NUMBER" --remove-label 'pending end-to-end tests' - env: - NUMBER: ${{ github.event.pull_request.number }} - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - name: Remove 'safe to test' label - if: - always() && github.event.pull_request && - contains(github.event.pull_request.labels.*.name, 'safe to test') - run: gh pr edit "$NUMBER" --remove-label 'safe to test' - env: - NUMBER: ${{ github.event.pull_request.number }} - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/linters.yml b/.github/workflows/linters.yml deleted file mode 100644 index 214796b1b..000000000 --- a/.github/workflows/linters.yml +++ /dev/null @@ -1,100 +0,0 @@ -name: Linters - -on: - push: - branches: [main] - paths-ignore: - - '.github/**' - - '!.github/workflows/linters.yml' - - '!.github/CONTRIBUTING.md' - pull_request: - # We want all branches so we configure types to be the GH default again - types: [opened, synchronize, reopened] - paths-ignore: - - '.github/**' - - '!.github/workflows/linters.yml' - - '!.github/CONTRIBUTING.md' - -env: - YARN_ENABLE_GLOBAL_CACHE: false - -jobs: - lint_js: - name: Lint JavaScript/TypeScript - runs-on: ubuntu-latest - steps: - - name: Checkout sources - uses: actions/checkout@v4 - - name: Get yarn cache directory path - id: yarn-cache-dir-path - run: - echo "dir=$(corepack yarn config get cacheFolder)" >> $GITHUB_OUTPUT - - - uses: actions/cache@v4 - id: yarn-cache # use this to check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`) - with: - path: ${{ steps.yarn-cache-dir-path.outputs.dir }} - key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} - restore-keys: | - ${{ runner.os }}-yarn- - - name: Install Node.js - uses: actions/setup-node@v4 - with: - node-version: lts/* - - name: Install dependencies - # List all projects that use a custom ESLint config: - run: - corepack yarn workspaces focus @uppy/angular @uppy-example/angular - @uppy-example/react-native-expo @uppy/react-native @uppy-dev/build - - name: Run linter - run: corepack yarn run lint - - name: Run Prettier - run: corepack yarn run format:check-diff - - lint_md: - name: Lint Markdown - runs-on: ubuntu-latest - steps: - - name: Checkout sources - uses: actions/checkout@v4 - - name: Get yarn cache directory path - id: yarn-cache-dir-path - run: - echo "dir=$(corepack yarn config get cacheFolder)" >> $GITHUB_OUTPUT - - - uses: actions/cache@v4 - id: yarn-cache # use this to check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`) - with: - path: ${{ steps.yarn-cache-dir-path.outputs.dir }} - key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} - restore-keys: | - ${{ runner.os }}-yarn- - - name: Install Node.js - uses: actions/setup-node@v4 - with: - node-version: lts/* - - name: Install dependencies - run: corepack yarn workspaces focus @uppy-dev/build - - name: Run linter - run: corepack yarn run lint:markdown - - lint_docs: - name: Lint Docs - runs-on: ubuntu-latest - steps: - - name: Checkout Uppy.io sources - uses: actions/checkout@v4 - with: - repository: transloadit/uppy.io - - run: rm -rf docs # the other PR has not landed - - name: Checkout docs - uses: actions/checkout@v4 - with: - path: uppy - - run: mv uppy /tmp/uppy && ln -s /tmp/uppy/docs docs - - name: Install dependencies - run: corepack yarn --immutable - - name: Lint files - run: corepack yarn lint - - name: Test build website - run: corepack yarn build diff --git a/.github/workflows/lockfile_check.yml b/.github/workflows/lockfile_check.yml index 3455c0f35..9334b8201 100644 --- a/.github/workflows/lockfile_check.yml +++ b/.github/workflows/lockfile_check.yml @@ -20,7 +20,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout sources - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Get yarn cache directory path id: yarn-cache-dir-path run: diff --git a/.github/workflows/manual-cdn.yml b/.github/workflows/manual-cdn.yml index fc818f2c8..7c6ec3eb4 100644 --- a/.github/workflows/manual-cdn.yml +++ b/.github/workflows/manual-cdn.yml @@ -25,7 +25,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout sources - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Get yarn cache directory path id: yarn-cache-dir-path run: @@ -44,7 +44,7 @@ jobs: node-version: lts/* - name: Install upload-to-cdn dependencies if: ${{ inputs.version }} - run: corepack yarn workspaces focus @uppy-dev/upload-to-cdn + run: corepack yarn workspaces focus uppy env: # https://docs.cypress.io/guides/references/advanced-installation#Skipping-installation CYPRESS_INSTALL_BINARY: 0 @@ -59,7 +59,7 @@ jobs: run: corepack yarn run build - name: Upload "${{ inputs.package }}" to CDN if: ${{ !inputs.force }} - run: corepack yarn run uploadcdn "$PACKAGE" "$VERSION" + run: corepack yarn workspace uppy node upload-to-cdn.js "$PACKAGE" "$VERSION" env: PACKAGE: ${{inputs.package}} VERSION: ${{inputs.version}} @@ -67,7 +67,7 @@ jobs: EDGLY_SECRET: ${{secrets.EDGLY_SECRET}} - name: Upload "${{ inputs.package }}" to CDN if: ${{ inputs.force }} - run: corepack yarn run uploadcdn "$PACKAGE" "$VERSION" -- --force + run: corepack yarn workspace uppy node upload-to-cdn.js "$PACKAGE" "$VERSION" --force env: PACKAGE: ${{inputs.package}} VERSION: ${{inputs.version}} diff --git a/.github/workflows/release-candidate.yml b/.github/workflows/release-candidate.yml deleted file mode 100644 index 4d587a556..000000000 --- a/.github/workflows/release-candidate.yml +++ /dev/null @@ -1,94 +0,0 @@ -name: Release candidate -on: - push: - branches: release - -env: - YARN_ENABLE_GLOBAL_CACHE: false - -jobs: - prepare-release: - name: Prepare release candidate Pull Request - runs-on: ubuntu-latest - steps: - - name: Checkout sources - uses: actions/checkout@v4 - with: - branch: release - - name: Rebase - run: | - git fetch origin HEAD --depth=1 - git config --global user.email "actions@github.com" - git config --global user.name "GitHub Actions" - git rebase FETCH_HEAD - - name: Get yarn cache directory path - id: yarn-cache-dir-path - run: - echo "dir=$(corepack yarn config get cacheFolder)" >> $GITHUB_OUTPUT - - - uses: actions/cache@v4 - id: yarn-cache # use this to check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`) - with: - path: ${{ steps.yarn-cache-dir-path.outputs.dir }} - key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} - restore-keys: | - ${{ runner.os }}-yarn- - - name: Install Node.js - uses: actions/setup-node@v4 - with: - node-version: lts/* - - name: Install dependencies - run: corepack yarn install --immutable - env: - # https://docs.cypress.io/guides/references/advanced-installation#Skipping-installation - CYPRESS_INSTALL_BINARY: 0 - - name: Bump candidate packages version - run: corepack yarn version apply --all --json | jq -s > releases.json - - name: Prepare changelog - run: - corepack yarn workspace @uppy-dev/release update-changelogs - releases.json | xargs git add - - name: Update contributors table - run: - corepack yarn contributors:save && corepack yarn remark -foq README.md - && corepack yarn prettier -w README.md && git add README.md - - name: Update CDN URLs - run: - corepack yarn workspace @uppy-dev/release update-version-URLs | xargs - git add - - name: Stage changes and remove temp files - run: | - git rm -rf .yarn/versions - git rm CHANGELOG.next.md - jq -r 'map(.cwd) | join("\n")' < releases.json | awk '{ print "git add " $0 "/package.json" }' | sh - - name: Commit - run: | - echo "Release: uppy@$(jq -r 'map(select(.ident == "uppy"))[0].newVersion' < releases.json)" > commitMessage - echo >> commitMessage - echo "This is a release candidate for the following packages:" >> commitMessage - echo >> commitMessage - jq -r 'map("- `"+.ident+"`: "+.oldVersion+" -> "+.newVersion) | join("\n") ' < releases.json >> commitMessage - git commit -n --amend --file commitMessage - - name: Open Pull Request - id: pr_opening - run: | - git push origin HEAD:release-candidate - gh api repos/${{ github.repository }}/pulls \ - -F base="$(gh api /repos/${{ github.repository }} | jq -r .default_branch)" \ - -F head="release-candidate" \ - -F title="$(head -1 commitMessage)" \ - -F body="$(git --no-pager diff HEAD^ -- CHANGELOG.md | awk '{ if( substr($0,0,1) == "+" && $1 != "+##" && $1 != "+Released:" && $1 != "+++" ) { print substr($0,2) } }')" \ - --jq '.number | tostring | "pr_number="+.' >> $GITHUB_OUTPUT - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - name: Assign to the releaser - run: - echo '{"assignees":[${{ toJSON(github.actor) }}]}' | gh api repos/${{ - github.repository }}/issues/${{ steps.pr_opening.outputs.pr_number - }}/assignees --input - - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - name: Enable Release workflow - run: gh workflow enable Release --repo ${{ github.repository }} - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 4a681e7c5..f96b793c8 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,149 +1,99 @@ -name: Release -on: - pull_request_review: - types: [submitted] +name: release -env: - YARN_ENABLE_GLOBAL_CACHE: false +on: + push: + branches: + - main + +concurrency: ${{ github.workflow }}-${{ github.ref }} jobs: release: - name: Publish releases - if: - ${{ github.event.review.state == 'approved' && github.event.sender.login - == github.event.pull_request.assignee.login && - github.event.pull_request.head.ref == 'release-candidate' }} - outputs: - companionWasReleased: - ${{ steps.checkIfCompanionWasReleased.outputs.version }} + name: Release runs-on: ubuntu-latest + outputs: + companionWasReleased: ${{ steps.checkIfCompanionWasReleased.outputs.version }} + published: ${{ steps.changesets.outputs.published }} steps: - - name: Checkout sources - uses: actions/checkout@v4 + - name: Checkout Repo + uses: actions/checkout@v5 with: fetch-depth: 2 - - name: Get yarn cache directory path - id: yarn-cache-dir-path - run: - echo "dir=$(corepack yarn config get cacheFolder)" >> $GITHUB_OUTPUT - - uses: actions/cache@v4 - id: yarn-cache # use this to check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`) - with: - path: ${{ steps.yarn-cache-dir-path.outputs.dir }} - key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} - restore-keys: | - ${{ runner.os }}-yarn- - - name: Install Node.js + - name: Setup Node.js uses: actions/setup-node@v4 with: node-version: lts/* + + # Allow yarn to make changes during release + - run: corepack yarn config set enableHardenedMode false + - run: corepack yarn --mode=update-lockfile + - name: Install dependencies run: corepack yarn install --immutable - env: - # https://docs.cypress.io/guides/references/advanced-installation#Skipping-installation - CYPRESS_INSTALL_BINARY: 0 - - name: Get CHANGELOG diff - run: - git --no-pager diff HEAD^ -- CHANGELOG.md | awk '{ if( substr($0,0,1) - == "+" && $1 != "+##" && $1 != "+Released:" && $1 != "+++" ) { print - substr($0,2) } }' > CHANGELOG.diff.md - - name: Copy README for `uppy` package - run: cp README.md packages/uppy/. - - name: Build before publishing - run: corepack yarn run build - - name: Hack to allow the publish of the Angular package + + - name: Build + run: corepack yarn build + + - name: '@uppy/angular prepublish' run: corepack yarn workspace @uppy/angular prepublishOnly - - name: Publish to the npm registry - run: - corepack yarn workspaces foreach --all --no-private npm publish - --access public --tolerate-republish + + - run: | + echo '' >> .yarnrc.yml + echo 'npmAuthToken: "${NPM_TOKEN}"' >> .yarnrc.yml + + - name: Create Release Pull Request or Publish + id: changesets + uses: changesets/action@v1 + with: + version: corepack yarn run version + publish: corepack yarn run release + commit: '[ci] release' + title: '[ci] release' env: - YARN_NPM_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} - - name: Merge PR - id: merge + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + NPM_TOKEN: ${{ secrets.NPM_TOKEN }} + + - name: Remove npmAuthToken from yarnrc run: | - gh api -X PUT repos/${{ github.repository }}/pulls/${{ github.event.pull_request.number }}/merge \ - -F merge_method="squash" \ - -F commit_message="$(cat CHANGELOG.diff.md)" \ - --jq 'if .merged then "sha="+.sha else error("not merged") end' >> $GITHUB_OUTPUT - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - name: Create tags - run: | - git --no-pager diff --name-only HEAD^ | awk '$0 ~ /^packages\/.+\/package\.json$/ { print "jq -r '"'"'\"gh api /repos/{owner}/{repo}/git/refs -f ref=\\\"refs/tags/\"+.name+\"@\"+.version+\"\\\" -f sha=${{ steps.merge.outputs.sha }}\"'"'"' < " $0 }' > createTags.sh - cat createTags.sh - sh createTags.sh | sh - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - name: Get Uppy version number - id: uppyVersion - run: - jq -r '"version="+.version' < packages/uppy/package.json >> - $GITHUB_OUTPUT - - name: Create GitHub release - run: - gh release create uppy@${{ steps.uppyVersion.outputs.version }} -t - "Uppy ${{ steps.uppyVersion.outputs.version }}" -F CHANGELOG.diff.md - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - name: Upload `uppy` to CDN - run: corepack yarn run uploadcdn uppy - env: - EDGLY_KEY: ${{secrets.EDGLY_KEY}} - EDGLY_SECRET: ${{secrets.EDGLY_SECRET}} - - name: Upload `@uppy/locales` to CDN if it was released - run: - git diff --exit-code --quiet HEAD^ -- - packages/@uppy/locales/package.json ||corepack yarn run uploadcdn - @uppy/locales - env: - EDGLY_KEY: ${{secrets.EDGLY_KEY}} - EDGLY_SECRET: ${{secrets.EDGLY_SECRET}} + sed -i '/npmAuthToken:/d' .yarnrc.yml + - name: Check if Companion was released id: checkIfCompanionWasReleased - run: - git diff --exit-code --quiet HEAD^ -- - packages/@uppy/companion/package.json || echo "version=$(jq -r - .version < packages/@uppy/companion/package.json)" >> $GITHUB_OUTPUT - - name: Remove release-candidate branch - run: - gh api -X DELETE repos/${{ github.repository - }}/git/refs/heads/release-candidate || echo "Already deleted" + if: steps.changesets.outputs.published == 'true' + run: | + git diff --exit-code --quiet HEAD^ -- packages/@uppy/companion/package.json || echo "version=$(jq -r .version < packages/@uppy/companion/package.json)" >> $GITHUB_OUTPUT + + - name: Upload `uppy` to CDN + if: steps.changesets.outputs.published == 'true' + run: node packages/uppy/upload-to-cdn.js uppy env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - name: Remove release branch - run: - gh api -X DELETE repos/${{ github.repository }}/git/refs/heads/release + EDGLY_KEY: ${{secrets.EDGLY_KEY}} + EDGLY_SECRET: ${{secrets.EDGLY_SECRET}} + + - name: Upload `@uppy/locales` to CDN if it was released + if: steps.changesets.outputs.published == 'true' + run: node packages/uppy/upload-to-cdn.js @uppy/locales env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - name: Disable Release workflow - run: gh workflow disable Release --repo ${{ github.repository }} - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - name: In case of failure - if: ${{ failure() }} - run: - gh pr comment ${{ github.event.pull_request.number }} --body "Release - job failed, please take action." - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + EDGLY_KEY: ${{secrets.EDGLY_KEY}} + EDGLY_SECRET: ${{secrets.EDGLY_SECRET}} + # See also companion-deploy.yml docker: name: DockerHub needs: release - if: ${{ needs.release.outputs.companionWasReleased }} + if: ${{ needs.release.outputs.published == 'true' && needs.release.outputs.companionWasReleased }} runs-on: ubuntu-latest env: DOCKER_BUILDKIT: 0 COMPOSE_DOCKER_CLI_BUILD: 0 steps: - name: Checkout sources - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Docker meta id: docker_meta - uses: docker/metadata-action@8e5442c4ef9f78752691e2d8f8d19755c6f78e81 # v5.5.1 + uses: docker/metadata-action@902fa8ec7d6ecbf8d84d538b9b233a880e428804 # v5.7.0 with: images: transloadit/companion tags: | @@ -151,15 +101,15 @@ jobs: type=semver,pattern={{version}},value=${{ needs.release.outputs.companionWasReleased }} # set latest tag for default branch type=raw,value=latest,enable=true - - uses: docker/setup-qemu-action@49b3bc8e6bdd4a60e6116a5414239cba5943d3cf # v3.2.0 + - uses: docker/setup-qemu-action@29109295f81e9208d7d86ff1c6c12d2833863392 # v3.6.0 - uses: docker/setup-buildx-action@v3 - name: Log in to DockerHub - uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0 + uses: docker/login-action@184bdaa0721073962dff0199f1fb9940f07167d1 # v3.5.0 with: username: ${{secrets.DOCKER_USERNAME}} password: ${{secrets.DOCKER_PASSWORD}} - name: Build and push - uses: docker/build-push-action@5cd11c3a4ced054e52742c5fd54dca954e0edd85 # v6.7.0 + uses: docker/build-push-action@471d1dc4e07e5cdedd4c2171150001c434f0b7a4 # v6.15.0 with: push: true context: . diff --git a/.gitignore b/.gitignore index 8abab34b1..219287d2e 100644 --- a/.gitignore +++ b/.gitignore @@ -16,9 +16,14 @@ yarn-error.log .env tsconfig.tsbuildinfo tsconfig.build.tsbuildinfo +.svelte-kit +.turbo +__screenshots__ dist/ lib/ +# @uppy/svelte needs lib inside src +!src/lib coverage/ examples/dev/bundle.js examples/aws-php/vendor/* diff --git a/.npmrc b/.npmrc new file mode 100644 index 000000000..ae643592e --- /dev/null +++ b/.npmrc @@ -0,0 +1 @@ +//registry.npmjs.org/:_authToken=${NPM_TOKEN} diff --git a/.prettierignore b/.prettierignore deleted file mode 100644 index d8404c980..000000000 --- a/.prettierignore +++ /dev/null @@ -1,9 +0,0 @@ -node_modules/ -*.js -*.jsx -*.cjs -*.mjs -!private/js2ts/* -!examples/svelte-example/* -*.lock -CHANGELOG.md diff --git a/.prettierrc.js b/.prettierrc.js deleted file mode 100644 index a64dbe40c..000000000 --- a/.prettierrc.js +++ /dev/null @@ -1,29 +0,0 @@ -/** @type {import("prettier").Config} */ -module.exports = { - proseWrap: 'always', - singleQuote: true, - trailingComma: 'all', - semi: false, - experimentalTernaries: true, - overrides: [ - { - files: 'packages/@uppy/angular/**', - options: { - semi: true, - }, - }, - { - files: 'docs/**', - options: { - semi: true, - useTabs: true, - }, - }, - { - files: ['tsconfig.json'], - options: { - parser: 'jsonc', - }, - }, - ], -} diff --git a/.remarkignore b/.remarkignore deleted file mode 100644 index 2995e88d0..000000000 --- a/.remarkignore +++ /dev/null @@ -1,8 +0,0 @@ -website/src/_posts/201* -website/src/_posts/2020-* -website/src/_posts/2021-0* -examples/ -CHANGELOG.md -CHANGELOG.next.md -BACKLOG.md -node_modules/ diff --git a/.stylelintrc.json b/.stylelintrc.json deleted file mode 100644 index 3d05d89b9..000000000 --- a/.stylelintrc.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "extends": [ - "stylelint-config-standard", - "stylelint-config-standard-scss", - "stylelint-config-rational-order" - ], - "rules": { - "at-rule-no-unknown": null, - "scss/at-rule-no-unknown": true - }, - "defaultSeverity": "warning" -} diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 000000000..699ed7331 --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,3 @@ +{ + "recommendations": ["biomejs.biome"] +} diff --git a/.vscode/uppy.code-workspace b/.vscode/uppy.code-workspace index 918de6b36..472f46c9c 100644 --- a/.vscode/uppy.code-workspace +++ b/.vscode/uppy.code-workspace @@ -1,29 +1,23 @@ { "folders": [ { - "path": "..", - }, + "path": ".." + } ], "settings": { + "editor.defaultFormatter": "biomejs.biome", "workbench.colorCustomizations": { "titleBar.activeForeground": "#ffffff", - "titleBar.activeBackground": "#ff009d", + "titleBar.activeBackground": "#ff009d" }, "search.exclude": { - "website/public/": true, "node_modules/": true, - "website/node_modules/": true, "dist/": true, "lib/": true, - "package-lock.json": true, - "website/package-lock.json": true, "yarn-error.log": true, - "website/.deploy_git": true, "npm-debug.log": true, - "website/npm-debug.log": true, - "website/debug.log": true, "nohup.out": true, - "yarn.lock": true, - }, - }, + "yarn.lock": true + } + } } diff --git a/.vscode/uppy.code-workspace.bak b/.vscode/uppy.code-workspace.bak deleted file mode 100644 index 2aea22309..000000000 --- a/.vscode/uppy.code-workspace.bak +++ /dev/null @@ -1,29 +0,0 @@ -{ - "folders": [ - { - "path": "." - } - ], - "settings": { - "workbench.colorCustomizations": { - "titleBar.activeForeground": "#ffffff", - "titleBar.activeBackground": "#ff009d", - }, - "search.exclude": { - "website/public/": true, - "node_modules/": true, - "website/node_modules/": true, - "dist/": true, - "lib/": true, - "package-lock.json": true, - "website/package-lock.json": true, - "yarn-error.log": true, - "website/.deploy_git": true, - "npm-debug.log": true, - "website/npm-debug.log": true, - "website/debug.log": true, - "nohup.out": true, - "yarn.lock": true - } - } -} diff --git a/.yarn/patches/@changesets-cli-npm-2.29.5-68d8030bf3.patch b/.yarn/patches/@changesets-cli-npm-2.29.5-68d8030bf3.patch new file mode 100644 index 000000000..efc57a61d --- /dev/null +++ b/.yarn/patches/@changesets-cli-npm-2.29.5-68d8030bf3.patch @@ -0,0 +1,52 @@ +diff --git a/dist/changesets-cli.cjs.js b/dist/changesets-cli.cjs.js +index 82ed39a6b2a38fe0aaabb71c2ec745c502dbe06a..c779498212942c5ee822501a5630c3755e335db8 100644 +--- a/dist/changesets-cli.cjs.js ++++ b/dist/changesets-cli.cjs.js +@@ -634,9 +634,8 @@ async function getPublishTool(cwd) { + const pm = await packageManagerDetector.detect({ + cwd + }); +- if (!pm || pm.name !== "pnpm") return { +- name: "npm" +- }; ++ if (!pm) return { name: "npm" }; ++ if (pm.name === "yarn") return { name: "yarn" }; + try { + let result = await spawn__default["default"]("pnpm", ["--version"], { + cwd +@@ -771,6 +770,9 @@ async function internalPublish(packageJson, opts, twoFactorState) { + } = publishTool.name === "pnpm" ? await spawn__default["default"]("pnpm", ["publish", "--json", ...publishFlags], { + env: Object.assign({}, process.env, envOverride), + cwd: opts.cwd ++ }) : publishTool.name === "yarn" ? await spawn__default["default"]("yarn", ["npm", "publish", ...publishFlags], { ++ env: Object.assign({}, process.env, envOverride), ++ cwd: opts.cwd, + }) : await spawn__default["default"](publishTool.name, ["publish", opts.publishDir, "--json", ...publishFlags], { + env: Object.assign({}, process.env, envOverride) + }); +diff --git a/dist/changesets-cli.esm.js b/dist/changesets-cli.esm.js +index 1e945455b39b9c6424b26960b3d26a095ad980e4..3a7339db5d9d1ffac16c0ef5a7d56a9d506483a4 100644 +--- a/dist/changesets-cli.esm.js ++++ b/dist/changesets-cli.esm.js +@@ -596,9 +596,8 @@ async function getPublishTool(cwd) { + const pm = await detect({ + cwd + }); +- if (!pm || pm.name !== "pnpm") return { +- name: "npm" +- }; ++ if (!pm) return { name: "npm" }; ++ if (pm.name === "yarn") return { name: "yarn" }; + try { + let result = await spawn$1("pnpm", ["--version"], { + cwd +@@ -733,6 +732,9 @@ async function internalPublish(packageJson, opts, twoFactorState) { + } = publishTool.name === "pnpm" ? await spawn$1("pnpm", ["publish", "--json", ...publishFlags], { + env: Object.assign({}, process.env, envOverride), + cwd: opts.cwd ++ }) : publishTool.name === "yarn" ? await spawn$1("yarn", ["npm", "publish", ...publishFlags], { ++ env: Object.assign({}, process.env, envOverride), ++ cwd: opts.cwd, + }) : await spawn$1(publishTool.name, ["publish", opts.publishDir, "--json", ...publishFlags], { + env: Object.assign({}, process.env, envOverride) + }); diff --git a/.yarn/patches/@vitest-utils-npm-1.2.1-3028846845.patch b/.yarn/patches/@vitest-utils-npm-1.2.1-3028846845.patch deleted file mode 100644 index b1bfff5d3..000000000 --- a/.yarn/patches/@vitest-utils-npm-1.2.1-3028846845.patch +++ /dev/null @@ -1,16 +0,0 @@ -diff --git a/dist/error.d.ts b/dist/error.d.ts -index bd657d5311ff3d255dc57a2f7224301d532a3177..8924e0982c64c566f4d9808cde62292d7ed334ac 100644 ---- a/dist/error.d.ts -+++ b/dist/error.d.ts -@@ -1,9 +1,9 @@ - import { D as DiffOptions } from './types-widbdqe5.js'; - import 'pretty-format'; - --declare function serializeError(val: any, seen?: WeakMap): any; -+declare function serializeError(val: any, seen?: WeakMap): any; - declare function processError(err: any, diffOptions?: DiffOptions): any; --declare function replaceAsymmetricMatcher(actual: any, expected: any, actualReplaced?: WeakSet, expectedReplaced?: WeakSet): { -+declare function replaceAsymmetricMatcher(actual: any, expected: any, actualReplaced?: WeakSet, expectedReplaced?: WeakSet): { - replacedActual: any; - replacedExpected: any; - }; diff --git a/.yarn/patches/p-queue-npm-8.0.1-fe1ddcd827.patch b/.yarn/patches/p-queue-npm-8.0.1-fe1ddcd827.patch deleted file mode 100644 index 200198f91..000000000 --- a/.yarn/patches/p-queue-npm-8.0.1-fe1ddcd827.patch +++ /dev/null @@ -1,12 +0,0 @@ -diff --git a/package.json b/package.json -index d4133284745349488cf0e623b983c78fd7cc7fb7..52433fa0ecea99ebbfe5b94a8023f73350896f7d 100644 ---- a/package.json -+++ b/package.json -@@ -6,6 +6,7 @@ - "repository": "sindresorhus/p-queue", - "funding": "https://github.com/sponsors/sindresorhus", - "type": "module", -+ "main": "./dist/index.js", - "exports": { - "types": "./dist/index.d.ts", - "default": "./dist/index.js" diff --git a/.yarn/patches/pre-commit-npm-1.2.2-f30af83877.patch b/.yarn/patches/pre-commit-npm-1.2.2-f30af83877.patch deleted file mode 100644 index 2e5dbd70e..000000000 --- a/.yarn/patches/pre-commit-npm-1.2.2-f30af83877.patch +++ /dev/null @@ -1,34 +0,0 @@ -diff --git a/index.js b/index.js -index a20646d922945004cb737918ef6b6d063bb3c2a4..a44863e9555abdaa569f309b1197fddc8dd244a5 100644 ---- a/index.js -+++ b/index.js -@@ -147,7 +147,7 @@ Hook.prototype.log = function log(lines, exit) { - * @api private - */ - Hook.prototype.initialize = function initialize() { -- ['git', 'npm'].forEach(function each(binary) { -+ ['git', 'corepack'].forEach(function each(binary) { - try { this[binary] = which.sync(binary); } - catch (e) {} - }, this); -@@ -159,9 +159,9 @@ Hook.prototype.initialize = function initialize() { - if (!this.npm) { - try { - process.env.PATH += path.delimiter + path.dirname(process.env._); -- this.npm = which.sync('npm'); -+ this.npm = which.sync('corepack'); - } catch (e) { -- return this.log(this.format(Hook.log.binary, 'npm'), 0); -+ return this.log(this.format(Hook.log.binary, 'corepack'), 0); - } - } - -@@ -225,7 +225,7 @@ Hook.prototype.run = function runner() { - // this doesn't have the required `isAtty` information that libraries use to - // output colors resulting in script output that doesn't have any color. - // -- spawn(hooked.npm, ['run', script, '--silent'], { -+ spawn(hooked.npm, ['yarn', script], { - env: process.env, - cwd: hooked.root, - stdio: [0, 1, 2] diff --git a/.yarn/patches/preact-npm-10.10.0-dd04de05e8.patch b/.yarn/patches/preact-npm-10.10.0-dd04de05e8.patch deleted file mode 100644 index 08aba7da0..000000000 --- a/.yarn/patches/preact-npm-10.10.0-dd04de05e8.patch +++ /dev/null @@ -1,60 +0,0 @@ -diff --git a/debug/package.json b/debug/package.json -index 054944f5478a0a5cf7b6b8791950c595f956157b..06a4fe2719605eb42c5ee795101c21cfd10b59ce 100644 ---- a/debug/package.json -+++ b/debug/package.json -@@ -9,6 +9,7 @@ - "umd:main": "dist/debug.umd.js", - "source": "src/index.js", - "license": "MIT", -+ "type": "module", - "mangle": { - "regex": "^(?!_renderer)^_" - }, -diff --git a/devtools/package.json b/devtools/package.json -index 09b04a77690bdfba01083939ff9eaf987dd50bcb..92c159fbb3cf312c6674202085fb237d6fb921ad 100644 ---- a/devtools/package.json -+++ b/devtools/package.json -@@ -10,6 +10,7 @@ - "source": "src/index.js", - "license": "MIT", - "types": "src/index.d.ts", -+ "type": "module", - "peerDependencies": { - "preact": "^10.0.0" - }, -diff --git a/hooks/package.json b/hooks/package.json -index 74807025bf3de273ebada2cd355428a2c972503d..98501726ffbfe55ffa09928e56a9dcafb9a348ff 100644 ---- a/hooks/package.json -+++ b/hooks/package.json -@@ -10,6 +10,7 @@ - "source": "src/index.js", - "license": "MIT", - "types": "src/index.d.ts", -+ "type": "module", - "scripts": { - "build": "microbundle build --raw", - "dev": "microbundle watch --raw --format cjs", -diff --git a/jsx-runtime/package.json b/jsx-runtime/package.json -index 7a4027831223f16519a74e3028c34f2f8f5f011a..6b58d17dbacce81894467ef43c0a8e2435e388c4 100644 ---- a/jsx-runtime/package.json -+++ b/jsx-runtime/package.json -@@ -10,6 +10,7 @@ - "source": "src/index.js", - "types": "src/index.d.ts", - "license": "MIT", -+ "type": "module", - "peerDependencies": { - "preact": "^10.0.0" - }, -diff --git a/package.json b/package.json -index 60279c24a08b808ffbf7dc64a038272bddb6785d..088f35fb2c92f2e9b7248557857af2839988d1aa 100644 ---- a/package.json -+++ b/package.json -@@ -9,6 +9,7 @@ - "umd:main": "dist/preact.umd.js", - "unpkg": "dist/preact.min.js", - "source": "src/index.js", -+ "type": "module", - "exports": { - ".": { - "types": "./src/index.d.ts", diff --git a/.yarn/patches/resize-observer-polyfill-npm-1.5.1-603120e8a0.patch b/.yarn/patches/resize-observer-polyfill-npm-1.5.1-603120e8a0.patch deleted file mode 100644 index b845cd4d6..000000000 --- a/.yarn/patches/resize-observer-polyfill-npm-1.5.1-603120e8a0.patch +++ /dev/null @@ -1,19 +0,0 @@ -diff --git a/src/index.d.ts b/src/index.d.ts -index 74aacc0526ff554e9248c3f6fb44c353b5465efc..1b236d215a9db4cbc1c83f4d8bce24add202483e 100644 ---- a/src/index.d.ts -+++ b/src/index.d.ts -@@ -1,14 +1,3 @@ --interface DOMRectReadOnly { -- readonly x: number; -- readonly y: number; -- readonly width: number; -- readonly height: number; -- readonly top: number; -- readonly right: number; -- readonly bottom: number; -- readonly left: number; --} -- - declare global { - interface ResizeObserverCallback { - (entries: ResizeObserverEntry[], observer: ResizeObserver): void diff --git a/.yarn/patches/start-server-and-test-npm-1.14.0-841aa34fdf.patch b/.yarn/patches/start-server-and-test-npm-1.14.0-841aa34fdf.patch deleted file mode 100644 index 90d10064a..000000000 --- a/.yarn/patches/start-server-and-test-npm-1.14.0-841aa34fdf.patch +++ /dev/null @@ -1,13 +0,0 @@ -diff --git a/src/utils.js b/src/utils.js -index 1f636c6617a71a68318dc587a1c9e6081020f9aa..b28e840ed08f26a4eadd242a6f541fbaefea0eda 100644 ---- a/src/utils.js -+++ b/src/utils.js -@@ -112,7 +112,7 @@ const getArguments = cliArgs => { - } - - function normalizeCommand (command) { -- return UTILS.isPackageScriptName(command) ? `npm run ${command}` : command -+ return UTILS.isPackageScriptName(command) ? `corepack yarn ${command}` : command - } - - /** diff --git a/.yarn/patches/uuid-npm-8.3.2-eca0baba53.patch b/.yarn/patches/uuid-npm-8.3.2-eca0baba53.patch deleted file mode 100644 index bf1cc4723..000000000 --- a/.yarn/patches/uuid-npm-8.3.2-eca0baba53.patch +++ /dev/null @@ -1,12 +0,0 @@ -diff --git a/package.json b/package.json -index f0ab3711ee4f490cbf961ebe6283ce2a28b6824b..644235a3ef52c974e946403a3fcdd137d01fad0c 100644 ---- a/package.json -+++ b/package.json -@@ -25,6 +25,7 @@ - "require": "./dist/index.js", - "import": "./wrapper.mjs" - }, -+ "jest": "./dist/index.js", - "default": "./dist/esm-browser/index.js" - }, - "./package.json": "./package.json" diff --git a/.yarnrc.yml b/.yarnrc.yml index 5c12bc772..d2890fb76 100644 --- a/.yarnrc.yml +++ b/.yarnrc.yml @@ -8,3 +8,6 @@ compressionLevel: mixed initScope: uppy nodeLinker: node-modules + +npmPublishAccess: public + diff --git a/BACKLOG.md b/BACKLOG.md deleted file mode 100644 index 870ee90d5..000000000 --- a/BACKLOG.md +++ /dev/null @@ -1,143 +0,0 @@ -# Backlog - - - -These are ideas that are planned for specific versions or act as a backlog -without a clear date. PRs are welcome! Please do open an issue to discuss first -if it's a big feature, priorities may have changed after something was added -here. - -## `3.0.0` - -- [x] Switch to ES Modules (ESM) -- [x] @uppy/image-editor: Remove silly hack to work around non-ESM. -- [ ] Some not too breaking breaking changes. Go through TODOs (@arturi, - @aduh95, @Murderlon) -- [ ] Companion breaking changes, like S3 keys (@mifi) -- [x] New remote-sources preset -- [x] Deprecate Robodog - - [x] Remove from 3.x branch (@aduh95) - - [x] Update docs that refer to Robodog (@arturi) - - [ ] Update Transloadit.com examples and docs to use @uppy/transloadit + - @uppy/remote-sources plugins instead of @uppy/robodog (@arturi) - -## `4.0.0` - -- [ ] core: change the preprocessing --> uploading flow to allow for files to - start uploading right away after their preprocessing step has finished. - See #1738 (@goto-but-stop) -- [ ] companion: add more reliable tests to catch edge cases in companion. For - example testing that oauth works for multiple companion instances that use - a master Oauth domain. -- [ ] Consider updating the name of @uppy/aws-s3 and @uppy/aws-s3-multipart to - reflect it also supports Google Cloud Storage, Wasabi, and other cloud - providers. -- [ ] Consider fixing all locale files to follow the bcp-47 standard (nl_NL --> - nl-NL) - -## Unplanned - -### Core - -- [ ] Make sure Uppy works well in VR -- [ ] normalize file names when uploading from iOS? Can we do it with meta data? - date? `image-${index}`? #678 -- [ ] Can Uppy upload a lot of files at once? Seems to fail now: - https://github.com/transloadit/uppy/issues/3313 (@aduh95, @Murderlon) -- [ ] Consider how we can make Uppy smaller. Replace some packages with smaller - alternatives. Talk about Socket.io again (@aduh95) -- [ ] Better events — more data, consistency, naming (@Murderlon) - -### Dashboard - -- [ ] Dashboard UI should support 20 providers (@arturi) -- [ ] Allow minimizing the Dashboard during upload (Uppy then becomes just a - tiny progress indicator) (@arturi) -- [ ] Display data like image resolution on file cards. should be done by - thumbnail generator maybe #783 -- [ ] Possibility to edit/delete more than one file at once. example: add - copyrigh info to 1000 files #118, #97 -- [ ] Possibility to work on already uploaded / in progress files. We'll just - provide the `fileId` to the `file-edit-complete` event so that folks can - more easily roll out custom code for this themselves #112, #113, #2063 -- [ ] Focus jumps weirdly if you remove a file - https://github.com/transloadit/uppy/pull/2161#issuecomment-613565486 -- [ ] A mini UI that features drop & progress (may involve a `mini: true` - options for dashboard, may involve drop+progress or new plugin) (@arturi) -- [ ] Add a Load More button so you don't have to TAB endlessly to get to the - upload button (https://github.com/transloadit/uppy/issues/1419) - -### New plugins - -- [ ] WordPress Back-end plugin. Should be another Transloadit Integration based - on Robodog Dashboard(?) we should add a provider, and possibly offer - already-uploaded content -- [ ] WordPress Front-end Gravity Forms Uppy plugin so one form field could be - an Uppy-powered file input -- [ ] A WakeLock based plugin that keeps your phone from going to sleep while an - upload is ongoing https://github.com/transloadit/uppy/issues/1725 -- [ ] Improve image editor: filters for images, no crashes (@aduh95) - -### New providers - -- [ ] Google Photos (#2163) -- [ ] MediaLibrary provider which shows you files that have already been - uploaded #450, #1121, #1112 #362 -- [ ] Giphy image search (on top of Unsplash plugin) () -- [ ] Image search (via Google or Bing or DuckDuckGo): use duckduckgo-images-api - or Google Search API (@arturi) -- [ ] Vimeo #2872 - -### Miscellaneous - -- [ ] goldenretriever: make it work with aws multipart - https://community.transloadit.com/t/resumable-aws-s3-multipart-integration/14888 - (@goto-bus-stop) -- [ ] provider: add sorting (by date) #254 -- [ ] qa: add one integration test (or add to existing test) that uses more - exotic (tus) options such as `useFastRemoteRetry` or - `removeFingerprintOnSuccess` - https://github.com/transloadit/uppy/issues/1327 (@arturi, - @ifedapoolarewaju) -- [x] react: Add a React Hook to manage an Uppy instance - https://github.com/transloadit/uppy/pull/1247#issuecomment-458063951 - (@goto-bus-stop) -- [ ] rn: Uppy React Native works with Expo, now let's make it work without -- [ ] rn: Uppy React Native works with Url Plugin, now let's make it work with - Instagram -- [ ] security: consider iframe / more security for Transloadit/Uppy integration - widget and Uppy itself. Page can’t get files from Google Drive if its an - iframe -- [ ] statusbar: Add a confirmation of the cancel action - (https://github.com/transloadit/uppy/issues/1418) as well as ask the user - if they really want to navigate away while an upload is in progress via - `onbeforeunload` (@arturi) -- [ ] uploaders: consider not showing progress updates from the server after an - upload’s been paused. Perhaps the button can be disabled and say - `Pausing..` until Companion has actually stopped transmitting updates - (@arturi, @ifedapoolarewaju) -- [ ] xhr: allow sending custom headers per file (as proposed in #785) -- [ ] website: It would be nice in the long run to have a dynamic package - builder here right on the website where you can select the plugins you - need/want and it builds and downloads a minified version of them? Sort of - like jQuery UI: https://jqueryui.com/download/ -- [ ] webcam: Specify the resolution of the webcam images/video. We should add a - way to specify any custom 'constraints' (aspect ratio, resolution, - mimetype (`/video/mp4;codec=h264`), bits per second, etc) to the Webcam - plugin #876 -- [ ] Constructor to build Uppy with what you need, “Dashboard example meets - Transloadit Wizard”. Select language, modes, providers — get code ready to - use. Maybe integrate Transloadit Wizard in there as well (@arturi, - @Murderlon) - -### Needs research - -- [ ] Add a prepublish test that checks if `npm pack` is not massive - (@goto-bus-stop) -- [ ] Add https://github.com/pa11y/pa11y for automated accessibility testing? -- [ ] Add lighthouse for automated performance testing? -- [ ] Switch one existing e2e test to use Parcel (create-react-app already using - webpack) (@arturi) -- [ ] Add typescript with JSDoc for @uppy/core - https://github.com/Microsoft/TypeScript/wiki/Type-Checking-JavaScript-Files - (@arturi) diff --git a/BUNDLE-README.md b/BUNDLE-README.md index c68e0b422..e015bef67 100644 --- a/BUNDLE-README.md +++ b/BUNDLE-README.md @@ -2,7 +2,7 @@ Hi, thanks for trying out the bundled version of the Uppy File Uploader. You can use this from a CDN -(``) +(``) or bundle it with your webapp. Note that the recommended way to use Uppy is to install it with yarn/npm and use diff --git a/CHANGELOG.md b/CHANGELOG.md index 15b1344db..9de11f65f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,542 @@ Please add your entries in this format: In the current stage we aim to release a new version at least every month. +## 4.18.0 + +Released: 2025-06-30 + +| Package | Version | Package | Version | +| -------------------------- | ------- | -------------------------- | ------- | +| @uppy/components | 0.2.0 | @uppy/remote-sources | 2.3.4 | +| @uppy/core | 4.4.7 | @uppy/screen-capture | 4.3.1 | +| @uppy/google-drive-picker | 0.3.6 | @uppy/svelte | 4.5.0 | +| @uppy/google-photos-picker | 0.3.6 | @uppy/vue | 2.3.0 | +| @uppy/locales | 4.6.0 | @uppy/webcam | 4.2.1 | +| @uppy/provider-views | 4.4.5 | uppy | 4.18.0 | +| @uppy/react | 4.4.0 | | | + +- meta: Remove remark reference from CI (Murderlon) +- @uppy/components,@uppy/screen-capture: useScreenCapture fixes (Prakash / #5793) +- examples: Add useRemoteSource (Merlijn Vos / #5778) +- @uppy/components: Use webcam fixes2 (Mikael Finstad / #5791) +- meta: Remove remark (Merlijn Vos / #5790) +- meta: Delete old, unused files (Merlijn Vos / #5788) +- meta: Sort package.json (Murderlon) +- examples: Headless Hooks: Add useScreenCapture (Prakash / #5784) +- @uppy/locales: Update pt_BR localization (Gabriel Pereira / #5780) +- e2e: Skip for now then (Murderlon) +- e2e: fixup! Fix CI for now (Murderlon) +- e2e: Fix CI for now (Murderlon) +- examples: Add useWebcam (Merlijn Vos / #5741) +- @uppy/react,@uppy/svelte,@uppy/vue: Add useDropzone & useFileInput (Merlijn Vos / #5735) +- meta: build(deps): bump base-x from 3.0.9 to 3.0.11 (dependabot[bot] / #5772) +- @uppy/provider-views: improve metadata handling in Google Photos Picker (ben rosenbaum / #5769) + + +## 4.17.0 + +Released: 2025-06-02 + +| Package | Version | Package | Version | +| -------------------- | ------- | -------------------- | ------- | +| @uppy/companion | 5.8.0 | @uppy/screen-capture | 4.3.0 | +| @uppy/components | 0.1.0 | @uppy/svelte | 4.4.0 | +| @uppy/core | 4.4.6 | @uppy/utils | 6.1.5 | +| @uppy/locales | 4.5.3 | @uppy/vue | 2.2.0 | +| @uppy/provider-views | 4.4.4 | @uppy/webcam | 4.2.0 | +| @uppy/react | 4.3.0 | uppy | 4.17.0 | + +- @uppy/provider-views: fix: handle pagination for Google Photos picker (fixes #5765) (ben rosenbaum / #5768) +- @uppy/companion: add max filename length env var (Freeman / #5763) +- @uppy/core: fix missing required meta field error not updating (Prakash / #5766) +- @uppy/screen-capture: add screenshot button (Prakash / #5737) +- @uppy/locales: Update cs_CZ.ts (Martin Štorek / #5749) +- examples: Headless components (Merlijn Vos / #5727) +- @uppy/companion: fix cookie maxAge to milliseconds (zolotarov@brights.io / #5746) +- meta: document how to fix a broken release (Mikael Finstad / #5755) +- @uppy/companion: improve Zoom folder structure (Merlijn Vos / #5739) + + +## 4.16.0 + +Released: 2025-05-18 + +| Package | Version | Package | Version | +| -------------------------- | ------- | -------------------------- | ------- | +| @uppy/audio | 2.1.3 | @uppy/image-editor | 3.3.3 | +| @uppy/box | 3.2.3 | @uppy/instagram | 4.2.3 | +| @uppy/companion | 5.7.0 | @uppy/onedrive | 4.2.4 | +| @uppy/companion-client | 4.4.2 | @uppy/remote-sources | 2.3.3 | +| @uppy/core | 4.4.5 | @uppy/screen-capture | 4.2.3 | +| @uppy/dashboard | 4.3.4 | @uppy/unsplash | 4.3.4 | +| @uppy/drag-drop | 4.1.3 | @uppy/url | 4.2.4 | +| @uppy/dropbox | 4.2.3 | @uppy/utils | 6.1.4 | +| @uppy/facebook | 4.2.3 | @uppy/webcam | 4.1.3 | +| @uppy/file-input | 4.1.3 | @uppy/webdav | 0.3.3 | +| @uppy/google-drive | 4.3.3 | @uppy/zoom | 3.2.3 | +| @uppy/google-drive-picker | 0.3.5 | uppy | 4.16.0 | +| @uppy/google-photos-picker | 0.3.5 | | | + +- meta: Revert "Release: uppy@4.16.0 (#5750)" (Mikael Finstad) +- meta: force cdn upload (Mikael Finstad) +- meta: fix invalid brach option (now ref) (Mikael Finstad) +- meta: improve release script output (Mikael Finstad) +- meta: fix error (Mikael Finstad) +- meta: Release: uppy@4.16.0 (github-actions[bot] / #5750) +- meta: Fix node versions (Mikael Finstad / #5740) +- @uppy/companion-client: don't reject on incorrect origin (Mikael Finstad / #5736) +- @uppy/companion: implement credentials param `transloadit_gateway` (Mikael Finstad / #5725) +- @uppy/companion: Fix AES key wear-out (Florian Maury / #5724) +- @uppy/core: fix undefined reference when cancelling an upload (Prakash / #5730) +- @uppy/audio,@uppy/box,@uppy/core,@uppy/dashboard,@uppy/drag-drop,@uppy/dropbox,@uppy/facebook,@uppy/file-input,@uppy/google-drive-picker,@uppy/google-drive,@uppy/google-photos-picker,@uppy/image-editor,@uppy/instagram,@uppy/onedrive,@uppy/remote-sources,@uppy/screen-capture,@uppy/unsplash,@uppy/url,@uppy/utils,@uppy/webcam,@uppy/webdav,@uppy/zoom: ts: make locale strings optional (Merlijn Vos / #5728) + + +## 4.15.0 + +Released: 2025-04-14 + +| Package | Version | Package | Version | +| -------------------- | ------- | -------------------- | ------- | +| @uppy/angular | 0.8.0 | uppy | 4.15.0 | +| @uppy/provider-views | 4.4.3 | | | + +- @uppy/provider-views: Fix google photos picker (Mikael Finstad / #5717) +- meta: build(deps): bump nanoid from 3.3.7 to 5.1.2 (dependabot[bot] / #5664) +- examples: build(deps-dev): bump vite from 5.4.16 to 5.4.17 (dependabot[bot] / #5707) +- @uppy/utils: Fix type check ci (Mikael Finstad / #5714) +- @uppy/angular: Support Angular 19 (#5709) (Arnaud Flaesch / #5715) +- meta: simplify e2e (Mikael Finstad / #5711) +- meta: fix ready to commit (Mikael Finstad / #5713) + + +## 4.14.0 + +Released: 2025-04-08 + +| Package | Version | Package | Version | +| -------------------------- | ------- | -------------------------- | ------- | +| @uppy/audio | 2.1.2 | @uppy/locales | 4.5.2 | +| @uppy/box | 3.2.2 | @uppy/onedrive | 4.2.3 | +| @uppy/companion | 5.6.0 | @uppy/react | 4.2.3 | +| @uppy/core | 4.4.4 | @uppy/remote-sources | 2.3.2 | +| @uppy/dashboard | 4.3.3 | @uppy/screen-capture | 4.2.2 | +| @uppy/drag-drop | 4.1.2 | @uppy/status-bar | 4.1.3 | +| @uppy/dropbox | 4.2.2 | @uppy/transloadit | 4.2.2 | +| @uppy/facebook | 4.2.2 | @uppy/unsplash | 4.3.3 | +| @uppy/file-input | 4.1.2 | @uppy/url | 4.2.3 | +| @uppy/google-drive | 4.3.2 | @uppy/utils | 6.1.3 | +| @uppy/google-drive-picker | 0.3.4 | @uppy/webcam | 4.1.2 | +| @uppy/google-photos-picker | 0.3.4 | @uppy/webdav | 0.3.2 | +| @uppy/image-editor | 3.3.2 | @uppy/zoom | 3.2.2 | +| @uppy/instagram | 4.2.2 | uppy | 4.14.0 | + +- @uppy/core: dry retryAll() and upload() (Mikael Finstad / #5691) +- @uppy/angular: Revert "Support Angular 19" (Mikael Finstad / #5710) +- @uppy/angular: Support Angular 19 (Arnaud Flaesch / #5709) +- @uppy/companion: implement dropbox business teams (Mikael Finstad / #5708) +- @uppy/utils: add msg mimetype (Merlijn Vos / #5699) +- @uppy/core: fix locale type for plugins (Merlijn Vos / #5700) +- examples: build(deps-dev): bump vite from 5.4.14 to 5.4.15 (dependabot[bot] / #5703) +- @uppy/locales: Update nb_NO.ts (Tore Sinding Bekkedal / #5678) +- meta: build(deps): bump docker/login-action from 3.3.0 to 3.4.0 (dependabot[bot] / #5689) +- examples: @uppy-example/aws-nodejs: fix fileType not present in S3 objects (Prakash / #5680) +- @uppy/core: fix events when retrying with upload() (Prakash / #5696) +- meta: Fix locales building (Mikael Finstad / #5693) +- @uppy/google-photos: remove google photos 😢 (Mikael Finstad / #5690) +- @uppy/locales: Update cs_CZ.ts (David Petrásek / #5658) + + +## 4.13.4 + +Released: 2025-03-13 + +| Package | Version | Package | Version | +| ----------- | ------- | ----------- | ------- | +| @uppy/core | 4.4.3 | uppy | 4.13.4 | +| @uppy/react | 4.2.2 | | | + +- @uppy/core: make upload() idempotent (Merlijn Vos / #5677) +- @uppy/react: pass getServerSnapshot to useSyncExternalStoreWithSelector (Merlijn Vos / #5685) +- meta: Fix BasePlugin export for CDN bundle (Merlijn Vos / #5684) +- meta: build(deps): bump docker/build-push-action from 6.14.0 to 6.15.0 (dependabot[bot] / #5673) +- meta: build(deps): bump docker/setup-qemu-action from 3.4.0 to 3.6.0 (dependabot[bot] / #5675) +- meta: build(deps): bump docker/metadata-action from 5.6.1 to 5.7.0 (dependabot[bot] / #5674) + + +## 4.13.3 + +Released: 2025-02-25 + +| Package | Version | Package | Version | +| ---------------- | ------- | ---------------- | ------- | +| @uppy/companion | 5.5.2 | @uppy/xhr-upload | 4.3.3 | +| @uppy/dashboard | 4.3.2 | uppy | 4.13.3 | +| @uppy/status-bar | 4.1.2 | | | + +- @uppy/dashboard: do not allow drag&drop of file preview (Merlijn Vos / #5650) +- @uppy/xhr-upload: fix when responseType is set to JSON (Merlijn Vos / #5651) +- meta: build(deps): bump akhileshns/heroku-deploy from 3.13.15 to 3.14.15 (dependabot[bot] / #5659) +- meta: build(deps): bump docker/build-push-action from 6.13.0 to 6.14.0 (dependabot[bot] / #5660) +- @uppy/status-bar: fix aria-hidden warning (Merlijn Vos / #5663) +- @uppy/companion: log when tus uploaded size differs (Mikael Finstad / #5647) +- @uppy/companion: remove redundant HEAD request for file size (Mikael Finstad / #5648) +- meta: build(deps): bump elliptic from 6.6.0 to 6.6.1 (dependabot[bot] / #5649) +- examples: build(deps-dev): bump esbuild from 0.21.5 to 0.25.0 (dependabot[bot] / #5643) +- meta: build(deps-dev): bump vitest from 1.6.0 to 1.6.1 (dependabot[bot] / #5641) +- meta: build(deps): bump docker/setup-qemu-action from 3.3.0 to 3.4.0 (dependabot[bot] / #5640) + + +## 4.13.2 + +Released: 2025-02-03 + +| Package | Version | Package | Version | +| -------------------------- | ------- | -------------------------- | ------- | +| @uppy/core | 4.4.2 | @uppy/provider-views | 4.4.2 | +| @uppy/google-drive-picker | 0.3.3 | @uppy/url | 4.2.2 | +| @uppy/google-photos-picker | 0.3.3 | @uppy/utils | 6.1.2 | +| @uppy/locales | 4.5.1 | uppy | 4.13.2 | + +- @uppy/utils: do not strip www in getSocketHost (Merlijn Vos / #5621) +- @uppy/url: skip drag/dropped local files (Merlijn Vos / #5626) +- @uppy/provider-views: fix google photos picker videos (Mikael Finstad / #5635) +- @uppy/core,@uppy/google-drive-picker,@uppy/google-photos-picker,@uppy/provider-views: + fix google picker i18n (Mikael Finstad / #5632) + + +## 4.13.1 + +Released: 2025-01-22 + +| Package | Version | Package | Version | +| --------------- | ------- | --------------- | ------- | +| @uppy/aws-s3 | 4.2.3 | @uppy/tus | 4.2.2 | +| @uppy/companion | 5.5.1 | uppy | 4.13.1 | + +- @uppy/tus: fix resumeFromPreviousUpload race condition (Merlijn Vos / #5616) +- @uppy/aws-s3: Fixed default shouldUseMultipart (Mika Laitinen / #5613) +- meta: build(deps): bump docker/build-push-action from 6.11.0 to 6.12.0 (dependabot[bot] / #5611) +- @uppy/aws-s3: remove console.error (Mikael Finstad / #5607) +- @uppy/companion: unify http error responses (Mikael Finstad / #5595) + + +## 4.13.0 + +Released: 2025-01-15 + +| Package | Version | Package | Version | +| --------------- | ------- | --------------- | ------- | +| @uppy/aws-s3 | 4.2.2 | @uppy/unsplash | 4.3.2 | +| @uppy/companion | 5.5.0 | uppy | 4.13.0 | + +- @uppy/aws-s3: always set S3 meta to UppyFile & include key (Merlijn Vos / #5602) +- @uppy/companion: fix forcePathStyle boolean conversion (Mikael Finstad / #5308) +- meta: Fix Webpack CI (Merlijn Vos / #5604) +- @uppy/aws-s3: allow uploads to fail/succeed independently (Merlijn Vos / #5603) +- meta: Add types for css files (Merlijn Vos / #5591) +- @uppy/unsplash: make utmSource optional (Merlijn Vos / #5601) +- meta: build(deps): bump docker/setup-qemu-action from 3.2.0 to 3.3.0 (dependabot[bot] / #5599) +- meta: build(deps): bump docker/build-push-action from 6.10.0 to 6.11.0 (dependabot[bot] / #5600) +- @uppy/companion: add COMPANION_TUS_DEFERRED_UPLOAD_LENGTH (Dominik Schmidt / #5561) + + +## 4.12.2 + +Released: 2025-01-09 + +| Package | Version | Package | Version | +| -------------------------- | ------- | -------------------------- | ------- | +| @uppy/audio | 2.1.1 | @uppy/instagram | 4.2.1 | +| @uppy/aws-s3 | 4.2.1 | @uppy/onedrive | 4.2.2 | +| @uppy/box | 3.2.1 | @uppy/progress-bar | 4.2.1 | +| @uppy/companion-client | 4.4.1 | @uppy/provider-views | 4.4.1 | +| @uppy/compressor | 2.2.1 | @uppy/react | 4.2.1 | +| @uppy/core | 4.4.1 | @uppy/remote-sources | 2.3.1 | +| @uppy/dashboard | 4.3.1 | @uppy/screen-capture | 4.2.1 | +| @uppy/drag-drop | 4.1.1 | @uppy/status-bar | 4.1.1 | +| @uppy/drop-target | 3.1.1 | @uppy/thumbnail-generator | 4.1.1 | +| @uppy/dropbox | 4.2.1 | @uppy/transloadit | 4.2.1 | +| @uppy/facebook | 4.2.1 | @uppy/tus | 4.2.1 | +| @uppy/file-input | 4.1.1 | @uppy/unsplash | 4.3.1 | +| @uppy/form | 4.1.1 | @uppy/url | 4.2.1 | +| @uppy/golden-retriever | 4.1.1 | @uppy/vue | 2.1.1 | +| @uppy/google-drive | 4.3.1 | @uppy/webcam | 4.1.1 | +| @uppy/google-drive-picker | 0.3.2 | @uppy/webdav | 0.3.1 | +| @uppy/google-photos | 0.5.1 | @uppy/xhr-upload | 4.3.2 | +| @uppy/google-photos-picker | 0.3.2 | @uppy/zoom | 3.2.1 | +| @uppy/image-editor | 3.3.1 | uppy | 4.12.2 | +| @uppy/informer | 4.2.1 | | | + +- @uppy/provider-views: Import types consistently from @uppy/core (Merlijn Vos / #5589) +- @uppy/status-bar: fix double upload progress (Mikael Finstad / #5587) +- @uppy/provider-views: fix incorrect import (Merlijn Vos / #5588) + + +## 4.12.1 + +Released: 2025-01-08 + +| Package | Version | Package | Version | +| --------------- | ------- | --------------- | ------- | +| @uppy/companion | 5.4.1 | uppy | 4.12.1 | + +- @uppy/companion: upgrade express & express-session (Merlijn Vos / #5582) + + +## 4.12.0 + +Released: 2025-01-08 + +| Package | Version | Package | Version | +| -------------------------- | ------- | -------------------------- | ------- | +| @uppy/google-drive-picker | 0.3.1 | @uppy/unsplash | 4.3.0 | +| @uppy/google-photos-picker | 0.3.1 | @uppy/utils | 6.1.1 | +| @uppy/onedrive | 4.2.1 | @uppy/xhr-upload | 4.3.1 | +| @uppy/provider-views | 4.4.0 | uppy | 4.12.0 | +| @uppy/svelte | 4.3.0 | | | + +- @uppy/unsplash,@uppy/provider-views: add utmSource option (Merlijn Vos / #5580) +- @uppy/xhr-upload: allow custom error message in onAfterResponse (Merlijn Vos / #5578) +- @uppy/onedrive: fix AsyncStore import (Merlijn Vos / #5579) +- @uppy/google-drive-picker,@uppy/google-photos-picker: Fix Google Picker plugins locale (Merlijn Vos / #5575) + + +## 4.11.0 + +Released: 2025-01-06 + +| Package | Version | Package | Version | +| -------------------------- | ------- | -------------------------- | ------- | +| @uppy/audio | 2.1.0 | @uppy/onedrive | 4.2.0 | +| @uppy/aws-s3 | 4.2.0 | @uppy/progress-bar | 4.2.0 | +| @uppy/box | 3.2.0 | @uppy/provider-views | 4.3.0 | +| @uppy/companion-client | 4.4.0 | @uppy/react | 4.2.0 | +| @uppy/compressor | 2.2.0 | @uppy/remote-sources | 2.3.0 | +| @uppy/core | 4.4.0 | @uppy/screen-capture | 4.2.0 | +| @uppy/dashboard | 4.3.0 | @uppy/status-bar | 4.1.0 | +| @uppy/drag-drop | 4.1.0 | @uppy/store-default | 4.2.0 | +| @uppy/drop-target | 3.1.0 | @uppy/svelte | 4.2.0 | +| @uppy/dropbox | 4.2.0 | @uppy/thumbnail-generator | 4.1.0 | +| @uppy/facebook | 4.2.0 | @uppy/transloadit | 4.2.0 | +| @uppy/file-input | 4.1.0 | @uppy/tus | 4.2.0 | +| @uppy/form | 4.1.0 | @uppy/unsplash | 4.2.0 | +| @uppy/golden-retriever | 4.1.0 | @uppy/url | 4.2.0 | +| @uppy/google-drive | 4.3.0 | @uppy/utils | 6.1.0 | +| @uppy/google-drive-picker | 0.3.0 | @uppy/vue | 2.1.0 | +| @uppy/google-photos | 0.5.0 | @uppy/webcam | 4.1.0 | +| @uppy/google-photos-picker | 0.3.0 | @uppy/webdav | 0.3.0 | +| @uppy/image-editor | 3.3.0 | @uppy/xhr-upload | 4.3.0 | +| @uppy/informer | 4.2.0 | @uppy/zoom | 3.2.0 | +| @uppy/instagram | 4.2.0 | uppy | 4.11.0 | +| @uppy/locales | 4.5.0 | | | + +- meta: build(deps): bump docker/metadata-action from 5.5.1 to 5.6.1 (dependabot[bot] / #5525) +- examples,@uppy/svelte: build(deps-dev): bump @sveltejs/kit from 2.5.17 to 2.8.3 (dependabot[bot] / #5526) +- meta: build(deps): bump docker/build-push-action from 6.9.0 to 6.10.0 (dependabot[bot] / #5531) +- meta: build(deps): bump elliptic from 6.5.7 to 6.6.0 (dependabot[bot] / #5498) +- @uppy/utils: Use .js(x) for all imports instead .ts(x) (Merlijn Vos / #5573) +- @uppy/angular,@uppy/audio,@uppy/aws-s3,@uppy/box,@uppy/companion-client,@uppy/compressor,@uppy/core,@uppy/dashboard,@uppy/drag-drop,@uppy/drop-target,@uppy/dropbox,@uppy/facebook,@uppy/file-input,@uppy/form,@uppy/golden-retriever,@uppy/google-drive-picker,@uppy/google-drive,@uppy/google-photos-picker,@uppy/google-photos,@uppy/image-editor,@uppy/informer,@uppy/instagram,@uppy/locales,@uppy/onedrive,@uppy/progress-bar,@uppy/provider-views,@uppy/react,@uppy/remote-sources,@uppy/screen-capture,@uppy/status-bar,@uppy/thumbnail-generator,@uppy/transloadit,@uppy/tus,@uppy/unsplash,@uppy/url,@uppy/vue,@uppy/webcam,@uppy/webdav,@uppy/xhr-upload,@uppy/zoom: Remove "paths" from all tsconfig's (Merlijn Vos / #5572) +- @uppy/tus: fix onBeforeRequest type (Dominik Schmidt / #5566) + + +## 4.10.0 + +Released: 2025-01-06 + +| Package | Version | Package | Version | +| -------------------- | ------- | -------------------- | ------- | +| @uppy/companion | 5.4.0 | @uppy/store-redux | 4.0.2 | +| @uppy/core | 4.3.2 | @uppy/url | 4.1.3 | +| @uppy/dashboard | 4.2.0 | @uppy/webdav | 0.2.0 | +| @uppy/provider-views | 4.2.1 | uppy | 4.10.0 | +| @uppy/react | 4.1.0 | | | + +- @uppy/react: allow React 19 as peer dependency (Shubs / #5556) +- @uppy/webdav: add plugin icon (Merlijn Vos / #5555) +- @uppy/companion: pass fetched origins to window.postMessage() (Merlijn Vos / #5529) +- @uppy/core,@uppy/dashboard,@uppy/provider-views,@uppy/store-redux,@uppy/url: build(deps): bump nanoid from 5.0.7 to 5.0.9 (dependabot[bot] / #5544) + + +## 4.9.0 + +Released: 2024-12-17 + +| Package | Version | Package | Version | +| -------------------------- | ------- | -------------------------- | ------- | +| @uppy/companion | 5.3.0 | @uppy/progress-bar | 4.1.0 | +| @uppy/companion-client | 4.3.0 | @uppy/provider-views | 4.2.0 | +| @uppy/core | 4.3.1 | @uppy/status-bar | 4.0.6 | +| @uppy/google-drive-picker | 0.2.1 | @uppy/utils | 6.0.6 | +| @uppy/google-photos-picker | 0.2.1 | @uppy/webdav | 0.1.0 | +| @uppy/locales | 4.4.0 | uppy | 4.9.0 | + +- @uppy/webdav: Add @uppy/webdav (Merlijn Vos / #5551) +- @uppy/google-drive-picker,@uppy/google-photos-picker,@uppy/locales: Add missing Google Picker locale entries (Merlijn Vos / #5552) +- @uppy/core: bring back validateRestrictions (Merlijn Vos / #5538) +- @uppy/google-drive-picker,@uppy/google-photos-picker: Fix TS generics on new Google Picker plugins (Merlijn Vos / #5550) +- @uppy/locales: Add missing French locale entries (Steven SAN / #5549) +- e2e,@uppy/status-bar,@uppy/utils: Companion stream upload unknown size files (Mikael Finstad / #5489) + + +## 4.8.0 + +Released: 2024-12-05 + +| Package | Version | Package | Version | +| -------------------------- | ------- | -------------------------- | ------- | +| @uppy/audio | 2.0.2 | @uppy/instagram | 4.1.2 | +| @uppy/aws-s3 | 4.1.3 | @uppy/locales | 4.3.1 | +| @uppy/box | 3.1.2 | @uppy/onedrive | 4.1.2 | +| @uppy/companion | 5.2.0 | @uppy/progress-bar | 4.0.2 | +| @uppy/companion-client | 4.2.0 | @uppy/provider-views | 4.1.0 | +| @uppy/compressor | 2.1.1 | @uppy/react | 4.0.4 | +| @uppy/core | 4.3.0 | @uppy/remote-sources | 2.2.1 | +| @uppy/dashboard | 4.1.3 | @uppy/screen-capture | 4.1.2 | +| @uppy/drag-drop | 4.0.5 | @uppy/status-bar | 4.0.5 | +| @uppy/drop-target | 3.0.2 | @uppy/store-default | 4.1.2 | +| @uppy/dropbox | 4.1.2 | @uppy/thumbnail-generator | 4.0.2 | +| @uppy/facebook | 4.1.2 | @uppy/transloadit | 4.1.4 | +| @uppy/file-input | 4.0.4 | @uppy/tus | 4.1.5 | +| @uppy/form | 4.0.2 | @uppy/unsplash | 4.1.2 | +| @uppy/golden-retriever | 4.0.2 | @uppy/url | 4.1.2 | +| @uppy/google-drive | 4.2.0 | @uppy/utils | 6.0.5 | +| @uppy/google-drive-picker | 0.2.0 | @uppy/vue | 2.0.3 | +| @uppy/google-photos | 0.4.0 | @uppy/webcam | 4.0.3 | +| @uppy/google-photos-picker | 0.2.0 | @uppy/xhr-upload | 4.2.3 | +| @uppy/image-editor | 3.2.1 | @uppy/zoom | 3.1.2 | +| @uppy/informer | 4.1.2 | uppy | 4.8.0 | + +- @uppy/companion-client: Fix allowed origins (Mikael Finstad / #5536) +- meta: Build lib refactor to esm (Mikael Finstad / #5537) +- @uppy/provider-views: Google picker scope (Mikael Finstad / #5535) +- @uppy/core,@uppy/provider-views: move useStore out of core (Mikael Finstad / #5533) +- @uppy/companion,@uppy/google-drive-picker,@uppy/google-photos-picker: Google Picker (Mikael Finstad / #5443) +- @uppy/aws-s3: console.error instead of throw for missing etag (Merlijn Vos / #5521) +- docs: Put docs back in uppy.io repository where they belong (Merlijn Vos / #5527) +- docs: typo (Azhar Rizqullah / #5523) +- @uppy/audio,@uppy/aws-s3,@uppy/box,@uppy/companion-client,@uppy/compressor,@uppy/core,@uppy/dashboard,@uppy/drag-drop,@uppy/drop-target,@uppy/dropbox,@uppy/facebook,@uppy/file-input,@uppy/form,@uppy/golden-retriever,@uppy/google-drive,@uppy/google-photos,@uppy/image-editor,@uppy/informer,@uppy/instagram,@uppy/locales,@uppy/onedrive,@uppy/progress-bar,@uppy/provider-views,@uppy/react,@uppy/remote-sources,@uppy/screen-capture,@uppy/status-bar,@uppy/store-default,@uppy/thumbnail-generator,@uppy/transloadit,@uppy/tus,@uppy/unsplash,@uppy/url,@uppy/utils,@uppy/vue,@uppy/webcam,@uppy/xhr-upload,@uppy/zoom: cleanup tsconfig (Mikael Finstad / #5520) +- meta: fix missing lint (Mikael Finstad / #5519) +- docs: Add Next.js docs (Merlijn Vos / #5502) +- e2e: try to fix flaky test (Mikael Finstad / #5512) +- meta: Fix broken lint on CI (Mikael Finstad / #5507) + + +## 4.7.0 + +Released: 2024-11-11 + +| Package | Version | Package | Version | +| --------------- | ------- | --------------- | ------- | +| @uppy/aws-s3 | 4.1.2 | @uppy/tus | 4.1.4 | +| @uppy/companion | 5.1.4 | uppy | 4.7.0 | +| @uppy/locales | 4.3.0 | | | + +- @uppy/aws-s3: clarify and warn when incorrect buckets settings are used (Mikael Finstad / #5505) +- @uppy/tus: fix event upload-success response.body.xhr (ItsOnlyBinary / #5503) +- @uppy/companion: Enable CSRF protection in grant (OAuth2) (Mikael Finstad / #5504) +- @uppy/locales: Add ms_MY (Malay) locale (Salimi / #5488) + + +## 4.6.0 + +Released: 2024-10-31 + +| Package | Version | Package | Version | +| ------------------------- | ------- | ------------------------- | ------- | +| @uppy/aws-s3 | 4.1.1 | @uppy/provider-views | 4.0.2 | +| @uppy/box | 3.1.1 | @uppy/react | 4.0.3 | +| @uppy/companion | 5.1.3 | @uppy/react-native | 0.6.1 | +| @uppy/companion-client | 4.1.1 | @uppy/redux-dev-tools | 4.0.1 | +| @uppy/core | 4.2.3 | @uppy/screen-capture | 4.1.1 | +| @uppy/dashboard | 4.1.2 | @uppy/status-bar | 4.0.4 | +| @uppy/drag-drop | 4.0.4 | @uppy/store-default | 4.1.1 | +| @uppy/dropbox | 4.1.1 | @uppy/store-redux | 4.0.1 | +| @uppy/facebook | 4.1.1 | @uppy/svelte | 4.1.1 | +| @uppy/file-input | 4.0.3 | @uppy/thumbnail-generator | 4.0.1 | +| @uppy/form | 4.0.1 | @uppy/transloadit | 4.1.3 | +| @uppy/golden-retriever | 4.0.1 | @uppy/tus | 4.1.3 | +| @uppy/google-drive | 4.1.1 | @uppy/unsplash | 4.1.1 | +| @uppy/google-photos | 0.3.1 | @uppy/url | 4.1.1 | +| @uppy/image-editor | 3.2.0 | @uppy/utils | 6.0.4 | +| @uppy/informer | 4.1.1 | @uppy/vue | 2.0.2 | +| @uppy/instagram | 4.1.1 | @uppy/webcam | 4.0.2 | +| @uppy/locales | 4.2.1 | @uppy/xhr-upload | 4.2.2 | +| @uppy/onedrive | 4.1.1 | @uppy/zoom | 3.1.1 | +| @uppy/progress-bar | 4.0.1 | uppy | 4.6.0 | + +- @uppy/xhr-upload: fix stale file references in events (Merlijn Vos / #5499) +- @uppy/image-editor: upgrade cropperjs (Merlijn Vos / #5497) +- @uppy/aws-s3,@uppy/box,@uppy/companion-client,@uppy/core,@uppy/dashboard,@uppy/drag-drop,@uppy/dropbox,@uppy/facebook,@uppy/file-input,@uppy/form,@uppy/golden-retriever,@uppy/google-drive,@uppy/google-photos,@uppy/image-editor,@uppy/informer,@uppy/instagram,@uppy/locales,@uppy/onedrive,@uppy/progress-bar,@uppy/provider-views,@uppy/react-native,@uppy/react,@uppy/redux-dev-tools,@uppy/screen-capture,@uppy/status-bar,@uppy/store-default,@uppy/store-redux,@uppy/svelte,@uppy/thumbnail-generator,@uppy/transloadit,@uppy/tus,@uppy/unsplash,@uppy/url,@uppy/utils,@uppy/vue,@uppy/webcam,@uppy/xhr-upload,@uppy/zoom: Fix links (Anthony Veaudry / #5492) +- docs,@uppy/companion: disallow corsOrigins "*" (Mikael Finstad / #5496) + + +## 4.5.0 + +Released: 2024-10-15 + +| Package | Version | Package | Version | +| ---------------- | ------- | ---------------- | ------- | +| @uppy/companion | 5.1.2 | @uppy/svelte | 4.1.0 | +| @uppy/core | 4.2.2 | @uppy/tus | 4.1.2 | +| @uppy/dashboard | 4.1.1 | @uppy/utils | 6.0.3 | +| @uppy/drag-drop | 4.0.3 | @uppy/xhr-upload | 4.2.1 | +| @uppy/file-input | 4.0.2 | uppy | 4.5.0 | +| @uppy/locales | 4.2.0 | | | + +- @uppy/dashboard: Dashboard - convert some files to typescript (Evgenia Karunus / #5367) +- @uppy/dashboard,@uppy/drag-drop,@uppy/file-input: `.handleInputChange()` - use `.currentTarget`; clear the input using `''` (Evgenia Karunus / #5381) +- meta: build(deps): bump @blakeembrey/template from 1.1.0 to 1.2.0 (dependabot[bot] / #5448) +- @uppy/locales: Update packages/@uppy/locales/src/fr_FR.ts (Zéfyx / #5472) +- @uppy/svelte: use SvelteKit as the build tool (Merlijn Vos / #5484) +- @uppy/xhr-upload: add response to upload-error callback (Caleb Hardin / #5486) +- @uppy/tus: tus: Avoid duplicate `upload-error` event (Marius / #5485) +- @uppy/companion: Fix redis emitter (Mikael Finstad / #5474) +- meta: build(deps): bump docker/build-push-action from 6.8.0 to 6.9.0 (dependabot[bot] / #5483) + + +## 4.4.1 + +Released: 2024-09-30 + +| Package | Version | Package | Version | +| ----------------- | ------- | ----------------- | ------- | +| @uppy/core | 4.2.1 | uppy | 4.4.1 | +| @uppy/transloadit | 4.1.2 | | | + +- @uppy/transloadit: fix multiple upload batches & run again (Merlijn Vos / #5478) +- meta: build(deps): bump docker/build-push-action from 6.7.0 to 6.8.0 (dependabot[bot] / #5477) +- meta: build(deps): bump vite from 5.2.11 to 5.4.8 (dependabot[bot] / #5471) +- @uppy/svelte: build(deps-dev): bump rollup from 4.18.0 to 4.22.4 (dependabot[bot] / #5470) +- meta: build(deps): bump vite from 5.2.11 to 5.4.6 (dependabot[bot] / #5466) + + +## 4.4.0 + +Released: 2024-09-20 + +| Package | Version | Package | Version | +| ----------------- | ------- | ----------------- | ------- | +| @uppy/companion | 5.1.1 | @uppy/tus | 4.1.1 | +| @uppy/svelte | 4.0.2 | @uppy/xhr-upload | 4.2.0 | +| @uppy/transloadit | 4.1.1 | uppy | 4.4.0 | + +- @uppy/tus: fix retry check for status code 400 (Merlijn Vos / #5461) +- meta: Merge branch 'main' of https://github.com/transloadit/uppy (Murderlon) +- meta: fix AwsS3 endpoint option in private/dev (Murderlon) +- examples: build(deps): bump body-parser from 1.20.2 to 1.20.3 (dependabot[bot] / #5462) +- examples: build(deps-dev): bump vite from 5.3.1 to 5.3.6 (dependabot[bot] / #5459) +- @uppy/tus: set response from tus-js-client (Merlijn Vos / #5456) +- docs: fix assemblyOptions example for React (Merlijn Vos / #5450) +- docs: rename Edgly to Smart CDN (Merlijn Vos / #5449) +- @uppy/tus: correctly type tus on UppyFile (Merlijn Vos / #5454) +- docs: remove old legacy CDN reference (Murderlon) +- @uppy/xhr-upload: pass files to onBeforeRequest (Merlijn Vos / #5447) +- @uppy/svelte: fix generated module to not bundle Svelte (Antoine du Hamel / #5446) +- examples,@uppy/svelte: Bump svelte from 4.2.18 to 4.2.19 (dependabot[bot] / #5440) +- meta: bump Yarn to 4.4.1 (Antoine du Hamel / #5445) +- docs: fix broken links in locale docs (Serghei Cebotari / #5441) + + ## 4.3.0 Released: 2024-08-29 @@ -6154,7 +6690,7 @@ Released: 2018-02-11. - dashboard: Use more accessible tip lib microtip (#536 / @arturi) - docs: Add PHP snippets to XHRUpload docs (#567 / @goto-bus-stop) - meta: Added instruction to fork the repo first (#512 / muhammadInam) -- meta: Automatically host releases on edgly and use that as our main CDN (#558 / @kvz) +- meta: Automatically host releases on Smart CDN and use that as our main CDN (#558 / @kvz) - meta: Dependency version updates (#523 / @goto-bus-stop) - meta: Remove unused files from published package (#586 / @goto-bus-stop) - s3: Respect `limit` option for upload parameter requests too; fix isXml() check when no content-type is available (#545, #544, #528 / @goto-bus-stop) diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 000000000..eb73dda14 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,130 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Repository Overview + +Uppy is a modular JavaScript file uploader that integrates seamlessly with any application. It's built using a monorepo structure with Yarn workspaces, Turbo for build orchestration, and Biome for linting/formatting. + +## Development Commands + +### Core Commands +- `yarn build` - Build all packages (uses Turbo) +- `yarn build:watch` - Watch mode for building packages +- `yarn test` - Run tests for all packages +- `yarn test:watch` - Run tests in watch mode +- `yarn typecheck` - Type checking across all packages +- `yarn check` - Run Biome linting and formatting +- `yarn check:ci` - Run Biome in CI mode (no writes) + +### Development Server +- `yarn dev` - Start development server (from private/dev workspace) +- `yarn dev:with-companion` - Start dev server with Companion backend +- `yarn start:companion` - Start only the Companion server + +### Single Test Execution +To run tests for a specific package: +```bash +yarn workspace @uppy/[package-name] test +# Example: yarn workspace @uppy/core test +``` + +### Individual Package Development +```bash +yarn workspace @uppy/[package-name] build +``` + +## Architecture + +### Monorepo Structure +- `packages/@uppy/` - Core Uppy packages (plugins, utilities) +- `packages/uppy/` - Main bundle package +- `examples/` - Example implementations for various frameworks +- `private/` - Internal development tools + +### Core Architecture +- **Uppy Core** (`@uppy/core`) - Main class that manages plugins, state, and events +- **Plugins** - Modular components for different functionalities: + - **UI Plugins**: Dashboard, Drag & Drop, File Input, Webcam, etc. + - **Provider Plugins**: Google Drive, Dropbox, Instagram, etc. (require Companion) + - **Uploader Plugins**: Tus (resumable), XHR Upload, AWS S3, etc. + - **Utility Plugins**: Golden Retriever (recovery), Thumbnail Generator, etc. + +### Plugin Types +1. **Acquirer** - Gets files (File Input, Webcam, Google Drive) +2. **Modifier** - Modifies files (Image Editor, Compressor) +3. **Uploader** - Uploads files (Tus, XHR, AWS S3) +4. **Presenter** - Shows UI (Dashboard, Progress Bar, Status Bar) +5. **Orchestrator** - Manages workflow +6. **Logger** - Handles logging + +### Key Packages +- `@uppy/core` - Main Uppy class and plugin system +- `@uppy/dashboard` - Complete UI with file management +- `@uppy/companion` - Server-side component for remote providers +- `@uppy/companion-client` - Client for communicating with Companion +- `@uppy/utils` - Shared utilities +- `@uppy/components` - Headless Preact components (with framework wrappers) + +## Build System + +### Turbo Configuration +The build system uses Turbo (turbo.json) for task orchestration: +- Builds have dependency ordering (`dependsOn: ["^build"]`) +- TypeScript builds output to `lib/` and `dist/` directories +- CSS builds process SCSS files to CSS +- Special handling for the main `uppy` package bundle + +### TypeScript Configuration +- Shared config in `tsconfig.shared.json` +- Individual packages have `tsconfig.json` and `tsconfig.build.json` +- Build outputs include declaration files (.d.ts) and source maps + +## Code Standards + +### Linting and Formatting +- **Biome** is used for linting and formatting (replaces ESLint/Prettier) +- Configuration in `biome.json` +- Use single quotes, semicolons as needed +- 2-space indentation + +### Framework Integration +- **Preact** for UI components with framework wrappers generated for React, Vue, Svelte +- **TypeScript** throughout the codebase +- Components in `@uppy/components` use Tailwind CSS with `uppy:` prefix + +### Plugin Development +- All plugins extend `BasePlugin` or `UIPlugin` +- Plugins must have unique IDs and specify their type +- State management through Uppy's store system +- Event-driven architecture using namespace-emitter + +## Testing + +### Test Framework +- **Vitest** for unit tests (migrated from Jest) +- **Vitest Browser Mode** for browser testing (migrated from Cypress) +- Unit tests use `.test.{ts,tsx}` extensions and browser tests `.browser.test.{ts,tsx}` + +### Running Tests +- Tests run in parallel across packages via Turbo +- Examples also include tests (React, Vue, SvelteKit examples) +- Use `yarn test` for development + +## Companion Server + +The Companion server enables integration with remote file sources: +- Handles OAuth flows for providers like Google Drive, Dropbox +- Proxies file downloads to avoid CORS issues +- Provides search functionality for providers +- Located in `packages/@uppy/companion/` + +## Important Notes + +- Never commit sensitive information (API keys, tokens) +- Follow existing code conventions and patterns within each package +- Use the `@uppy/utils` package for shared functionality +- UI components should be accessible and support internationalization +- Always run `yarn check` and `yarn typecheck` before committing +- When adding a new component to `@uppy/components`, you have to run `yarn migrate:components` from root. + This is not needed for changing existing components. diff --git a/Dockerfile b/Dockerfile index dd2891d37..5ce604c25 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM node:18.17.1-alpine as build +FROM node:22.18.0-alpine AS build # Create link to node on amd64 so that corepack can find it RUN if [ "$(uname -m)" == "aarch64" ]; then mkdir -p /usr/local/sbin/ && ln -s /usr/local/bin/node /usr/local/sbin/node; fi @@ -17,7 +17,7 @@ RUN cd /app && corepack yarn workspace @uppy/companion build # Now remove all non-prod dependencies for a leaner image RUN cd /app && corepack yarn workspaces focus @uppy/companion --production -FROM node:18.17.1-alpine +FROM node:22.18.0-alpine WORKDIR /app diff --git a/Dockerfile.test b/Dockerfile.test deleted file mode 100644 index 75a5549c4..000000000 --- a/Dockerfile.test +++ /dev/null @@ -1,15 +0,0 @@ -FROM node:18.17.1-alpine - -COPY package.json /app/package.json - -WORKDIR /app - -RUN apk --update add --virtual native-dep \ - make gcc g++ python3 libgcc libstdc++ git && \ - corepack yarn install && \ - apk del native-dep -RUN apk add bash - -COPY . /app -RUN npm install -g nodemon -CMD ["npm","test"] diff --git a/Makefile b/Makefile deleted file mode 100644 index 1c69206be..000000000 --- a/Makefile +++ /dev/null @@ -1,43 +0,0 @@ -# Licensed under MIT. -# Copyright (2016) by Kevin van Zonneveld https://twitter.com/kvz -# -# https://www.npmjs.com/package/fakefile -# -# Please do not edit this file directly, but propose changed upstream instead: -# https://github.com/kvz/fakefile/blob/master/Makefile -# -# This Makefile offers convience shortcuts into any Node.js project that utilizes npm scripts. -# It functions as a wrapper around the actual listed in `package.json` -# So instead of typing: -# -# $ npm script build:assets -# -# you could also type: -# -# $ make build-assets -# -# Notice that colons (:) are replaced by dashes for Makefile compatibility. -# -# The benefits of this wrapper are: -# -# - You get to keep the the scripts package.json, which is more portable -# (Makefiles & Windows are harder to mix) -# - Offer a polite way into the project for developers coming from different -# languages (npm scripts is obviously very Node centric) -# - Profit from better autocomplete (make ) than npm currently offers. -# OSX users will have to install bash-completion -# (http://davidalger.com/development/bash-completion-on-os-x-with-brew/) - -define npm_script_targets -TARGETS := $(shell node -e 'for (var k in require("./package.json").scripts) {console.log(k.replace(/:/g, "-"));}') -$$(TARGETS): - npm run $(subst -,:,$(MAKECMDGOALS)) - -.PHONY: $$(TARGETS) -endef - -$(eval $(call npm_script_targets)) - -# These npm run scripts are available, without needing to be mentioned in `package.json` -install: - npm install diff --git a/README.md b/README.md index 2faed23c2..f53ca78bf 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,7 @@ Uppy is being developed by the folks at [Transloadit](https://transloadit.com), a versatile API to handle any file in your app. - +
TestsCI status for Uppy testsCI status for Companion testsCI status for browser tests
TestsCI status for Uppy testsCI status for Companion tests
DeploysCI status for CDN deploymentCI status for Companion deploymentCI status for website deployment
@@ -61,6 +61,22 @@ const uppy = new Uppy() **[read the docs](https://uppy.io/docs)** for more details on how to use Uppy and its plugins. +## Integrations + +Uppy has first-class support for plain JS/HTML, +[React](https://uppy.io/docs/react/), [Svelte](https://uppy.io/docs/svelte/), +[Vue](https://uppy.io/docs/vue/), and [Angular](https://uppy.io/docs/angular/). + +For the supported frameworks (except Angular) Uppy offers three ways to build user interfaces: + +1. **Pre-composed, plug-and-play components.** Mainly ``. + The downside is that you can’t customize the UI. +2. **Headless components.** Smaller components, easier to override the styles + or compose them together with your own components. +3. **Hooks.** Attach our logic to your own components, no restrictions, create a + tailor-made UI. + + ## Features - Lightweight, modular plugin-based architecture, light on dependencies :zap: @@ -87,13 +103,12 @@ npm install @uppy/core @uppy/dashboard @uppy/tus ``` Add CSS -[uppy.min.css](https://releases.transloadit.com/uppy/v4.3.0/uppy.min.css), +[uppy.min.css](https://releases.transloadit.com/uppy/v5.0.0/uppy.min.css), either to your HTML page’s `` or include in JS, if your bundler of choice supports it. -Alternatively, you can also use a pre-built bundle from Transloadit’s CDN: -Edgly. In that case `Uppy` will attach itself to the global `window.Uppy` -object. +Alternatively, you can also use a pre-built bundle from Transloadit’s CDN: Smart +CDN. In that case `Uppy` will attach itself to the global `window.Uppy` object. > ⚠️ The bundle consists of most Uppy plugins, so this method is not recommended > for production, as your users will have to download all plugins when you are @@ -102,7 +117,7 @@ object. ```html @@ -113,7 +128,7 @@ object. Uppy, Dashboard, Tus, - } from 'https://releases.transloadit.com/uppy/v4.3.0/uppy.min.mjs' + } from 'https://releases.transloadit.com/uppy/v5.0.0/uppy.min.mjs' const uppy = new Uppy() uppy.use(Dashboard, { target: '#files-drag-drop' }) @@ -139,14 +154,7 @@ object. - [`Dashboard`](https://uppy.io/docs/dashboard/) — universal UI with previews, progress bars, metadata editor and all the cool stuff. Required for most UI plugins like Webcam and Instagram -- [`Progress Bar`](https://uppy.io/docs/progress-bar/) — minimal progress bar - that fills itself when upload progresses -- [`Status Bar`](https://uppy.io/docs/status-bar/) — more detailed progress, - pause/resume/cancel buttons, percentage, speed, uploaded/total sizes (included - by default with `Dashboard`) -- [`Informer`](https://uppy.io/docs/informer/) — send notifications like “smile” - before taking a selfie or “upload failed” when all is lost (also included by - default with `Dashboard`) +- Headless components ([react](https://uppy.io/docs/react/), [svelte](https://uppy.io/docs/svelte/), [vue](https://uppy.io/docs/vue/)) ### Sources @@ -178,8 +186,6 @@ server-side component, is needed for a plugin to work. backend out there (like Apache, Nginx) - [`AWS S3`](https://uppy.io/docs/aws-s3/) — plain upload to AWS S3 or compatible services -- [`AWS S3 Multipart`](https://uppy.io/docs/aws-s3-multipart/) — S3-style - “Multipart” upload to AWS or compatible services ### File Processing @@ -195,15 +201,6 @@ server-side component, is needed for a plugin to work. image previews (included by default with `Dashboard`) - [`Form`](https://uppy.io/docs/form/) — collects metadata from `
` right before an Uppy upload, then optionally appends results back to the form -- [`Redux`](https://uppy.io/docs/redux/) — for your emerging - [time traveling](https://github.com/gaearon/redux-devtools) needs - -## React - -- [React](https://uppy.io/docs/react/) — components to integrate Uppy UI plugins - with React apps -- [React Native](./examples/react-native-expo/) — basic Uppy component for React - Native with Expo ## Browser Support @@ -296,77 +293,7 @@ Use Uppy in your project? ## Contributors - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
arturigoto-bus-stopkvzaduh95ifedapoolarewajuMurderlon
hedgerhmifinqstAJvanLoongithub-actions[bot]lakesare
dependabot[bot]kiloreuxsamuelayosadovnychyirichardwillarsajkachnic
zcallanYukeshShrjankooliverpoolBotzmcallistertyler
mokutsu-courseradschmidtDJWassinkmrbatistataoqftimodwhit
tuoxianspeltocieartim-kospaulnMikeKovariktoadkicker
dominicedenap--tranvansangLiviaMedeirosbertho-zerojuliangruber
HawxyelenalapegavboultonmejiaejAcconutjhen0409
mkabatekstephentusobencergazdaa-kriyayonahforststanislav-cervenak
sksavantogtfabernndevstudioMatthiasKunnendargmueslimanuelkiessling
johnnyperkinsofhopeyaegorzhuangyasparanoidThomasG77
subha1206schonertSlavikTraktorscottbesslerjrschumacherrosenfeld
rdimartinoahmedkandelYoussef1313allenfantasyZyclotrop-janark
bdiritodarthf1fortriebfrederikhorsheocoijarey
muhammadInamrettgerstjukakoskiolemoign5iderealbtrice
AndrwMbehnammodiBePo65bradedelmancamiloforerocommand-tab
craig-jenningsdavekissdenysdesignethanwillisfrobinsonjrichmeij
richartkeilpaescujmsandmartiuslimMartin005mskelton
mactavishzlafedogrockerjedwoodjasonboscoghasrfakhri
geertclerxJimmyLvrartrossngscherromanrobwilson1
SxDxreforaulibanezluarmreman8519pedantic-git
Pzocoppadmavilasomphillipalexanderpmusarajpedrofsplneto
patricklindsaytcgjTashowstajstrayersjauld
steverobamaituquigebowaptikSpazzMarticusszh
sergei-zelinskysebasegovia01sdebackerRattonesamuelcolburnfortunto2
GNURubMitchell8210achmiralken-kuromilannakummkopinsky
mhulethrshmauricioribeiromatthewhartstongemjesuelemattfik
mateuscruzmasum-ulumasaokmartin-brennanmarcusforsbergmarcosthejew
mperrandopascalwengerterParsaArvanehPAcryptic022Ozodbek1405leftdevel
nil1511coreprocessnicojonestrungcva10a6tnnaveed-ahmadnadeemc
pleasespammelatermarton-laszlo-attilanavruzmmogzolshahimcltmnafees
boudranetdownmosi-khamaddy-jomdxiaohumagumbo
jx-zyfkode-ninjasontixyoujur-ngjohnmanjiro13jyoungblood
green-mikegaelicwinterfrancklfingulelliotsayesdzcpy
dkisiczanzlenderolitomasyoann-hellopretvedran555tusharjkhunt
thanhthotstduhpfslawexxx44rtaiebrmoura-92rlebosse
rhymeslunttaphil714ordagoodselsevierninesalt
neuronet77craigcbrunnerweston-sankey-mark43dwnstenagyvstiig
valentinoliviallybodryityler-dot-earthtrivikrtanadeau
top-mastertvaliasektomekptomsaleebaWIStudenttmaier
Tiarhaitwarlopcodehero7386christianwengertcgoinglovecanvasbh
c0b41avallaargghalfatvagreene-courseraaduh95-test-account
sartoshi-foot-daozackbloomzlawson-utzachconneryafkariYehudaKremer
xhocquetwillycamargoonhateardeoisCommanderRootczj
cbush06Aarbelcfracspranceprattcmpsubvertallchris
charlybillaudCretezychaocellvinchungcartfiskcyu
bryanjswiftbedgerottowbaaronyoldarefbautistaemuell
EdgarSantiago93sweetrojeetissDennisKofflardhoangsvitdavilima6
akizorKaminskiDaniellCantabarmrboomerdanilatdanschalow
danmichaeloCruaiersercraigQuorafindamitporttekacs
Dogfaloalirezahiaalepisalexnjasmt3ahmadissa
adritasharmaAdrreiadityapatadiaadamvigneaultajh-sradamdottv
abannachaaron-russellsuperhawk610ajschmidt8bducharmeazizk
azeembaayhankesiciogluavneetmalhotraThe-Flashatsawinash-jc-allen
apuyouarthurdennerAbourasstyndriaanthony0030andychongyz
andrii-bodnarsuperandrew213radarherefunctinokevin-west-10xkergekacsa
firesharkstudioskaspermeinematykaroljveltenmellow-fellowjmontoyaa
jcalonsojbelejjszobodyjorgeepcjondewoojonathanarbely
jsanchez034Jokcychromacomaprofsmallpinemarc-mabeLucklj521
lucax88xlucaperretombrlouimdolphinigleleomelzer
leods92galli-leodvirylarowlanleaanthonyhoangbits
labohkip81kyleparisielkebabkidonngtheJoeBizhuydod
HussainAlkhalifahHughbertDhiromi2424giacomocerquoneroenschggjungb
geoffapplefordgabiganamfuadscodesdtrucsferdiusafgallinari
GkleinerevaepexaEnricoSottileelliotdickisoneliOcsJmales
jessica-courseravithjanwiltsjanklimojamestiotiojcjmcclean
JbithellJakubHaladejjakemcallistergaejabongJacobMGEvansmazoruss
GreenJimmyintenziveNaxYoishendywebIanVS
+![contributors table](https://contrib.rocks/image?repo=transloadit/uppy) ## License diff --git a/assets/developed-by-transloadit.png b/assets/developed-by-transloadit.png deleted file mode 100644 index cdd549344..000000000 Binary files a/assets/developed-by-transloadit.png and /dev/null differ diff --git a/assets/palette.png b/assets/palette.png deleted file mode 100644 index ab4b1a61a..000000000 Binary files a/assets/palette.png and /dev/null differ diff --git a/assets/uppy-demo-2.gif b/assets/uppy-demo-2.gif deleted file mode 100644 index 3fc1dea0f..000000000 Binary files a/assets/uppy-demo-2.gif and /dev/null differ diff --git a/assets/uppy-demo-oct-2018.gif b/assets/uppy-demo-oct-2018.gif deleted file mode 100644 index 2902fdecb..000000000 Binary files a/assets/uppy-demo-oct-2018.gif and /dev/null differ diff --git a/assets/uppy-demo-oct-2018.mov b/assets/uppy-demo-oct-2018.mov deleted file mode 100644 index 2a758a4aa..000000000 Binary files a/assets/uppy-demo-oct-2018.mov and /dev/null differ diff --git a/assets/uppy-demo-oct-2018.mov-palette.png b/assets/uppy-demo-oct-2018.mov-palette.png deleted file mode 100644 index b8af19f62..000000000 Binary files a/assets/uppy-demo-oct-2018.mov-palette.png and /dev/null differ diff --git a/assets/uppy-demo-oct-2018.mp4 b/assets/uppy-demo-oct-2018.mp4 deleted file mode 100644 index 9efad1e14..000000000 Binary files a/assets/uppy-demo-oct-2018.mp4 and /dev/null differ diff --git a/assets/uppy-demo.gif b/assets/uppy-demo.gif deleted file mode 100644 index 9b200bb1e..000000000 Binary files a/assets/uppy-demo.gif and /dev/null differ diff --git a/assets/uppy-demo.mp4 b/assets/uppy-demo.mp4 deleted file mode 100644 index 97920dbff..000000000 Binary files a/assets/uppy-demo.mp4 and /dev/null differ diff --git a/assets/uppy-demo2.mov b/assets/uppy-demo2.mov deleted file mode 100644 index 2473a9eb4..000000000 Binary files a/assets/uppy-demo2.mov and /dev/null differ diff --git a/assets/uppy-demo2.mov-palette.png b/assets/uppy-demo2.mov-palette.png deleted file mode 100644 index 5784dc20d..000000000 Binary files a/assets/uppy-demo2.mov-palette.png and /dev/null differ diff --git a/assets/uppy-demo2.mov.gif b/assets/uppy-demo2.mov.gif deleted file mode 100644 index 4a1a7cf29..000000000 Binary files a/assets/uppy-demo2.mov.gif and /dev/null differ diff --git a/assets/uppy-design.sketch b/assets/uppy-design.sketch deleted file mode 100644 index cbdbc8d36..000000000 Binary files a/assets/uppy-design.sketch and /dev/null differ diff --git a/assets/uppy-screenshot.jpg b/assets/uppy-screenshot.jpg deleted file mode 100644 index cdbe6248b..000000000 Binary files a/assets/uppy-screenshot.jpg and /dev/null differ diff --git a/babel.config.js b/babel.config.js deleted file mode 100644 index 529ea982d..000000000 --- a/babel.config.js +++ /dev/null @@ -1,26 +0,0 @@ -module.exports = (api) => { - const targets = {} - if (api.env('test')) { - targets.node = 'current' - } - - return { - presets: [ - ['@babel/preset-env', { - include: [ - '@babel/plugin-proposal-nullish-coalescing-operator', - '@babel/plugin-proposal-optional-chaining', - '@babel/plugin-proposal-numeric-separator', - ], - loose: true, - targets, - useBuiltIns: false, // Don't add polyfills automatically. - modules: false, - }], - ], - plugins: [ - ['@babel/plugin-transform-react-jsx', { pragma: 'h', pragmaFrag: 'Fragment' }], - process.env.NODE_ENV !== 'dev' && 'babel-plugin-inline-package-json', - ].filter(Boolean), - } -} diff --git a/bin/build-bundle.mjs b/bin/build-bundle.mjs deleted file mode 100644 index 19f7a63f8..000000000 --- a/bin/build-bundle.mjs +++ /dev/null @@ -1,60 +0,0 @@ -#!/usr/bin/env node - -import fs from 'node:fs/promises' -import chalk from 'chalk' - -import esbuild from 'esbuild' - -const UPPY_ROOT = new URL('../', import.meta.url) -const PACKAGES_ROOT = new URL('./packages/', UPPY_ROOT) - -function buildBundle (srcFile, bundleFile, { minify = true, standalone = '', plugins, target, format } = {}) { - return esbuild.build({ - bundle: true, - sourcemap: true, - entryPoints: [srcFile], - outfile: bundleFile, - platform: 'browser', - minify, - keepNames: target !== 'es5', - plugins, - tsconfigRaw: '{}', - target, - format, - }).then(() => { - if (minify) { - console.info(chalk.green(`✓ Built Minified Bundle [${standalone}]:`), chalk.magenta(bundleFile)) - } else { - console.info(chalk.green(`✓ Built Bundle [${standalone}]:`), chalk.magenta(bundleFile)) - } - }) -} - -await fs.mkdir(new URL('./uppy/dist', PACKAGES_ROOT), { recursive: true }) - -const methods = [ - buildBundle( - './packages/uppy/src/bundle.ts', - './packages/uppy/dist/uppy.min.mjs', - { standalone: 'Uppy (ESM)', format: 'esm' }, - ), - buildBundle( - './packages/uppy/bundle.mjs', - './packages/uppy/dist/uppy.min.js', - { standalone: 'Uppy', format: 'iife' }, - ), -] - -// Add BUNDLE-README.MD -methods.push( - fs.copyFile( - new URL('./BUNDLE-README.md', UPPY_ROOT), - new URL('./uppy/dist/README.md', PACKAGES_ROOT), - ), -) - -await Promise.all(methods).then(() => { - console.info(chalk.yellow('✓ JS bundles 🎉')) -}, (err) => { - console.error(chalk.red('✗ Error:'), chalk.red(err.message)) -}) diff --git a/bin/build-css.js b/bin/build-css.js deleted file mode 100644 index ad1460382..000000000 --- a/bin/build-css.js +++ /dev/null @@ -1,97 +0,0 @@ -const sass = require('sass') -const postcss = require('postcss') -const autoprefixer = require('autoprefixer') -const postcssLogical = require('postcss-logical') -const postcssDirPseudoClass = require('postcss-dir-pseudo-class') -const cssnano = require('cssnano') -const { promisify } = require('node:util') -const fs = require('node:fs') -const path = require('node:path') -const resolve = require('resolve') -const glob = promisify(require('glob')) - -const renderScss = promisify(sass.render) -const { mkdir, writeFile } = fs.promises - -const cwd = process.cwd() -let chalk - -function handleErr (err) { - console.error(chalk.red('✗ Error:'), chalk.red(err.message)) -} - -async function compileCSS () { - ({ default:chalk } = await import('chalk')) - const files = await glob('packages/{,@uppy/}*/src/style.scss') - - for (const file of files) { - const importedFiles = new Set() - const scssResult = await renderScss({ - file, - importer (url, from, done) { - resolve(url, { - basedir: path.dirname(from), - filename: from, - extensions: ['.scss'], - }, (err, resolved) => { - if (err) { - done(err) - return - } - - const realpath = fs.realpathSync(resolved) - - if (importedFiles.has(realpath)) { - done({ contents: '' }) - return - } - importedFiles.add(realpath) - - done({ file: realpath }) - }) - }, - }) - - const plugins = [ - autoprefixer, - postcssLogical(), - postcssDirPseudoClass(), - ] - const postcssResult = await postcss(plugins) - .process(scssResult.css, { from: file }) - postcssResult.warnings().forEach((warn) => { - console.warn(warn.toString()) - }) - - const outdir = path.join(path.dirname(file), '../dist') - // Save the `uppy` package's CSS as `uppy.css`, - // the rest as `style.css`. - // const outfile = path.join(outdir, outdir.includes(path.normalize('packages/uppy/')) ? 'uppy.css' : 'style.css') - let outfile = path.join(outdir, 'style.css') - if (outdir.includes(path.normalize('packages/uppy/'))) { - outfile = path.join(outdir, 'uppy.css') - } - await mkdir(outdir, { recursive: true }) - await writeFile(outfile, postcssResult.css) - console.info( - chalk.green('✓ Built Uppy CSS:'), - chalk.magenta(path.relative(cwd, outfile)), - ) - - const minifiedResult = await postcss([ - cssnano({ safe: true }), - ]).process(postcssResult.css, { from: outfile }) - minifiedResult.warnings().forEach((warn) => { - console.warn(warn.toString()) - }) - await writeFile(outfile.replace(/\.css$/, '.min.css'), minifiedResult.css) - console.info( - chalk.green('✓ Minified Bundle CSS:'), - chalk.magenta(path.relative(cwd, outfile).replace(/\.css$/, '.min.css')), - ) - } -} - -compileCSS().then(() => { - console.info(chalk.yellow('✓ CSS Bundles 🎉')) -}, handleErr) diff --git a/bin/build-lib.js b/bin/build-lib.js deleted file mode 100644 index e185ebb54..000000000 --- a/bin/build-lib.js +++ /dev/null @@ -1,147 +0,0 @@ -const babel = require('@babel/core') -const t = require('@babel/types') -const { promisify } = require('node:util') -const glob = promisify(require('glob')) -const fs = require('node:fs') -const path = require('node:path') - -const { mkdir, stat, writeFile } = fs.promises - -const PACKAGE_JSON_IMPORT = /^\..*\/package.json$/ -const SOURCE = 'packages/{*,@uppy/*}/src/**/*.{js,ts}?(x)' -// Files not to build (such as tests) -const IGNORE = /\.test\.jsx?$|\.test\.tsx?$|__mocks__|svelte|angular|companion\//; -// Files that should trigger a rebuild of everything on change -const META_FILES = [ - 'babel.config.js', - 'package.json', - 'package-lock.json', - 'yarn.lock', - 'bin/build-lib.js', -] - -function lastModified (file, createParentDir = false) { - return stat(file).then((s) => s.mtime, async (err) => { - if (err.code === 'ENOENT') { - if (createParentDir) { - await mkdir(path.dirname(file), { recursive: true }) - } - return 0 - } - throw err - }) -} - -const versionCache = new Map() - -async function preparePackage (file) { - const packageFolder = file.slice(0, file.indexOf('/src/')) - if (versionCache.has(packageFolder)) return - - // eslint-disable-next-line import/no-dynamic-require, global-require - const { version } = require(path.join(__dirname, '..', packageFolder, 'package.json')) - if (process.env.FRESH) { - // in case it hasn't been done before. - await mkdir(path.join(packageFolder, 'lib'), { recursive: true }) - } - versionCache.set(packageFolder, version) -} - -const nonJSImport = /^\.\.?\/.+\.([jt]sx|ts)$/ -// eslint-disable-next-line no-shadow -function rewriteNonJSImportsToJS (path) { - const match = nonJSImport.exec(path.node.source.value) - if (match) { - // eslint-disable-next-line no-param-reassign - path.node.source.value = `${match[0].slice(0, -match[1].length)}js` - } -} - -async function buildLib () { - const metaMtimes = await Promise.all(META_FILES.map((filename) => lastModified(path.join(__dirname, '..', filename)))) - const metaMtime = Math.max(...metaMtimes) - - const files = await glob(SOURCE) - /* eslint-disable no-continue */ - for (const file of files) { - if (IGNORE.test(file)) { - continue - } - await preparePackage(file) - const libFile = file.replace('/src/', '/lib/').replace(/\.[jt]sx?$/, '.js') - - // on a fresh build, rebuild everything. - if (!process.env.FRESH) { - const [srcMtime, libMtime] = await Promise.all([ - lastModified(file), - lastModified(libFile, true), - ]) - // Skip files that haven't changed - if (srcMtime < libMtime && metaMtime < libMtime) { - continue - } - } - - const plugins = [{ - visitor: { - // eslint-disable-next-line no-shadow - ImportDeclaration (path) { - rewriteNonJSImportsToJS(path) - if (PACKAGE_JSON_IMPORT.test(path.node.source.value) - && path.node.specifiers.length === 1 - && path.node.specifiers[0].type === 'ImportDefaultSpecifier') { - // Vendor-in version number from package.json files: - const version = versionCache.get(file.slice(0, file.indexOf('/src/'))) - if (version != null) { - const [{ local }] = path.node.specifiers - path.replaceWith( - t.variableDeclaration('const', [t.variableDeclarator(local, - t.objectExpression([ - t.objectProperty(t.stringLiteral('version'), t.stringLiteral(version)), - ]))]), - ) - } - } - }, - - ExportAllDeclaration: rewriteNonJSImportsToJS, - // eslint-disable-next-line no-shadow - ExportNamedDeclaration (path) { - if (path.node.source != null) { - rewriteNonJSImportsToJS(path) - } - }, - }, - }] - const isTSX = file.endsWith('.tsx') - if (isTSX || file.endsWith('.ts')) { - plugins.push(['@babel/plugin-transform-typescript', { - disallowAmbiguousJSXLike: true, - isTSX, - jsxPragma: 'h', - jsxPragmaFrag: 'Fragment', - }]) - } - - const { code, map } = await babel.transformFileAsync(file, { - sourceMaps: true, - plugins, - // no comments because https://github.com/transloadit/uppy/pull/4868#issuecomment-1897717779 - comments: !process.env.DIFF_BUILDER, - }) - const [{ default: chalk }] = await Promise.all([ - import('chalk'), - writeFile(libFile, code), - writeFile(`${libFile}.map`, JSON.stringify(map)), - ]) - console.log(chalk.green('Compiled lib:'), chalk.magenta(libFile)) - } - /* eslint-enable no-continue */ -} - -console.log('Using Babel version:', require('@babel/core/package.json').version) - -buildLib().catch((err) => { - console.error(err) - process.exit(1) -}) diff --git a/bin/to-gif-hd.sh b/bin/to-gif-hd.sh deleted file mode 100644 index 5854bbebd..000000000 --- a/bin/to-gif-hd.sh +++ /dev/null @@ -1,10 +0,0 @@ -#!/bin/sh -# Convert a video file to a gif. -# `to-gif /path/to/input.mp4 /path/to/output.gif` -palette="/tmp/to-gif-palette.png" -filters="fps=15" -ffmpeg -v warning -i $1 -vf "$filters,palettegen" -y $palette -ffmpeg -v warning -i $1 -i $palette -lavfi "$filters [x]; [x][1:v] paletteuse" -y $2 - -# resize after -# gifsicle --resize-fit-width 1000 -i animation.gif > animation-1000px.gif diff --git a/bin/to-gif-hq.sh b/bin/to-gif-hq.sh deleted file mode 100755 index d324e8eec..000000000 --- a/bin/to-gif-hq.sh +++ /dev/null @@ -1,9 +0,0 @@ -#!/bin/sh -# Convert a video file to a gif. -# `to-gif /path/to/input.mp4 /path/to/output.gif` -palette="/tmp/to-gif-palette.png" -filters="fps=15" -ffmpeg -v warning -i $1 -vf "$filters,palettegen" -y $palette -ffmpeg -v warning -i $1 -i $palette -lavfi "$filters [x]; [x][1:v] paletteuse" -y $2 - -# gifsicle --resize-fit-width 1000 -i animation.gif > animation-1000px.gif diff --git a/bin/to-gif.sh b/bin/to-gif.sh deleted file mode 100755 index a9e23bde2..000000000 --- a/bin/to-gif.sh +++ /dev/null @@ -1,32 +0,0 @@ -#!/usr/bin/env bash -set -o pipefail -set -o errexit -set -o nounset -# set -o xtrace - -# Set magic variables for current file & dir -__dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -__file="${__dir}/$(basename "${BASH_SOURCE[0]}")" -__base="$(basename ${__file} .sh)" -__root="$(cd "$(dirname "${__dir}")" && pwd)" - -width=600 -speed=0.7 -input="${__root}/assets/uppy-demo-oct-2018.mov" -base="$(basename "${input}")" -output="${__root}/assets/${base}.gif" - -ffmpeg \ - -y \ - -i "${input}" \ - -vf fps=10,scale=${width}:-1:flags=lanczos,palettegen "${__root}/assets/${base}-palette.png" - -ffmpeg \ - -y \ - -i "${input}" \ - -i "${__root}/assets/${base}-palette.png" \ - -filter_complex "setpts=${speed}*PTS,fps=10,scale=${width}:-1:flags=lanczos[x];[x][1:v]paletteuse" \ - "${output}" - -du -hs "${output}" -open -a 'Google Chrome' "${output}" diff --git a/bin/update-contributors.mjs b/bin/update-contributors.mjs deleted file mode 100755 index 8a877a494..000000000 --- a/bin/update-contributors.mjs +++ /dev/null @@ -1,65 +0,0 @@ -#!/usr/bin/env node - -import fetchContrib from 'github-contributors-list/lib/contributors.js' -import layoutStrategy from 'github-contributors-list/lib/strategies/layout_strategies/json.js' -import sortStrategy from 'github-contributors-list/lib/strategies/sort_strategies/sort_desc.js' -import filterStrategy from 'github-contributors-list/lib/strategies/filter_strategies/login.js' -import { Buffer } from 'node:buffer' -import fs from 'node:fs/promises' -import process from 'node:process' - -const README_FILE_NAME = new URL('../README.md', import.meta.url) - -const readme = await fs.open(README_FILE_NAME, 'r+') -const readmeContent = await readme.readFile() - -// Detect start of contributors section. -const START_TAG = Buffer.from('\n') -const START_TAG_POSITION = - readmeContent.indexOf(START_TAG) + START_TAG.byteLength - -const args = { - owner: 'transloadit', - repository: 'uppy', - cols: 6, - format: 'json', - sortBy: 'contributions', - sortOrder: 'desc', - filter: [], - layoutStrategy, - sortStrategy, - filterStrategy, -} -const { contributors } = await fetchContrib(args).loadAll( - args.owner, - args.repository, - args.authToken || process.env.GITHUB_API_TOKEN, - args.fromDate, -) -let cursor = START_TAG_POSITION -for (const line of contributors) { - let row = '' - for (const { html_url, login, avatar_url } of line) { - row += `` - } - row += '\n' - const { bytesWritten } = await readme.write(row, cursor, 'utf-8') - cursor += bytesWritten -} - -if (cursor === START_TAG_POSITION) { - console.log('Empty response from githubcontrib. GitHub’s rate limit?') - await readme.close() - process.exit(1) -} - -await readme.truncate(cursor) - -// Write the end of the file. -await readme.write( - readmeContent, - readmeContent.indexOf(''), - undefined, - cursor, -) -await readme.close() diff --git a/bin/update-yarn.sh b/bin/update-yarn.sh deleted file mode 100755 index 7edb8a308..000000000 --- a/bin/update-yarn.sh +++ /dev/null @@ -1,37 +0,0 @@ -#!/usr/bin/env bash - -# This script is meant to be run on a dev's machine to update the version on -# Yarn used by the monorepo. Its goal is to make sure that every mention of Yarn -# version is updated, and it re-installs the plugins to make sure those are -# up-to-date as well. - -set -o pipefail -set -o errexit -set -o nounset - -CURRENT_VERSION=$(corepack yarn --version) -LAST_VERSION=$(curl \ - -H "Accept: application/vnd.github.v3+json" \ - https://api.github.com/repos/yarnpkg/berry/releases?per_page=1 | \ - awk '{ if ($1 == "\"tag_name\":") print $2 }' | \ - sed 's#^"@yarnpkg/cli/##;s#",$##') - -[ "$CURRENT_VERSION" = "$LAST_VERSION" ] && \ - echo "Already using latest version." && \ - exit 0 - -echo "Upgrading to Yarn $LAST_VERSION (from Yarn $CURRENT_VERSION)..." - -PLUGINS=$(awk '{ if ($1 == "spec:") print $2 }' .yarnrc.yml) - -echo "$PLUGINS" | xargs -n1 -t corepack yarn plugin remove - -cp package.json .yarn/cache/tmp.package.json -sed "s#\"yarn\": \"$CURRENT_VERSION\"#\"yarn\": \"$LAST_VERSION\"#;s#\"yarn@$CURRENT_VERSION\"#\"yarn@$LAST_VERSION\"#" .yarn/cache/tmp.package.json > package.json -rm .yarn/cache/tmp.package.json - -echo "$PLUGINS" | xargs -n1 -t corepack yarn plugin import -corepack yarn - -git add package.json yarn.lock -git add .yarn/plugins diff --git a/biome.json b/biome.json new file mode 100644 index 000000000..5ccb0bae6 --- /dev/null +++ b/biome.json @@ -0,0 +1,84 @@ +{ + "$schema": "https://biomejs.dev/schemas/2.0.5/schema.json", + "vcs": { + "enabled": true, + "clientKind": "git", + "useIgnoreFile": true + }, + "files": { + "ignoreUnknown": true, + "includes": [ + "packages/**", + "examples/**", + "scripts/**", + "bin/**", + "e2e/**", + "private/**", + "!packages/**/{dist,lib}/**", + "!node_modules", + "!.svelte-kit", + "!packages/@uppy/components/src/input.css" + ] + }, + "formatter": { + "enabled": true, + "indentStyle": "space", + "formatWithErrors": true + }, + "linter": { + "enabled": true, + "includes": [ + "packages/**", + "examples/**", + "scripts/**", + "private/**", + "!packages/**/{dist,lib}/**", + "!node_modules", + "!.svelte-kit", + "!packages/@uppy/components/src/input.css", + "!**/*.vue", + "!**/*.svelte" + ], + "rules": { + "recommended": true, + "suspicious": { + "noExplicitAny": "off" + }, + "correctness": { + "useExhaustiveDependencies": "error", + "useHookAtTopLevel": "error", + "noUnusedFunctionParameters": "off", + "noUnusedVariables": { + "level": "error", + "options": { + "ignoreRestSiblings": true + } + } + }, + "style": { + "noNonNullAssertion": "off" + }, + "a11y": { + "noSvgWithoutTitle": "off" + }, + "nursery": { + "noNestedComponentDefinitions": "error", + "noReactPropAssign": "error" + } + } + }, + "javascript": { + "formatter": { + "quoteStyle": "single", + "semicolons": "asNeeded" + } + }, + "assist": { + "enabled": true, + "actions": { + "source": { + "organizeImports": "on" + } + } + } +} diff --git a/docker-compose-dev.yml b/docker-compose-dev.yml deleted file mode 100644 index 0ee79ae76..000000000 --- a/docker-compose-dev.yml +++ /dev/null @@ -1,19 +0,0 @@ -version: '3.9' - -services: - uppy: - image: transloadit/companion - build: - context: . - dockerfile: Dockerfile - environment: - - NODE_ENV=development - volumes: - - ./:/app - - /app/node_modules - - /mnt/uppy-server-data:/mnt/uppy-server-data - ports: - - '3020:3020' - command: '/app/src/standalone/start-server.js --config nodemon.json' - env_file: - - .env diff --git a/docker-compose-test.yml b/docker-compose-test.yml deleted file mode 100644 index d27b3d838..000000000 --- a/docker-compose-test.yml +++ /dev/null @@ -1,8 +0,0 @@ -version: '3.9' - -services: - uppy: - image: companion - build: - context: . - dockerfile: Dockerfile.test diff --git a/docker-compose.yml b/docker-compose.yml deleted file mode 100644 index fd6dbe4e0..000000000 --- a/docker-compose.yml +++ /dev/null @@ -1,15 +0,0 @@ -version: '3.9' - -services: - uppy: - image: transloadit/companion - build: - context: . - dockerfile: Dockerfile - volumes: - - /app/node_modules - - /mnt/uppy-server-data:/mnt/uppy-server-data - ports: - - '3020:3020' - env_file: - - .env diff --git a/docs/README.md b/docs/README.md deleted file mode 100644 index ee7415a35..000000000 --- a/docs/README.md +++ /dev/null @@ -1,4 +0,0 @@ -# Uppy documentation - -See instructions for linting this documentation and seeing this documentation in -the browser in . diff --git a/docs/companion.md b/docs/companion.md deleted file mode 100644 index e58c00a6e..000000000 --- a/docs/companion.md +++ /dev/null @@ -1,989 +0,0 @@ ---- -sidebar_position: 4 ---- - -# Companion - -Companion is an open source server application which **takes away the complexity -of authentication and the cost of downloading files from remote sources**, such -as Instagram, Google Drive, and others. Companion is a server-to-server -orchestrator that streams files from a source to a destination, and files are -never stored in Companion. Companion can run either as a standalone -(self-hosted) application, [Transloadit-hosted](#hosted), or plugged in as an -Express middleware into an existing application. The Uppy client requests remote -files from Companion, which it will download and simultaneously upload to your -[Tus server](/docs/tus), [AWS bucket](/docs/aws-s3), or any server that supports -[PUT, POST or Multipart uploads](/docs/xhr-upload). - -This means a user uploading a 5GB video from Google Drive from their phone isn’t -eating into their data plans and you don’t have to worry about implementing -OAuth. - -## When should I use it? - -If you want to let users download files from [Box][], [Dropbox][], [Facebook][], -[Google Drive][googledrive], [Google Photos][googlephotos], [Instagram][], -[OneDrive][], [Unsplash][], [Import from URL][url], or [Zoom][] — you need -Companion. - -Companion supports the same [uploaders](/docs/guides/choosing-uploader) as Uppy: -[Tus](/docs/tus), [AWS S3](/docs/aws-s3), and [regular multipart](/docs/tus). -But instead of manually setting a plugin, Uppy sends along a header with the -uploader and Companion will use the same on the server. This means if you are -using [Tus](/docs/tus) for your local uploads, you can send your remote uploads -to the same Tus server (and likewise for your AWS S3 bucket). - -:::note - -Companion only deals with _remote_ files, _local_ files are still uploaded from -the client with your upload plugin. - -::: - -## Hosted - -Using [Transloadit][] services comes with a hosted version of Companion so you -don’t have to worry about hosting your own server. Whether you are on a free or -paid Transloadit [plan](https://transloadit.com/pricing/), you can use -Companion. It’s not possible to rent a Companion server without a Transloadit -plan. - -[**Sign-up for a (free) plan**](https://transloadit.com/pricing/). - -:::tip - -Choosing Transloadit for your file services also comes with credentials for all -remote providers. This means you don’t have to waste time going through the -approval process of every app. You can still add your own credentials in the -Transloadit admin page if you want. - -::: - -:::info - -Downloading and uploading files through Companion doesn’t count towards your -[monthly quota](https://transloadit.com/docs/faq/1gb-worth/), it’s a way for -files to arrive at Transloadit servers, much like Uppy. - -::: - -To do so each provider plugin must be configured with Transloadit’s Companion -URLs: - -```js -import { COMPANION_URL, COMPANION_ALLOWED_HOSTS } from '@uppy/transloadit'; -import Dropbox from '@uppy/dropbox'; - -uppy.use(Dropbox, { - companionUrl: COMPANION_URL, - companionAllowedHosts: COMPANION_ALLOWED_HOSTS, -}); -``` - -You may also hit rate limits, because the OAuth application is shared between -everyone using Transloadit. - -To solve that, you can use your own OAuth keys with Transloadit’s hosted -Companion servers by using Transloadit Template Credentials. [Create a Template -Credential][template-credentials] on the Transloadit site. Select “Companion -OAuth” for the service, and enter the key and secret for the provider you want -to use. Then you can pass the name of the new credentials to that provider: - -```js -import { COMPANION_URL, COMPANION_ALLOWED_HOSTS } from '@uppy/transloadit'; -import Dropbox from '@uppy/dropbox'; - -uppy.use(Dropbox, { - companionUrl: COMPANION_URL, - companionAllowedHosts: COMPANION_ALLOWED_HOSTS, - companionKeysParams: { - key: 'YOUR_TRANSLOADIT_API_KEY', - credentialsName: 'my_companion_dropbox_creds', - }, -}); -``` - -## Installation & use - -Companion is installed from npm. Depending on how you want to run Companion, the -install process is slightly different. Companion can be integrated as middleware -into your [Express](https://expressjs.com/) app or as a standalone server. Most -people probably want to run it as a standalone server, while the middleware -could be used to further customise Companion or integrate it into your own HTTP -server code. - -:::note - -Since v2, you need to be running `node.js >= v10.20.1` to use Companion. More -information in the -[migrating to 2.0](/docs/guides/migration-guides/#migrate-from-uppy-1x-to-2x) -guide. - -Windows is not a supported platform right now. It may work, and we’re happy to -accept improvements in this area, but we can’t provide support. - -::: - -### Standalone mode - -You can use the standalone version if you want to run Companion as it’s own -Node.js process. It’s a configured Express server with sessions, logging, and -security best practices. First you’ll typically want to install it globally: - -```bash -npm install -g @uppy/companion -``` - -Standalone Companion will always serve HTTP (not HTTPS) and expects a reverse -proxy with SSL termination in front of it when running in production. See -[`COMPANION_PROTOCOL`](#server) for more information. - -Companion ships with an executable file (`bin/companion`) which is the -standalone server. Unlike the middleware version, options are set via -environment variables. - -:::info - -Checkout [options](#options) for the available options in JS and environment -variable formats. - -::: - -You need at least these three to get started: - -```bash -export COMPANION_SECRET="shh!Issa Secret!" -export COMPANION_DOMAIN="YOUR SERVER DOMAIN" -export COMPANION_DATADIR="PATH/TO/DOWNLOAD/DIRECTORY" -``` - -Then run: - -```bash -companion -``` - -You can also pass in the path to your JSON config file, like so: - -```bash -companion --config /path/to/companion.json -``` - -You may also want to run Companion in a process manager like -[PM2](https://pm2.keymetrics.io/) to make sure it gets restarted on upon -crashing as well as allowing scaling to many instances. - -### Express middleware mode - -First install it into your Node.js project with your favorite package manager: - -```bash -npm install @uppy/companion -``` - -To plug Companion into an existing server, call its `.app` method, passing in an -[options](#options) object as a parameter. This returns a server instance that -you can mount on a route in your Express app. - -```js -import express from 'express'; -import bodyParser from 'body-parser'; -import session from 'express-session'; -import companion from '@uppy/companion'; - -const app = express(); - -// Companion requires body-parser and express-session middleware. -// You can add it like this if you use those throughout your app. -// -// If you are using something else in your app, you can add these -// middlewares in the same subpath as Companion instead. -app.use(bodyParser.json()); -app.use(session({ secret: 'some secrety secret' })); - -const companionOptions = { - providerOptions: { - drive: { - key: 'GOOGLE_DRIVE_KEY', - secret: 'GOOGLE_DRIVE_SECRET', - }, - }, - server: { - host: 'localhost:3020', - protocol: 'http', - // Default installations normally don't need a path. - // However if you specify a `path`, you MUST specify - // the same path in `app.use()` below, - // e.g. app.use('/companion', companionApp) - // path: '/companion', - }, - filePath: '/path/to/folder/', -}; - -const { app: companionApp } = companion.app(companionOptions); -app.use(companionApp); -``` - -Companion uses WebSockets to communicate progress, errors, and successes to the -client. This is what Uppy listens to to update it’s internal state and UI. - -Add the Companion WebSocket server using the `companion.socket` function: - -```js -const server = app.listen(PORT); - -companion.socket(server); -``` - -If WebSockets fail for some reason Uppy and Companion will fallback to HTTP -polling. - -### Running many instances - -We recommend running at least two instances in production, so that if the -Node.js event loop gets blocked by one or more requests (due to a bug or spike -in traffic), it doesn’t also block or slow down all other requests as well (as -Node.js is single threaded). - -As an example for scale, one enterprise customer of Transloadit, who self-hosts -Companion to power an education service that is used by many universities -globally, deploys 7 Companion instances. Their earlier solution ran on 35 -instances. In our general experience Companion will saturate network interface -cards before other resources on commodity virtual servers (`c5d.2xlarge` for -instance). - -Your mileage may vary, so we recommend to add observability. You can let -Prometheus crawl the `/metrics` endpoint and graph that with Grafana for -instance. - -#### Using unique endpoints - -One option is to run many instances with each instance having its own unique -endpoint. This could be on separate ports, (sub)domain names, or IPs. With this -setup, you can either: - -1. Implement your own logic that will direct each upload to a specific Companion - endpoint by setting the `companionUrl` option -2. Setting the Companion option `COMPANION_SELF_ENDPOINT`. This option will - cause Companion to respond with a `i-am` HTTP header containing the value - from `COMPANION_SELF_ENDPOINT`. When Uppy’s sees this header, it will pin all - requests for the upload to this endpoint. - -In either case, you would then also typically configure a single Companion -instance (one endpoint) to handle all OAuth authentication requests, so that you -only need to specify a single OAuth callback URL. See also `oauthDomain` and -`validHosts`. - -#### Using a load balancer - -The other option is to set up a load balancer in front of many Companion -instances. Then Uppy will only see a single endpoint and send all requests to -the associated load balancer, which will then distribute them between Companion -instances. The companion instances coordinate their messages and events over -Redis so that any instance can serve the client’s requests. Note that sticky -sessions are **not** needed with this setup. Here are the requirements for this -setup: - -- The instances need to be connected to the same Redis server. -- You need to set `COMPANION_SECRET` to the same value on both servers. -- if you use the `companionKeysParams` feature (Transloadit), you also need - `COMPANION_PREAUTH_SECRET` to be the same on each instance. -- All other configuration needs to be the same, except if you’re running many - instances on the same machine, then `COMPANION_PORT` should be different for - each instance. - -## API - -### Options - -:::tip - -The headings display the JS and environment variable options (`option` -`ENV_OPTION`). When integrating Companion into your own server, you pass the -options to `companion.app()`. If you are using the standalone version, you -configure Companion using environment variables. Some options only exist as -environment variables or only as a JS option. - -::: - -
- Default configuration - -```javascript -const options = { - server: { - protocol: 'http', - path: '', - }, - providerOptions: {}, - s3: { - endpoint: 'https://{service}.{region}.amazonaws.com', - conditions: [], - useAccelerateEndpoint: false, - getKey: ({ filename }) => `${crypto.randomUUID()}-${filename}`, - expires: 800, // seconds - }, - allowLocalUrls: false, - logClientVersion: true, - periodicPingUrls: [], - streamingUpload: true, - clientSocketConnectTimeout: 60000, - metrics: true, -}; -``` - -
- -#### `filePath` `COMPANION_DATADIR` - -Full path to the directory to which provider files will be downloaded -temporarily. - -#### `secret` `COMPANION_SECRET` `COMPANION_SECRET_FILE` - -A secret string which Companion uses to generate authorization tokens. You -should generate a long random string for this. For example: - -```js -const crypto = require('node:crypto'); - -const secret = crypto.randomBytes(64).toString('hex'); -``` - -:::caution - -Omitting the `secret` in the standalone version will generate a secret for you, -using the above `crypto` string. But when integrating with Express you must -provide it yourself. This is an essential security measure. - -::: - -:::note - -Using a secret file means passing an absolute path to a file with any extension, -which has only the secret, nothing else. - -::: - -#### `preAuthSecret` `COMPANION_PREAUTH_SECRET` `COMPANION_PREAUTH_SECRET_FILE` - -If you are using the [Transloadit](/docs/transloadit) `companionKeysParams` -feature (Transloadit-hosted Companion using your own custom OAuth credentials), -set this variable to a strong randomly generated secret. See also -`COMPANION_SECRET` (but do not use the same secret!) - -:::note - -Using a secret file means passing an absolute path to a file with any extension, -which has only the secret, nothing else. - -::: - -#### `uploadUrls` `COMPANION_UPLOAD_URLS` - -An allowlist (array) of strings (exact URLs) or regular expressions. Companion -will only accept uploads to these URLs. This ensures that your Companion -instance is only allowed to upload to your trusted servers and prevents -[SSRF](https://en.wikipedia.org/wiki/Server-side_request_forgery) attacks. - -#### `COMPANION_PORT` - -The port on which to start the standalone server, defaults to 3020. This is a -standalone-only option. - -#### `COMPANION_COOKIE_DOMAIN` - -Allows you to customize the domain of the cookies created for Express sessions. -This is a standalone-only option. - -#### `COMPANION_HIDE_WELCOME` - -Setting this to `true` disables the welcome message shown at `/`. This is a -standalone-only option. - -#### `redisUrl` `COMPANION_REDIS_URL` - -URL to running Redis server. This can be used to scale Companion horizontally -using many instances. See [How to scale Companion](#how-to-scale-companion). - -#### `COMPANION_REDIS_EXPRESS_SESSION_PREFIX` - -Set a custom prefix for redis keys created by -[connect-redis](https://github.com/tj/connect-redis). Defaults to -`companion-session:`. Sessions are used for storing authentication state and for -allowing thumbnails to be loaded by the browser via Companion and for OAuth2. -See also `COMPANION_REDIS_PUBSUB_SCOPE`. - -#### `redisOptions` `COMPANION_REDIS_OPTIONS` - -An object of -[options supported by the `ioredis` client](https://github.com/redis/ioredis). -See also -[`RedisOptions`](https://github.com/redis/ioredis/blob/af832752040e616daf51621681bcb40cab965a9b/lib/redis/RedisOptions.ts#L8). - -#### `redisPubSubScope` `COMPANION_REDIS_PUBSUB_SCOPE` - -Use a scope for the companion events at the Redis server. Setting this option -will prefix all events with the name provided and a colon. See also -`COMPANION_REDIS_EXPRESS_SESSION_PREFIX`. - -#### `server` - -Configuration options for the underlying server. - -| Key / Environment variable | Value | Description | -| ---------------------------------------- | ----------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `protocol` `COMPANION_PROTOCOL` | `http` or `https` | Used to build a URL to reference the Companion instance itself, which is used for headers and cookies. Companion itself always runs as a HTTP server, so locally you should use `http`. You must to set this to `https` once you enabled SSL/HTTPS for your domain in production by running a reverse https-proxy in front of Companion, or with a built-in HTTPS feature of your hosting service. | -| `host` `COMPANION_DOMAIN` | `String` | Your server’s publicly facing hostname (for example `example.com`). | -| `oauthDomain` `COMPANION_OAUTH_DOMAIN` | `String` | If you have several instances of Companion with different (and perhaps dynamic) subdomains, you can set a single fixed subdomain and server (such as `sub1.example.com`) to handle your OAuth authentication for you. This would then redirect back to the correct instance with the required credentials on completion. This way you only need to configure a single callback URL for OAuth providers. | -| `path` `COMPANION_PATH` | `String` | The server path to where the Companion app is sitting. For instance, if Companion is at `example.com/companion`, then the path would be `/companion`). | -| `implicitPath` `COMPANION_IMPLICIT_PATH` | `String` | If the URL’s path in your reverse proxy is different from your Companion path in your express app, then you need to set this path as `implicitPath`. For instance, if your Companion URL is `example.com/mypath/companion`. Where the path `/mypath` is defined in your NGINX server, while `/companion` is set in your express app. Then you need to set the option `implicitPath` to `/mypath`, and set the `path` option to `/companion`. | -| `validHosts` `COMPANION_DOMAINS` | `Array` | If you are setting an `oauthDomain`, you need to set a list of valid hosts, so the oauth handler can validate the host of the Uppy instance requesting the authentication. This is essentially a list of valid domains running your Companion instances. The list may also contain regex patterns. e.g `['sub2.example.com', 'sub3.example.com', '(\\w+).example.com']` | - -#### `sendSelfEndpoint` `COMPANION_SELF_ENDPOINT` - -This is essentially the same as the `server.host + server.path` attributes. The -major reason for this attribute is that, when set, it adds the value as the -`i-am` header of every request response. - -#### `providerOptions` - -Object to enable providers with their keys and secrets. For example: - -```json -{ - "drive": { - "key": "***", - "secret": "***" - } -} -``` - -When using the standalone version you use the corresponding environment -variables or point to a secret file (such as `COMPANION_GOOGLE_SECRET_FILE`). - -:::note - -Secret files need an absolute path to a file with any extension which only has -the secret, nothing else. - -::: - -| Service | Key | Environment variables | -| ------------- | -------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| Box | `box` | `COMPANION_BOX_KEY`, `COMPANION_BOX_SECRET`, `COMPANION_BOX_SECRET_FILE` | -| Dropbox | `dropbox` | `COMPANION_DROPBOX_KEY`, `COMPANION_DROPBOX_SECRET`, `COMPANION_DROPBOX_SECRET_FILE` | -| Facebook | `facebook` | `COMPANION_FACEBOOK_KEY`, `COMPANION_FACEBOOK_SECRET`, `COMPANION_FACEBOOK_SECRET_FILE` | -| Google Drive | `drive` | `COMPANION_GOOGLE_KEY`, `COMPANION_GOOGLE_SECRET`, `COMPANION_GOOGLE_SECRET_FILE` | -| Google Photos | `googlephotos` | `COMPANION_GOOGLE_KEY`, `COMPANION_GOOGLE_SECRET`, `COMPANION_GOOGLE_SECRET_FILE` | -| Instagram | `instagram` | `COMPANION_INSTAGRAM_KEY`, `COMPANION_INSTAGRAM_SECRET`, `COMPANION_INSTAGRAM_SECRET_FILE` | -| OneDrive | `onedrive` | `COMPANION_ONEDRIVE_KEY`, `COMPANION_ONEDRIVE_SECRET`, `COMPANION_ONEDRIVE_SECRET_FILE`, `COMPANION_ONEDRIVE_DOMAIN_VALIDATION` (Settings this variable to `true` enables a route that can be used to validate your app with OneDrive) | -| Zoom | `zoom` | `COMPANION_ZOOM_KEY`, `COMPANION_ZOOM_SECRET`, `COMPANION_ZOOM_SECRET_FILE`, `COMPANION_ZOOM_VERIFICATION_TOKEN` | - -#### `s3` - -Companion comes with signature endpoints for AWS S3. These can be used by the -Uppy client to sign requests to upload files directly to S3, without exposing -secret S3 keys in the browser. Companion also supports uploading files from -providers like Dropbox and Instagram directly into S3. - -##### `s3.key` `COMPANION_AWS_KEY` - -The S3 access key ID. - -##### `s3.secret` `COMPANION_AWS_SECRET` `COMPANION_AWS_SECRET_FILE` - -The S3 secret access key. - -:::note - -Using a secret file means passing an absolute path to a file with any extension, -which has only the secret, nothing else. - -::: - -##### `s3.endpoint` `COMPANION_AWS_ENDPOINT` - -Optional URL to a custom S3 (compatible) service. Otherwise uses the default -from the AWS SDK. - -##### `s3.bucket` `COMPANION_AWS_BUCKET` - -The name of the bucket to store uploaded files in. - -A `string` or function that returns the name of the bucket as a `string` and -takes one argument which is an object with the following properties: - -- `filename`, the original name of the uploaded file; -- `metadata` provided by the user for the file (will only be provided during the - initial calls for each uploaded files, otherwise it will be `undefined`). -- `req`, Express.js `Request` object. Do not use any Companion internals from - the req object, as these might change in any minor version of Companion. - -#### `s3.forcePathStyle` `COMPANION_AWS_FORCE_PATH_STYLE` - -This adds support for setting the S3 client’s `forcePathStyle` option. That is -necessary to use Uppy/Companion alongside localstack in development -environments. **Default**: `false`. - -##### `s3.region` `COMPANION_AWS_REGION` - -The datacenter region where the target bucket is located. - -##### `COMPANION_AWS_PREFIX` - -An optional prefix for all uploaded keys. This is a standalone-only option. The -same can be achieved by the `getKey` option when using the express middleware. - -##### `s3.awsClientOptions` - -You can supply any -[S3 option supported by the AWS SDK](https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/S3.html#constructor-property) -in the `providerOptions.s3.awsClientOptions` object, _except for_ the below: - -- `accessKeyId`. Instead, use the `providerOptions.s3.key` property. This is to - make configuration names consistent between different Companion features. -- `secretAccessKey`. Instead, use the `providerOptions.s3.secret` property. This - is to make configuration names consistent between different Companion - features. - -Be aware that some options may cause wrong behaviour if they conflict with -Companion’s assumptions. If you find that a particular option does not work as -expected, please -[open an issue on the Uppy repository](https://github.com/transloadit/uppy/issues/new) -so we can document it here. - -##### `s3.getKey({ filename, metadata, req })` - -Get the key name for a file. The key is the file path to which the file will be -uploaded in your bucket. This option should be a function receiving three -arguments: - -- `filename`, the original name of the uploaded file; -- `metadata`, user-provided metadata for the file. -- `req`, Express.js `Request` object. Do not use any Companion internals from - the req object, as these might change in any minor version of Companion. - -This function should return a string `key`. The `req` parameter can be used to -upload to a user-specific folder in your bucket, for example: - -```js -app.use(authenticationMiddleware); -app.use( - uppy.app({ - providerOptions: { - s3: { - getKey: ({ req, filename, metadata }) => `${req.user.id}/${filename}`, - /* auth options */ - }, - }, - }), -); -``` - -The default implementation returns the `filename`, so all files will be uploaded -to the root of the bucket as their original file name. - -```js -app.use( - uppy.app({ - providerOptions: { - s3: { - getKey: ({ filename, metadata }) => filename, - }, - }, - }), -); -``` - -When signing on the client, this function will only be called for multipart -uploads. - -#### `COMPANION_AWS_USE_ACCELERATE_ENDPOINT` - -Enable S3 -[Transfer Acceleration](https://docs.aws.amazon.com/AmazonS3/latest/userguide/transfer-acceleration.html). -This is a standalone-only option. - -#### `COMPANION_AWS_EXPIRES` - -Set `X-Amz-Expires` query parameter in the presigned urls (in seconds, default: -300\). This is a standalone-only option. - -#### `COMPANION_AWS_ACL` - -Set a -[Canned ACL](https://docs.aws.amazon.com/AmazonS3/latest/dev/acl-overview.html#canned-acl) -for uploaded objects. This is a standalone-only option. - -#### `customProviders` - -This option enables you to add custom providers along with the already supported -providers. See [adding custom providers](#how-to-add-custom-providers) for more -information. - -#### `logClientVersion` - -A boolean flag to tell Companion whether to log its version upon startup. - -#### `metrics` `COMPANION_HIDE_METRICS` - -A boolean flag to tell Companion whether to provide an endpoint `/metrics` with -Prometheus metrics (by default metrics are enabled.) - -#### `streamingUpload` `COMPANION_STREAMING_UPLOAD` - -A boolean flag to tell Companion whether to enable streaming uploads. If -enabled, it will lead to _faster uploads_ because companion will start uploading -at the same time as downloading using `stream.pipe`. If `false`, files will be -fully downloaded first, then uploaded. Defaults to `true`. - -#### `maxFileSize` `COMPANION_MAX_FILE_SIZE` - -If this value is set, companion will limit the maximum file size to process. If -unset, it will process files without any size limit (this is the default). - -#### `periodicPingUrls` `COMPANION_PERIODIC_PING_URLS` - -If this value is set, companion will periodically send POST requests to the -specified URLs. Useful for keeping track of companion instances as a keep-alive. - -#### `periodicPingInterval` `COMPANION_PERIODIC_PING_INTERVAL` - -Interval for periodic ping requests (in ms). - -#### `periodicPingStaticPayload` `COMPANION_PERIODIC_PING_STATIC_JSON_PAYLOAD` - -A `JSON.stringify`-able JavaScript Object that will be sent as part of the JSON -body in the period ping requests. - -#### `allowLocalUrls` `COMPANION_ALLOW_LOCAL_URLS` - -A boolean flag to tell Companion whether to allow requesting local URLs -(non-internet IPs). - -:::caution - -Only enable this in development. **Enabling it in production is a security -risk.** - -::: - -#### `corsOrigins` (required) - -Allowed CORS Origins. Passed as the `origin` option in -[cors](https://github.com/expressjs/cors#configuration-options). - -Note this is used for both CORS’ `Access-Control-Allow-Origin` header, and for -the -[`targetOrigin`](https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage#targetorigin) -for `postMessage` calls in the context of OAuth. - -Setting it to `true` treats any origin as a trusted one, making it easier to -impersonate your brand. Setting it to `false` disables cross-origin support, use -this if you’re serving Companion and Uppy from the same domain name. - -##### `COMPANION_CLIENT_ORIGINS` - -Stand-alone alternative to the `corsOrigins` option. A comma-separated string of -origins, or `'true'` (which will be interpreted as the boolean value `true`), or -`'false'` (which will be interpreted as the boolean value `false`). -`COMPANION_CLIENT_ORIGINS_REGEX` will be ignored if this option is used. - -##### `COMPANION_CLIENT_ORIGINS_REGEX` - -:::note - -In most cases, you should not be using a regex, and instead provide the list of -accepted origins to `COMPANION_CLIENT_ORIGINS`. If you have to use this option, -have in mind that this regex will be used to parse unfiltered user input, so -make sure you’re validating the entirety of the string. - -::: - -Stand-alone alternative to the `corsOrigins` option. Like -`COMPANION_CLIENT_ORIGINS`, but allows a single regex instead. - -#### `chunkSize` `COMPANION_CHUNK_SIZE` - -Controls how big the uploaded chunks are for AWS S3 Multipart and Tus. Smaller -values lead to more overhead, but larger values lead to slower retries in case -of bad network connections. Passed to tus-js-client -[`chunkSize`](https://github.com/tus/tus-js-client/blob/master/docs/api.md#chunksize) -as well as -[AWS S3 Multipart](https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/S3.html) -`partSize`. - -#### `enableUrlEndpoint` `COMPANION_ENABLE_URL_ENDPOINT` - -Set this to `true` to enable the [URL functionalily](https://uppy.io/docs/url/). -Default: `false`. - -### Events - -The object returned by `companion.app()` also has a property `companionEmitter` -which is an `EventEmitter` that emits the following events: - -- `upload-start` - When an upload starts, this event is emitted with an object - containing the property `token`, which is a unique ID for the upload. -- **token** - The event name is the token from `upload-start`. The event has an - object with the following properties: - - `action` - One of the following strings: - - `success` - When the upload succeeds. - - `error` - When the upload fails with an error. - - `payload` - the error or success payload. - -Example code for using the `EventEmitter` to handle a finished file upload: - -```js -const companionApp = companion.app(options); -const { companionEmitter: emitter } = companionApp; - -emitter.on('upload-start', ({ token }) => { - console.log('Upload started', token); - - function onUploadEvent({ action, payload }) { - if (action === 'success') { - emitter.off(token, onUploadEvent); // avoid listener leak - console.log('Upload finished', token, payload.url); - } else if (action === 'error') { - emitter.off(token, onUploadEvent); // avoid listener leak - console.error('Upload failed', payload); - } - } - emitter.on(token, onUploadEvent); -}); -``` - - - -## Frequently asked questions - -### Do you have a live example? - -An example server is running at . - -### How does the Authentication and Token mechanism work? - -This section describes how Authentication works between Companion and Providers. -While this behaviour is the same for all Providers (Dropbox, Instagram, Google -Drive, etc.), we are going to be referring to Dropbox in place of any Provider -throughout this section. - -The following steps describe the actions that take place when a user -Authenticates and Uploads from Dropbox through Companion: - -- The visitor to a website with Uppy clicks `Connect to Dropbox`. -- Uppy sends a request to Companion, which in turn sends an OAuth request to - Dropbox (Requires that OAuth credentials from Dropbox have been added to - Companion). -- Dropbox asks the visitor to log in, and whether the Website should be allowed - to access your files -- If the visitor agrees, Companion will receive a token from Dropbox, with which - we can temporarily download files. -- Companion encrypts the token with a secret key and sends the encrypted token - to Uppy (client) -- Every time the visitor clicks on a folder in Uppy, it asks Companion for the - new list of files, with this question, the token (still encrypted by - Companion) is sent along. -- Companion decrypts the token, requests the list of files from Dropbox and - sends it to Uppy. -- When a file is selected for upload, Companion receives the token again - according to this procedure, decrypts it again, and thereby downloads the file - from Dropbox. -- As the bytes arrive, Companion uploads the bytes to the final destination - (depending on the configuration: Apache, a Tus server, S3 bucket, etc). -- Companion reports progress to Uppy, as if it were a local upload. -- Completed! - -### How to use provider redirect URIs? - -When generating your provider API keys on their corresponding developer -platforms (e.g -[Google Developer Console](https://console.developers.google.com/)), you’d need -to provide a `redirect URI` for the OAuth authorization process. In general the -redirect URI for each provider takes the format: - -`http(s)://$YOUR_COMPANION_HOST_NAME/$PROVIDER_NAME/redirect` - -For example, if your Companion server is hosted on -`https://my.companion.server.com`, then the redirect URI you would supply for -your OneDrive provider would be: - -`https://my.companion.server.com/onedrive/redirect` - -Please see -[Supported Providers](https://uppy.io/docs/companion/#Supported-providers) for a -list of all Providers and their corresponding names. - -### How to use Companion with Kubernetes? - -We have a detailed -[guide](https://github.com/transloadit/uppy/blob/main/packages/%40uppy/companion/KUBERNETES.md) -on running Companion in Kubernetes. - -### How to add custom providers? - -As of now, Companion supports the -[providers listed here](https://uppy.io/docs/companion/#Supported-providers) out -of the box, but you may also choose to add your own custom providers. You can do -this by passing the `customProviders` option when calling the Uppy `app` method. -The custom provider is expected to support Oauth 1 or 2 for -authentication/authorization. - -```javascript -import providerModule from './path/to/provider/module'; - -const options = { - customProviders: { - myprovidername: { - config: { - authorize_url: 'https://mywebsite.com/authorize', - access_url: 'https://mywebsite.com/token', - oauth: 2, - key: '***', - secret: '***', - scope: ['read', 'write'], - }, - module: providerModule, - }, - }, -}; - -uppy.app(options); -``` - -The `customProviders` option should be an object containing each custom -provider. Each custom provider would, in turn, be an object with two keys, -`config` and `module`. The `config` option would contain Oauth API settings, -while the `module` would point to the provider module. - -To work well with Companion, the **module** must be a class with the following -methods. Note that the methods must be `async`, return a `Promise` or reject -with an `Error`): - -1. `async list ({ token, directory, query })` - Returns a object containing a - list of user files (such as a list of all the files in a particular - directory). See [example returned list data structure](#list-data). `token` - - authorization token (retrieved from oauth process) to send along with your - request - - `directory` - the id/name of the directory from which data is to be - retrieved. This may be ignored if it doesn’t apply to your provider - - `query` - expressjs query params object received by the server (in case - some data you need in there). -2. `async download ({ token, id, query })` - Downloads a particular file from - the provider. Returns an object with a single property `{ stream }` - a - [`stream.Readable`](https://nodejs.org/api/stream.html#stream_class_stream_readable), - which will be read from and uploaded to the destination. To prevent memory - leaks, make sure you release your stream if you reject this method with an - error. - - `token` - authorization token (retrieved from oauth process) to send along - with your request. - - `id` - ID of the file being downloaded. - - `query` - expressjs query params object received by the server (in case - some data you need in there). -3. `async size ({ token, id, query })` - Returns the byte size of the file that - needs to be downloaded as a `Number`. If the size of the object is not known, - `null` may be returned. - - `token` - authorization token (retrieved from oauth process) to send along - with your request. - - `id` - ID of the file being downloaded. - - `query` - expressjs query params object received by the server (in case - some data you need in there). - -The class must also have: - -- A unique `static authProvider` string property - a lowercased value which - indicates name of the [`grant`](https://github.com/simov/grant) OAuth2 - provider to use (e.g `google` for Google). If your provider doesn’t use - OAuth2, you can omit this property. -- A `static` property `static version = 2`, which is the current version of the - Companion Provider API. - -See also -[example code with a custom provider](https://github.com/transloadit/uppy/blob/main/examples/custom-provider/server). - -#### list data - -```json -{ - // username or email of the user whose provider account is being accessed - "username": "johndoe", - // list of files and folders in the directory. An item is considered a folder - // if it mainly exists as a collection to contain sub-items - "items": [ - { - // boolean value of whether or NOT it's a folder - "isFolder": false, - // icon image URL - "icon": "https://random-api.url.com/fileicon.jpg", - // name of the item - "name": "myfile.jpg", - // the mime type of the item. Only relevant if the item is NOT a folder - "mimeType": "image/jpg", - // the id (in string) of the item - "id": "uniqueitemid", - // thumbnail image URL. Only relevant if the item is NOT a folder - "thumbnail": "https://random-api.url.com/filethumbnail.jpg", - // for folders this is typically the value that will be passed as "directory" in the list(...) method. - // For files, this is the value that will be passed as id in the download(...) method. - "requestPath": "file-or-folder-requestpath", - // datetime string (in ISO 8601 format) of when this item was last modified - "modifiedDate": "2020-06-29T19:59:58Z", - // the size in bytes of the item. Only relevant if the item is NOT a folder - "size": 278940, - "custom": { - // an object that may contain some more custom fields that you may need to send to the client. Only add this object if you have a need for it. - "customData1": "the value", - "customData2": "the value" - } - // more items here - } - ], - // if the "items" list is paginated, this is the request path needed to fetch the next page. - "nextPagePath": "directory-name?cursor=cursor-to-next-page" -} -``` - -### How to run Companion locally? - -1. To set up Companion for local development, please clone the Uppy repo and - install, like so: - - ```bash - git clone https://github.com/transloadit/uppy - cd uppy - yarn install - ``` - -2. Configure your environment variables by copying the `env.example.sh` file to - `env.sh` and edit it to its correct values. - - ```bash - cp .env.example .env - $EDITOR .env - ``` - -3. To start the server, run: - - ```bash - yarn run start:companion - ``` - -This would get the Companion instance running on `http://localhost:3020`. It -uses [`node --watch`](https://nodejs.org/api/cli.html#--watch) so it will -automatically restart when files are changed. - -[box]: /docs/box -[dropbox]: /docs/dropbox -[facebook]: /docs/facebook -[googledrive]: /docs/google-drive -[googlephotos]: /docs/google-photos -[instagram]: /docs/instagram -[onedrive]: /docs/onedrive -[unsplash]: /docs/unsplash -[url]: /docs/url -[zoom]: /docs/zoom -[transloadit]: https://transloadit.com -[template-credentials]: - https://transloadit.com/docs/#how-to-create-template-credentials diff --git a/docs/compressor.mdx b/docs/compressor.mdx deleted file mode 100644 index ab817db02..000000000 --- a/docs/compressor.mdx +++ /dev/null @@ -1,125 +0,0 @@ ---- -sidebar_position: 12 ---- - -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; - -import UppyCdnExample from '/src/components/UppyCdnExample'; - -# Compressor - -The `@uppy/compressor` plugin optimizes images (JPEG, PNG, and any other format -supported by the client’s browser) before upload, saving up to 60% in size -(roughly 18 MB for 10 images). It uses [Compressor.js][] library under the hood. - -## When should I use it? - -When your users are likely to upload images, potentially on mobile devices, and -saving data and faster uploads are important. - -## Install - - - - -```shell -npm install @uppy/compressor -``` - - - - - -```shell -yarn add @uppy/compressor -``` - - - - - - {` - import { Uppy, Compressor } from "{{UPPY_JS_URL}}" - const uppy = new Uppy() - uppy.use(Compressor, { - // Options - }) - `} - - - - -## Use - -```js {7} showLineNumbers -import Uppy from '@uppy/core'; -import Dashboard from '@uppy/dashboard'; -import Compressor from '@uppy/compressor'; - -new Uppy() - .use(Dashboard, {inline:true, target: '#dashboard') - .use(Compressor); -``` - -No action is needed from the user — Uppy will automatically optimize images, -show an [Informer](/docs/informer) message with saved bytes, and then begin the -upload as usual. - -## API - -### Options - -:::tip - -You can also pass any of the [Compressor.js options][] here as well. - -::: - -#### `id` - -A unique identifier for this plugin (`string`, default: `'Compressor'`). - -#### `quality` - -The quality of the output image passed to [Compressor.js][] (`number`, default: -`0.6`). - -It must be a number between `0` and `1`. Be careful to use `1` as it may make -the size of the output image become larger. In most cases, going with the -default value is best. - -:::note - -This option is only available for `image/jpeg` and `image/webp` images. - -::: - -#### `limit` - -Number of images that will be compressed in parallel (`number`, default: `10`). - -You likely don’t need to change this, unless you are experiencing performance -issues. - -#### `locale` - -```js -export default { - strings: { - // Shown in the Status Bar - compressingImages: 'Compressing images...', - compressedX: 'Saved %{size} by compressing images', - }, -}; -``` - -## Events - -#### `compressor:complete` - -The event is emitted when all files are compressed. You can use it for side -effects or custom UI notifications. - -[compressor.js]: https://github.com/fengyuanchen/compressorjs -[compressor.js options]: https://github.com/fengyuanchen/compressorjs#options diff --git a/docs/form.mdx b/docs/form.mdx deleted file mode 100644 index 1253004de..000000000 --- a/docs/form.mdx +++ /dev/null @@ -1,181 +0,0 @@ -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; - -import UppyCdnExample from '/src/components/UppyCdnExample'; - -# Form - -The `@uppy/form` plugin integrates with an existing HTML `` element to -extract input data from it, and send along with the Uppy upload. It then appends -the upload result back to the form via a hidden input. - -## When should I use this? - -When you have an existing HTML `` element and you would like to: - -- **Attach the form input values to the files.** This is useful if you want to - associate meta data from the form (for example, name, location, id) with the - uploaded file, so you can process it on the backend. `@uppy/form` extracts the - input values before uploading/processing files and adds them to Uppy meta data - state (`uppy.state.meta`), as well as and each file’s meta, and appends to the - upload in an object with `[file input name attribute]` -> `[file input value]` - key/values. -- **Upload the files and put the response (such as the file URLs) into a hidden - field** (``). Then you can POST and - handle the form yourself. The appended result is a stringified version of a - result returned from calling `uppy.upload()` or listening to `complete` event. -- **Automatically start the file upload on submit or submit the form after file - upload.** This is off by default. See [`submitOnSuccess`](#submitOnSuccess) - and [`triggerUploadOnSubmit`](#triggerUploadOnSubmit) options respectively for - details. - -:::note - -If you are using a UI framework or library like React, Vue or Svelte, you’ll -most likely handle form data there as well, and thus won’t need this plugin. -Instead, pass meta data to Uppy via [`uppy.setMeta()`](/docs/uppy#setmetadata) -and listen to [`uppy.on('complete')`](/docs/uppy#complete) to get the upload -results back. - -::: - -## Install - - - - -```shell -npm install @uppy/form -``` - - - - - -```shell -yarn add @uppy/form -``` - - - - - - {` - import { Uppy, Form } from "{{UPPY_JS_URL}}" - const uppy = new Uppy() - uppy.use(Form, { - // Options - }) - `} - - - - -## Use - -```js title="app.js" -import Uppy from '@uppy/core'; -import Form from '@uppy/form'; -import DragDrop from '@uppy/drag-drop'; - -import '@uppy/core/dist/style.min.css'; -import '@uppy/drag-drop/dist/style.min.css'; - -new Uppy().use(Form, { - target: '#my-form', -}); -``` - -```html title="index.html" - - - - - - - - - -``` - -By default the code above will: - -1. Extract meta data from the form `#my-form`. -2. Send it with the Uppy upload. -3. Those fields will then be added to Uppy meta data state (`uppy.state.meta`) - and each file’s meta, and appended as (meta)data to the upload in an object - with `[file input name attribute]` -> `[file input value]` key/values. -4. When Uppy completes upload/processing, it will add an - `` with the stringified upload result - object back to the form. - -:::note - -You can disable both of these features, see options below. - -::: - -:::tip - -`@uppy/form` can also start Uppy upload automatically once the form is -submitted, and even submit the form after the upload is complete. This is off by -default. See [`triggerUploadOnSubmit`](#triggerUploadOnSubmit) and -[`submitOnSuccess`](#submitOnSuccess) options below for details. - -::: - -## API - -### Options - -#### `id` - -A unique identifier for this plugin (`string`, default: `'Form'`). - -#### `target` - -DOM element or CSS selector for the form element (`string` or `Element`, -default: `null`). - -This is required for the plugin to work. - -#### `resultName` - -The `name` attribute for the `` where the result will be -added (`string`, default: `uppyResult`). - -#### `getMetaFromForm` - -Configures whether to extract metadata from the form (`boolean`, default: -`true`). - -When set to `true`, the Form plugin will extract all fields from a `
` -element before upload begins. Those fields will then be added to Uppy meta data -state (`uppy.state.meta`) and each file’s meta, and appended as (meta)data to -the upload in an object with `[file input name attribute]` -> -`[file input value]` key/values. - -#### `addResultToForm` - -Configures whether to add upload/encoding results back to the form in an -`` element (`boolean`, default: `true`). - -#### `triggerUploadOnSubmit` - -Configures whether to start the upload when the form is submitted (`boolean`, -default: `false`). - -When a user submits the form (via a submit button, the Enter key or -otherwise), this option will prevent form submission, and instead upload files -via Uppy. Then you could: - -- Set `submitOnSuccess: true` if you need the form to _actually_ be submitted - once all files have been uploaded. -- Listen for `uppy.on('complete')` event to do something else if the file - uploads are all you need. For example, if the form is used for file metadata - only. - -#### `submitOnSuccess` - -Configures whether to submit the form after Uppy finishes uploading/encoding -(`boolean`, default: `false`). diff --git a/docs/framework-integrations/_category_.json b/docs/framework-integrations/_category_.json deleted file mode 100644 index 7ec3e898d..000000000 --- a/docs/framework-integrations/_category_.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "label": "Framework integrations", - "position": 8 -} diff --git a/docs/framework-integrations/angular.mdx b/docs/framework-integrations/angular.mdx deleted file mode 100644 index bcb154e42..000000000 --- a/docs/framework-integrations/angular.mdx +++ /dev/null @@ -1,117 +0,0 @@ ---- -slug: /angular ---- - -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; - -# Angular - -[Angular][] components for Uppy UI plugins. - -## When should I use it? - -When you are using the Angular framework and you would like to use one of the UI -components. - -## Install - - - - -```shell -npm install @uppy/angular -``` - - - - - -```shell -yarn add @uppy/angular -``` - - - - -:::note - -You also need to install the UI plugin you want to use. For instance, -`@uppy/dashboard`. - -::: - -## Use - -Instead of adding a UI plugin to an Uppy instance with `.use()`, the Uppy -instance can be passed into components as a `props` prop. - -The following plugins are available as Angular component wrappers: - -- Import `UppyAngularDashboardModule` used as `` renders - [`@uppy/dashboard`](/docs/dashboard) -- Import `UppyAngularDashboardModalModule` used as `` - renders [`@uppy/dashboard`](/docs/dashboard) as a modal -- Import `UppyAngularProgressBarModule` used as `` renders - [`@uppy/progress-bar`](/docs/progress-bar) -- Import `UppyAngularStatusBarModule` used as `` renders - [`@uppy/status-bar`](/docs/status-bar) -- Import `UppyAngularDragDropModule` used as `` renders - [`@uppy/drag-drop`](/docs/drag-drop) - -Each component takes a `props` prop that will be passed to the UI Plugin. - -```typescript title="app.module.ts" showLineNumbers -import { NgModule } from '@angular/core'; -import { UppyAngularDashboardModule } from '@uppy/angular'; - -import { BrowserModule } from '@angular/platform-browser'; -import { AppComponent } from './app.component'; - -@NgModule({ - declarations: [AppComponent], - imports: [BrowserModule, UppyAngularDashboardModule], - providers: [], - bootstrap: [AppComponent], -}) -class {} -``` - -```html title="app.component.html" showLineNumbers - -``` - -You should initialize Uppy as a property of your component. - -```typescript title="app.component.ts" showLineNumbers -import { Component } from '@angular/core'; -import { Uppy } from '@uppy/core'; - -@Component({ - selector: 'app-root', -}) -export class AppComponent { - uppy: Uppy = new Uppy({ debug: true, autoProceed: true }); -} -``` - -### CSS - -All components have their own styling and should be added to your component -decorator. You can find the CSS import statements in the docs of the UI plugin -you want to use. For instance, for `@uppy/dashboard`: - -```typescript -import { Component, ViewEncapsulation } from '@angular/core'; -//... -@Component({ - // ... - encapsulation: ViewEncapsulation.None, - styleUrls: [ - '../node_modules/@uppy/core/dist/style.css', - '../node_modules/@uppy/dashboard/dist/style.css', - ], -}) -``` - -[angular]: https://angular.io diff --git a/docs/framework-integrations/react.mdx b/docs/framework-integrations/react.mdx deleted file mode 100644 index 80ab8778b..000000000 --- a/docs/framework-integrations/react.mdx +++ /dev/null @@ -1,244 +0,0 @@ ---- -slug: /react ---- - -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; - -# React - -[React][] components for the Uppy UI plugins and hooks. - -## Install - - - - -```shell -npm install @uppy/react -``` - - - - - -```shell -yarn add @uppy/react -``` - - - - -:::note - -You also need to install the UI plugin you want to use. For instance, -`@uppy/dashboard`. - -::: - -## Use - -`@uppy/react` exposes component wrappers for `Dashboard`, `DragDrop`, and all -other UI elements. The components can be used with either [React][] or -API-compatible alternatives such as [Preact][]. - -:::caution - -If you find yourself writing many instances of `useState` and `useEffect` to -achieve something with Uppy in React, you are most likely breaking React best -practices. Consider reading -“[You Might Not Need an Effect](https://react.dev/learn/you-might-not-need-an-effect)” -and looking at our examples below. - -::: - -### Components - -The following components are exported from `@uppy/react`: - -- `` renders [`@uppy/dashboard`](/docs/dashboard) -- `` renders [`@uppy/dashboard`](/docs/dashboard) as a modal -- `` renders [`@uppy/drag-drop`](/docs/drag-drop) -- `` renders [`@uppy/progress-bar`](/docs/progress-bar) -- `` renders [`@uppy/status-bar`](/docs/status-bar) - -### Hooks - -#### `useUppyState(uppy, selector)` - -Use this hook when you need to access Uppy’s state reactively. Most of the -times, this is needed if you are building a custom UI for Uppy in React. - -```js -// IMPORTANT: passing an initializer function to prevent Uppy from being reinstantiated on every render. -const [uppy] = useState(() => new Uppy()); - -const files = useUppyState(uppy, (state) => state.files); -const totalProgress = useUppyState(uppy, (state) => state.totalProgress); -// We can also get specific plugin state. -// Note that the value on `plugins` depends on the `id` of the plugin. -const metaFields = useUppyState( - uppy, - (state) => state.plugins?.Dashboard?.metaFields, -); -``` - -You can see all the values you can access on the -[`State`](https://github.com/transloadit/uppy/blob/c45407d099d87e25cecaf03c5d9ce59c582ca0dc/packages/%40uppy/core/src/Uppy.ts#L155-L181) -type. If you are accessing plugin state, you would have to look at the types of -the plugin. - -#### `useUppyEvent(uppy, event, callback)` - -Listen to Uppy events in a React component. - -The first item in the array is an array of results from the event. Depending on -the event, that can be empty or have up to three values. The second item is a -function to clear the results. Values remain in state until the next event (if -that ever comes). Depending on your use case, you may want to keep the values in -state or clear the state after something else happenend. - -```ts -// IMPORTANT: passing an initializer function to prevent Uppy from being reinstantiated on every render. -const [uppy] = useState(() => new Uppy()); - -const [results, clearResults] = useUppyEvent(uppy, 'transloadit:result'); -const [stepName, result, assembly] = results; // strongly typed - -useUppyEvent(uppy, 'cancel-all', clearResults); -``` - -## Examples - -### Example: basic component - -Here we have a basic component which ties Uppy’s state to the component. This -means you can render multiple instances. But be aware that as your component -unmounts, for instance because the user navigates to a different page, Uppy’s -state will be lost and uploads will stop. - -:::note - -If you render multiple instances of Uppy, make sure to give each instance a -unique `id`. - -::: - -```js -import React, { useEffect, useState } from 'react'; -import Uppy from '@uppy/core'; -import Webcam from '@uppy/webcam'; -import { Dashboard } from '@uppy/react'; - -import '@uppy/core/dist/style.min.css'; -import '@uppy/dashboard/dist/style.min.css'; -import '@uppy/webcam/dist/style.min.css'; - -function Component() { - // IMPORTANT: passing an initializer function to prevent Uppy from being reinstantiated on every render. - const [uppy] = useState(() => new Uppy().use(Webcam)); - - return ; -} -``` - -### Example: keep Uppy state and uploads while navigating between pages - -When you want Uppy’s state to persist and keep uploads running between pages, -you can -[lift the state up](https://react.dev/learn/sharing-state-between-components#lifting-state-up-by-example). - -```js -import React, { useState, useEffect } from 'react'; -import Uppy from '@uppy/core'; -import { Dashboard } from '@uppy/react'; - -function Page1() { - // ... -} - -function Page2({ uppy }) { - return ( - <> -

{totalProgress}

- - - ); -} - -export default function App() { - // keeping the uppy instance alive above the pages the user can switch during uploading - const [uppy] = useState(() => new Uppy()); - - return ( - // Add your router here - <> - - - - ); -} -``` - -### Example: updating Uppy’s options dynamically based on props - -```js -// ... -function Component(props) { - // IMPORTANT: passing an initializer function to prevent the state from recreating. - const [uppy] = useState(() => new Uppy().use(Webcam)); - - useEffect(() => { - uppy.setOptions({ restrictions: props.restrictions }); - }, [props.restrictions]); - - useEffect(() => { - uppy.getPlugin('Webcam').setOptions({ modes: props.webcamModes }); - }, [props.webcamModes]); - - return ; -} -``` - -### Example: dynamic params and signature for Transloadit - -When you go to production always make sure to set the `signature`. **Not using -[Signature Authentication](https://transloadit.com/docs/topics/signature-authentication/) -can be a security risk**. Signature Authentication is a security measure that -can prevent outsiders from tampering with your Assembly Instructions. - -Generating a signature should be done on the server to avoid leaking secrets. In -React, this could get awkward with a `fetch` in a `useEffect` and setting it to -`useState`. Instead, it’s easier to use the -[`assemblyOptions`](/docs/transloadit#assemblyoptions) option to `fetch` the -params. - -```js -// ... -function createUppy(userId) { - return new Uppy({ meta: { userId } }).use(Transloadit, { - async assemblyOptions(file) { - // You can send meta data along for use in your template. - // https://transloadit.com/docs/topics/assembly-instructions/#form-fields-in-instructions - const body = JSON.stringify({ userId: file.meta.userId }); - const res = await fetch('/transloadit-params', { method: 'POST', body }); - return response.json(); - }, - }); -} - -function Component({ userId }) { - // IMPORTANT: passing an initializer function to prevent Uppy from being reinstantiated on every render. - const [uppy] = useState(() => createUppy(userId)); - - useEffect(() => { - if (userId) { - // Adding to global `meta` will add it to every file. - uppy.setOptions({ meta: { userId } }); - } - }, [uppy, userId]); -} -``` - -[react]: https://facebook.github.io/react -[preact]: https://preactjs.com/ diff --git a/docs/framework-integrations/svelte.mdx b/docs/framework-integrations/svelte.mdx deleted file mode 100644 index 6d8922bc3..000000000 --- a/docs/framework-integrations/svelte.mdx +++ /dev/null @@ -1,71 +0,0 @@ ---- -slug: /svelte ---- - -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; - -# Svelte - -[Svelte][] components for the Uppy UI plugins. - -## Install - - - - -```shell -npm install @uppy/svelte -``` - - - - - -```shell -yarn add @uppy/svelte -``` - - - - -:::note - -You also need to install the UI plugin you want to use. For instance, -`@uppy/dashboard`. - -::: - -## Use - -The following plugins are available as Svelte component wrappers: - -- `` renders [`@uppy/dashboard`](/docs/dashboard) -- `` renders [`@uppy/dashboard`](/docs/dashboard) as a modal -- `` renders [`@uppy/drag-drop`](/docs/drag-drop) -- `` renders [`@uppy/progress-bar`](/docs/progress-bar) -- `` renders [`@uppy/status-bar`](/docs/status-bar) - -Instead of adding a UI plugin to an Uppy instance with `.use()`, the Uppy -instance can be passed into components as an `uppy` prop. Due to the way Svelte -handles reactivity, you can initialize Uppy the same way you would with vanilla -JavaScript. - -```svelte - - -
-``` - -[svelte]: https://svelte.dev diff --git a/docs/framework-integrations/vue.mdx b/docs/framework-integrations/vue.mdx deleted file mode 100644 index 2d348f802..000000000 --- a/docs/framework-integrations/vue.mdx +++ /dev/null @@ -1,73 +0,0 @@ ---- -slug: /vue ---- - -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; - -# Vue - -[Vue][] components for the Uppy UI plugins. - -## Install - - - - -```shell -npm install @uppy/vue -``` - - - - - -```shell -yarn add @uppy/vue -``` - - - - -:::note - -You also need to install the UI plugin you want to use. For instance, -`@uppy/dashboard`. - -::: - -## Use - -The following plugins are available as Vue component wrappers: - -- `` renders [`@uppy/dashboard`](/docs/dashboard) inline -- `` renders [`@uppy/dashboard`](/docs/dashboard) as a modal -- `` renders [`@uppy/drag-drop`](/docs/drag-drop) -- `` renders [`@uppy/progress-bar`](/docs/progress-bar) -- `` renders [`@uppy/status-bar`](/docs/status-bar) - -Instead of adding a UI plugin to an Uppy instance with `.use()`, the Uppy -instance can be passed into components as an `uppy` prop. Due to the way Vue -handles reactivity, you can initialize Uppy the same way you would with vanilla -JavaScript. - -```html - - - -``` - -[vue]: https://vuejs.org diff --git a/docs/golden-retriever.mdx b/docs/golden-retriever.mdx deleted file mode 100644 index 33387d280..000000000 --- a/docs/golden-retriever.mdx +++ /dev/null @@ -1,150 +0,0 @@ ---- -sidebar_position: 11 ---- - -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; - -import UppyCdnExample from '/src/components/UppyCdnExample'; - -# Golden Retriever - -The `@uppy/golden-retriever` plugin saves selected files in your browser cache, -so that if the browser crashes, or the user accidentally closes the tab, Uppy -can restore everything and continue uploading as if nothing happened. You can -read more about it -[on our blog](https://uppy.io/blog/2017/07/golden-retriever/). - -The Golden Retriever uses three methods of browser data storage: - -- `LocalStorage` to store file metadata and Uppy state only. -- `IndexedDB` for small files, usually under 5MiB. -- `Service Worker` (_optional_) for _all_ files because, unlike `IndexedDB`, - `Service Worker` can keep references to large files. Service Worker storage is - _quite_ temporary though, and doesn’t persist across browser crashes or - restarts. It works well, however, for accidental refreshes or closed tabs. - -Upon restore, the plugin first restores state from `LocalStorage` and then -checks both file storages — `IndexedDB` and `ServiceWorker` — merging the -results. - -If restore is unsuccessful for certain files, they will be marked as “ghosts” in -the Dashboard UI, and a message + button offering to re-select those files will -be displayed. - -Checkout the -[storage quotas](https://developer.mozilla.org/en-US/docs/Web/API/Storage_API/Storage_quotas_and_eviction_criteria#other_web_technologies) -on MDN to see how much data can be stored depending on the device and browser. - -## When should I use this? - -When you want extra insurance for the user-selected files. If you feel like -users might spend some time picking files, tweaking descriptions etc, and will -not appreciate having to do it over again if something crashes. Another use case -might be when you think user might want to pick a few files, navigate away to do -something else in your app or otherwise, and then come back to the same -selection. - -## Install - - - - -```shell -npm install @uppy/golden-retriever -``` - - - - - -```shell -yarn add @uppy/golden-retriever -``` - - - - - - {` - import { Uppy, GoldenRetriever } from "{{UPPY_JS_URL}}" - new Uppy().use(GoldenRetriever) - `} - - - - -## Use - -```js {7} showLineNumbers -import Uppy from '@uppy/core'; -import Dashboard from '@uppy/dashboard'; -import GoldenRetriever from '@uppy/golden-retriever'; - -new Uppy() - .use(Dashboard, {inline:true, target: '#dashboard') - .use(GoldenRetriever); -``` - -By default, Golden Retriever will only use the `IndexedDB` storage, which is -good enough for files up to 5 MiB. `Service Worker` is optional and requires -setup. - -### Enabling Service Worker - -With the Service Worker storage, Golden Retriever will be able to temporary -store references to large files. - -1. Bundle your own service worker `sw.js` file with Uppy GoldenRetriever’s - service worker. - - :::tip - - For Webpack, check out - [serviceworker-webpack-plugin](https://github.com/oliviertassinari/serviceworker-webpack-plugin). - - ::: - - ```js title="sw.js" - import '@uppy/golden-retriever/lib/ServiceWorker'; - ``` - -2. Register it in your app’s entry point: - - ```js - // you app.js entry point - uppy.use(GoldenRetriever, { serviceWorker: true }); - - if ('serviceWorker' in navigator) { - navigator.serviceWorker - .register('/sw.js') // path to your bundled service worker with GoldenRetriever service worker - .then((registration) => { - console.log( - 'ServiceWorker registration successful with scope: ', - registration.scope, - ); - }) - .catch((error) => { - console.log(`Registration failed with ${error}`); - }); - } - ``` - -Voilà, that’s it. Happy retrieving! - -## API - -### Options - -#### `id` - -A unique identifier for this plugin (`string`, default: `'GoldenRetriever'`). - -#### `expires` - -How long to store metadata and files for. Used for `LocalStorage` and -`IndexedDB` (`number`, default: `24 * 60 * 60 * 1000` // 24 hours). - -#### `serviceWorker` - -Whether to enable `Service Worker` storage (`boolean`, default: `false`). diff --git a/docs/guides/_category_.json b/docs/guides/_category_.json deleted file mode 100644 index 82e09efaa..000000000 --- a/docs/guides/_category_.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "label": "Guides", - "position": 2 -} diff --git a/docs/guides/browser-support.mdx b/docs/guides/browser-support.mdx deleted file mode 100644 index 4537f47f1..000000000 --- a/docs/guides/browser-support.mdx +++ /dev/null @@ -1,62 +0,0 @@ ---- -sidebar_position: 7 ---- - -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; - -import UppyCdnExample from '/src/components/UppyCdnExample'; - -# Supporting IE11 - -We officially support recent versions of Chrome, Firefox, Safari and Edge. - -Internet Explorer is not officially supported, as in we don’t run tests for it, -but you can be mostly confident it works with the right polyfills. But it does -come with a risk of unexpected results in styling or functionality. - -## Polyfills - - - - -```shell -npm install core-js whatwg-fetch abortcontroller-polyfill md-gum-polyfill resize-observer-polyfill -``` - - - - - -```shell -yarn add core-js whatwg-fetch abortcontroller-polyfill md-gum-polyfill resize-observer-polyfill -``` - - - - -```js -import 'core-js'; -import 'whatwg-fetch'; -import 'abortcontroller-polyfill/dist/polyfill-patch-fetch'; -// Order matters: AbortController needs fetch which needs Promise (provided by core-js). - -import 'md-gum-polyfill'; -import ResizeObserver from 'resize-observer-polyfill'; - -window.ResizeObserver ??= ResizeObserver; - -export { default } from '@uppy/core'; -export * from '@uppy/core'; -``` - -## Legacy CDN bundle - - - {` - import { Uppy, DragDrop, Tus } from "{{UPPY_JS_URL}}" - const uppy = new Uppy() - uppy.use(DragDrop, { target: '#uppy' }) - uppy.use(Tus, { endpoint: '//tusd.tusdemo.net/files/' }) - `} - diff --git a/docs/guides/building-plugins.md b/docs/guides/building-plugins.md deleted file mode 100644 index d6b44178c..000000000 --- a/docs/guides/building-plugins.md +++ /dev/null @@ -1,382 +0,0 @@ ---- -sidebar_position: 3 ---- - -# Building plugins - -You can find already a few useful Uppy plugins out there, but there might come a -time when you will want to build your own. Plugins can hook into the upload -process or render a custom UI, typically to: - -- Render some custom UI element, such as [StatusBar](/docs/status-bar) or - [Dashboard](/docs/dashboard). -- Do the actual uploading, such as [XHRUpload](/docs/xhr-upload) or - [Tus](/docs/tus). -- Do work before the upload, like compressing an image or calling external API. -- Interact with a third-party service to process uploads correctly, such as - [Transloadit](/docs/transloadit) or [AwsS3](/docs/aws-s3). - -See a [full example of a plugin](#example-of-a-custom-plugin) below. - -## Creating A Plugin - -Uppy has two classes to create plugins with. `BasePlugin` for plugins that don’t -need a user interface, and `UIPlugin` for ones that do. Each plugin has an `id` -and a `type`. `id`s are used to uniquely identify plugins. A `type` can be -anything—some plugins use `type`s to decide whether to do something to some -other plugin. For example, when targeting plugins at the built-in `Dashboard` -plugin, the Dashboard uses the `type` to figure out where to mount different UI -elements. `'acquirer'`-type plugins are mounted into the tab bar, while -`'progressindicator'`-type plugins are mounted into the progress bar area. - -The plugin constructor receives the Uppy instance in the first parameter, and -any options passed to `uppy.use()` in the second parameter. - -```js -import BasePlugin from '@uppy/core'; - -export default class MyPlugin extends BasePlugin { - constructor(uppy, opts) { - super(uppy, opts); - this.id = opts.id || 'MyPlugin'; - this.type = 'example'; - } -} -``` - -## Methods - -Plugins can define methods to execute certain tasks. The most important method -is `install()`, which is called when a plugin is `.use`d. - -All the below methods are optional! Only define the methods you need. - -### `BasePlugin` - -#### `install()` - -Called when the plugin is `.use`d. Do any setup work here, like attaching events -or adding [upload hooks](#Upload-Hooks). - -```js -export default class MyPlugin extends UIPlugin { - // ... - install() { - this.uppy.on('upload-progress', this.onProgress); - this.uppy.addPostProcessor(this.afterUpload); - } -} -``` - -#### `uninstall()` - -Called when the plugin is removed, or the Uppy instance is closed. This should -undo all the work done in the `install()` method. - -```js -export default class MyPlugin extends UIPlugin { - // ... - uninstall() { - this.uppy.off('upload-progress', this.onProgress); - this.uppy.removePostProcessor(this.afterUpload); - } -} -``` - -#### `afterUpdate()` - -Called after every state update with a debounce, after everything has mounted. - -#### `addTarget()` - -Use this to add your plugin to another plugin’s target. This is what -`@uppy/dashboard` uses to add other plugins to its UI. - -### `UIPlugin` - -`UIPlugin` extends the `BasePlugin` class so it will also contain all the above -methods. - -#### `mount(target)` - -Mount this plugin to the `target` element. `target` can be a CSS query selector, -a DOM element, or another Plugin. If `target` is a Plugin, the source (current) -plugin will register with the target plugin, and the latter can decide how and -where to render the source plugin. - -This method can be overridden to support for different render engines. - -#### `render()` - -Render this plugin’s UI. Uppy uses [Preact](https://preactjs.com) as its view -engine, so `render()` should return a Preact element. `render` is automatically -called by Uppy on each state change. - -#### `onMount()` - -Called after Preact has rendered the components of the plugin. - -#### `update(state)` - -Called on each state update. You will rarely need to use this, unless if you -want to build a UI plugin using something other than Preact. - -#### `onUnmount()` - -Called after the elements have been removed from the DOM. Can be used to do some -clean up or other side-effects. - -## Upload Hooks - -When creating an upload, Uppy runs files through an upload pipeline. The -pipeline consists of three parts, each of which can be hooked into: -Preprocessing, Uploading, and Postprocessing. Preprocessors can be used to -configure uploader plugins, encrypt files, resize images, etc., before uploading -them. Uploaders do the actual uploading work, such as creating an XMLHttpRequest -object and sending the file. Postprocessors do their work after files have been -uploaded completely. This could be anything from waiting for a file to propagate -across a CDN, to sending another request to relate some metadata to the file. - -Each hook is a function that receives an array containing the file IDs that are -being uploaded, and returns a Promise to signal completion. Hooks are added and -removed through `Uppy` methods: -[`addPreProcessor`](/docs/uppy#addpreprocessorfn), -[`addUploader`](/docs/uppy#adduploaderfn), -[`addPostProcessor`](/docs/uppy#addpostprocessorfn), and their -[`remove*`](/docs/uppy#removepreprocessorremoveuploaderremovepostprocessorfn) -counterparts. Normally, hooks should be added during the plugin `install()` -method, and removed during the `uninstall()` method. - -Additionally, upload hooks can fire events to signal progress. - -When adding hooks, make sure to bind the hook `fn` beforehand! Otherwise, it -will be impossible to remove. For example: - -```js -class MyPlugin extends BasePlugin { - constructor(uppy, opts) { - super(uppy, opts); - this.id = opts.id || 'MyPlugin'; - this.type = 'example'; - this.prepareUpload = this.prepareUpload.bind(this); // ← this! - } - - prepareUpload(fileIDs) { - console.log(this); // `this` refers to the `MyPlugin` instance. - return Promise.resolve(); - } - - install() { - this.uppy.addPreProcessor(this.prepareUpload); - } - - uninstall() { - this.uppy.removePreProcessor(this.prepareUpload); - } -} -``` - -Or you can define the method as a class field: - -```js -class MyPlugin extends UIPlugin { - constructor(uppy, opts) { - super(uppy, opts); - this.id = opts.id || 'MyPlugin'; - this.type = 'example'; - } - - prepareUpload = (fileIDs) => { - // ← this! - console.log(this); // `this` refers to the `MyPlugin` instance. - return Promise.resolve(); - }; - - install() { - this.uppy.addPreProcessor(this.prepareUpload); - } - - uninstall() { - this.uppy.removePreProcessor(this.prepareUpload); - } -} -``` - -## Progress events - -Progress events can be fired for individual files to show feedback to the user. -For upload progress events, only emitting how many bytes are expected and how -many have been uploaded is enough. Uppy will handle calculating progress -percentages, upload speed, etc. - -Preprocessing and postprocessing progress events are plugin-dependent and can -refer to anything, so Uppy doesn’t try to be smart about them. Processing -progress events can be of two types: determinate or indeterminate. Some -processing does not have meaningful progress beyond “not done” and “done”. For -example, sending a request to initialize a server-side resource that will serve -as the upload destination. In those situations, indeterminate progress is -suitable. Other processing does have meaningful progress. For example, -encrypting a large file. In those situations, determinate progress is suitable. - -Here are the relevant events: - -- [`preprocess-progress`](/docs/uppy#preprocess-progress) -- [`upload-progress`](/docs/uppy#upload-progress) -- [`postprocess-progress`](/docs/uppy#postprocess-progress) - -## JSX - -Since Uppy uses Preact and not React, the default Babel configuration for JSX -elements does not work. You have to import the Preact `h` function and tell -Babel to use it by adding a `/** @jsx h */` comment at the top of the file. - -See the Preact -[Getting Started Guide](https://preactjs.com/guide/getting-started) for more on -Babel and JSX. - - - -```jsx -/** @jsx h */ -import { UIPlugin } from '@uppy/core'; -import { h } from 'preact'; - -class NumFiles extends UIPlugin { - render() { - const numFiles = Object.keys(this.uppy.state.files).length; - - return
Number of files: {numFiles}
; - } -} -``` - -## Locales - -For any user facing language that you use while writing your Plugin, please -provide them as strings in the `defaultLocale` property like so: - -```js -this.defaultLocale = { - strings: { - youCanOnlyUploadFileTypes: 'You can only upload: %{types}', - youCanOnlyUploadX: { - 0: 'You can only upload %{smart_count} file', - 1: 'You can only upload %{smart_count} files', - 2: 'You can only upload %{smart_count} files', - }, - }, -}; -``` - -This allows them to be overridden by Locale Packs, or directly when users pass -`locale: { strings: youCanOnlyUploadFileTypes: 'Something else completely about %{types}'} }`. -For this to work, it’s also required that you call `this.i18nInit()` in the -plugin constructor. - -## Example of a custom plugin - -Below is a full example of a -[small plugin](https://github.com/arturi/uppy-plugin-image-compressor) that -compresses images before uploading them. You can replace `compressorjs` method -with any other work you need to do. This works especially well for async stuff, -like calling an external API. - - - -```js -import { UIPlugin } from '@uppy/core'; -import Translator from '@uppy/utils/lib/Translator'; -import Compressor from 'compressorjs/dist/compressor.esm.js'; - -class UppyImageCompressor extends UIPlugin { - constructor(uppy, opts) { - const defaultOptions = { - quality: 0.6, - }; - super(uppy, { ...defaultOptions, ...opts }); - - this.id = this.opts.id || 'ImageCompressor'; - this.type = 'modifier'; - - this.defaultLocale = { - strings: { - compressingImages: 'Compressing images...', - }, - }; - - // we use those internally in `this.compress`, so they - // should not be overridden - delete this.opts.success; - delete this.opts.error; - - this.i18nInit(); - } - - compress(blob) { - return new Promise( - (resolve, reject) => - new Compressor(blob, { - ...this.opts, - success(result) { - return resolve(result); - }, - error(err) { - return reject(err); - }, - }), - ); - } - - prepareUpload = (fileIDs) => { - const promises = fileIDs.map((fileID) => { - const file = this.uppy.getFile(fileID); - this.uppy.emit('preprocess-progress', file, { - mode: 'indeterminate', - message: this.i18n('compressingImages'), - }); - - if (!file.type.startsWith('image/')) { - return; - } - - return this.compress(file.data) - .then((compressedBlob) => { - this.uppy.log( - `[Image Compressor] Image ${file.id} size before/after compression: ${file.data.size} / ${compressedBlob.size}`, - ); - this.uppy.setFileState(fileID, { data: compressedBlob }); - }) - .catch((err) => { - this.uppy.log( - `[Image Compressor] Failed to compress ${file.id}:`, - 'warning', - ); - this.uppy.log(err, 'warning'); - }); - }); - - const emitPreprocessCompleteForAll = () => { - fileIDs.forEach((fileID) => { - const file = this.uppy.getFile(fileID); - this.uppy.emit('preprocess-complete', file); - }); - }; - - // Why emit `preprocess-complete` for all files at once, instead of - // above when each is processed? - // Because it leads to StatusBar showing a weird “upload 6 files” button, - // while waiting for all the files to complete pre-processing. - return Promise.all(promises).then(emitPreprocessCompleteForAll); - }; - - install() { - this.uppy.addPreProcessor(this.prepareUpload); - } - - uninstall() { - this.uppy.removePreProcessor(this.prepareUpload); - } -} - -export default UppyImageCompressor; -``` diff --git a/docs/guides/building-your-own-ui-with-uppy.md b/docs/guides/building-your-own-ui-with-uppy.md deleted file mode 100644 index fc8389c0f..000000000 --- a/docs/guides/building-your-own-ui-with-uppy.md +++ /dev/null @@ -1,11 +0,0 @@ ---- -sidebar_position: 4 ---- - -# Building your own UI with Uppy - -:::note - -This guide is in progress. - -::: diff --git a/docs/guides/choosing-uploader.md b/docs/guides/choosing-uploader.md deleted file mode 100644 index 692265365..000000000 --- a/docs/guides/choosing-uploader.md +++ /dev/null @@ -1,84 +0,0 @@ ---- -sidebar_position: 1 ---- - -# Choosing the uploader you need - -Versatile, reliable uploading is at the heart of Uppy. It has many configurable -plugins to suit your needs. In this guide we will explain the different plugins, -their strategies, and when to use them based on use cases. - -## Use cases - -### I want worry-free, plug-and-play uploads with Transloadit services - -Transloadit’s strength is versatility. By doing video, audio, images, documents, -and more, you only need one vendor for [all your file processing -needs][transloadit-services]. The [`@uppy/transloadit`][] plugin directly -uploads to Transloadit so you only have to worry about creating a -[template][transloadit-concepts]. It uses -[Tus](#i-want-reliable-resumable-uploads) under the hood so you don’t have to -sacrifice reliable, resumable uploads for convenience. - -You should use [`@uppy/transloadit`][] if you don’t want to host your own -server, (optionally) need file processing, and store it in the service (such as -S3 or GCS) of your liking. All with minimal effort. - -### I want reliable, resumable uploads - -[Tus][tus] is a new open protocol for resumable uploads built on HTTP. This -means accidentally closing your tab or losing connection let’s you continue, for -instance, your 10GB upload instead of starting all over. - -Tus supports any language, any platform, and any network. It requires a client -and server integration to work. You can checkout the client and server -[implementations][tus-implementations] to find the server in your preferred -language. You can store files on the Tus server itself, but you can also use -service integrations (such as S3) to store files externally. - -If you want reliable, resumable uploads: use [`@uppy/tus`][] to connect to your -Tus server in a few lines of code. - -:::tip - -If you plan to let people upload _a lot_ of files, [`@uppy/tus`][] has -exponential backoff built-in. Meaning if your server (or proxy) returns HTTP 429 -because it’s being overloaded, [`@uppy/tus`][] will find the ideal sweet spot to -keep uploading without overloading. - -::: - -### I want to upload to AWS S3 (or S3-compatible storage) directly - -When you prefer a _client-to-storage_ over a _client-to-server-to-storage_ (such -as [Transloadit](/docs/transloadit) or [Tus](/docs/tus)) setup. This may in some -cases be preferable, for instance, to reduce costs or the complexity of running -a server and load balancer with [Tus](/docs/tus). - -Uppy has two plugins to make this happen [`@uppy/aws-s3`][] and -[`@uppy/aws-s3-multipart`][], but we are planning to merge the two plugins in -the next major. You should use [`@uppy/aws-s3`][] with the new -`shouldUseMultipart` option. - -:::info - -You can also save files in S3 with the [`/s3/store`][s3-robot] robot while still -using the powers of Transloadit services. - -::: - -### I want to send regular HTTP uploads to my own server - -[`@uppy/xhr-upload`][] handles classic HTML multipart form uploads as well as -uploads using the HTTP `PUT` method. - -[s3-robot]: https://transloadit.com/services/file-exporting/s3-store/ -[transloadit-services]: https://transloadit.com/services/ -[transloadit-concepts]: https://transloadit.com/docs/getting-started/concepts/ -[`@uppy/transloadit`]: /docs/transloadit -[`@uppy/tus`]: /docs/tus -[`@uppy/aws-s3-multipart`]: /docs/aws-s3-multipart -[`@uppy/aws-s3`]: /docs/aws-s3-multipart -[`@uppy/xhr-upload`]: /docs/xhr-upload -[tus]: https://tus.io/ -[tus-implementations]: https://tus.io/implementations.html diff --git a/docs/guides/custom-stores.md b/docs/guides/custom-stores.md deleted file mode 100644 index 2370f9262..000000000 --- a/docs/guides/custom-stores.md +++ /dev/null @@ -1,136 +0,0 @@ -# Custom stores - -If your app uses a state management library such as -[Redux](https://redux.js.org), it can be useful to have Uppy store its state -there instead—that way, you could write custom uploader UI components in the -same way as the other components in the application. - -Uppy comes with two state management solutions (stores): - -- `@uppy/store-default`, a basic object-based store. -- `@uppy/store-redux`, a store that uses a key in a Redux store. - -You can also use a third-party store: - -- [uppy-store-ngrx](https://github.com/rimlin/uppy-store-ngrx/), keeping Uppy - state in a key in an [Ngrx](https://github.com/ngrx/platform) store for use - with Angular. - -## Using stores - -To use a store, pass an instance to the -[`store` option](/docs/uppy#store-defaultstore) in the Uppy constructor: - -```js -import DefaultStore from '@uppy/store-default'; - -const uppy = new Uppy({ - store: new DefaultStore(), -}); -``` - -### `DefaultStore` - -Uppy uses the `DefaultStore` by default! You do not need to do anything to use -it. It does not take any options. - -### `ReduxStore` - -The `ReduxStore` stores Uppy state on a key in an existing Redux store. The -`ReduxStore` dispatches `uppy/STATE_UPDATE` actions to update state. When the -state in Redux changes, it notifies Uppy. This way, you get most of the benefits -of Redux, including support for the Redux Devtools and time traveling! - -Checkout our -[Redux example](https://github.com/transloadit/uppy/tree/main/examples/redux) -for a working demo. - -#### `opts.store` - -Pass a Redux store instance, from `Redux.createStore`. This instance should have -the Uppy reducer mounted somewhere already. - -#### `opts.id` - -By default, the `ReduxStore` assumes Uppy state is stored on a `state.uppy[id]` -key. `id` is randomly generated by the store constructor, but can be specified -by passing an `id` option if it should be predictable. - -```js -ReduxStore({ - store, - id: 'avatarUpload', -}); -``` - -#### `opts.selector` - -If you’d rather not store the Uppy state under the `state.uppy` key at all, use -the `selector` option to the `ReduxStore` constructor to tell it where to find -state instead: - -```js -const uppy = new Uppy({ - store: ReduxStore({ - store, - id: 'avatarUpload', - selector: (state) => state.pages.profile.uppy.avatarUpload, - }), -}); -``` - -Note that when specifying a custom selector, you **must** also specify a custom -store ID. The store `id` tells the reducer in which property it should put -Uppy’s state. The selector must then take the state from that property. In the -example, we set the ID to `avatarUpload` and take the state from the -`[reducer mount path].avatarUpload`. - -If your app uses [`reselect`](https://npmjs.com/package/reselect), its selectors -work well with this! - -## Implementing Stores - -An Uppy store is an object with three methods. - -- `getState()` - Return the current state object. -- `setState(patch)` - Merge the object `patch` into the current state. -- `subscribe(listener)` - Call `listener` whenever the state changes. `listener` - is a function that should receive three parameters: - `(prevState, nextState, patch)` - - The `subscribe()` method should return a function that “unsubscribes” - (removes) the `listener`. - -The default store implementation, for example, looks a bit like this: - -```js -function createDefaultStore() { - let state = {}; - const listeners = new Set(); - - return { - getState: () => state, - setState: (patch) => { - const prevState = state; - const nextState = { ...prevState, ...patch }; - - state = nextState; - - listeners.forEach((listener) => { - listener(prevState, nextState, patch); - }); - }, - subscribe: (listener) => { - listeners.add(listener); - return () => listeners.remove(listener); - }, - }; -} -``` - -A pattern like this, where users can pass options via a function call if -necessary, is recommended. - -See the -[@uppy/store-default](https://github.com/transloadit/uppy/tree/main/packages/%40uppy/store-default) -package for more inspiration. diff --git a/docs/guides/migration-guides.md b/docs/guides/migration-guides.md deleted file mode 100644 index b8ba53799..000000000 --- a/docs/guides/migration-guides.md +++ /dev/null @@ -1,865 +0,0 @@ -# Migration guides - -These cover all the major Uppy versions and how to migrate to them. - -## Migrate from Companion 4.x to 5.x - -- End-of-Life versions of Node.js are no longer supported (use latest 18.x LTS, - 20.x LTS, or 22.x current). -- Setting the `corsOrigin` (`COMPANION_CLIENT_ORIGINS`) option is now required. - You should define the list of origins you expect your app to be served from, - otherwise it can be impersonated from a different origin you don’t control. - Set it to `true` if you don’t care about impersonating. -- `COMPANION_REDIS_EXPRESS_SESSION_PREFIX` now defaults to `companion-session:` - (before `sess:`). To revert keep backwards compatibility, set the environment - variable `COMPANION_REDIS_EXPRESS_SESSION_PREFIX=sess:`. -- The URL endpoint (used by the `Url`/`Link` plugin) is now turned off by - default and must be explicitly enabled with - `COMPANION_ENABLE_URL_ENDPOINT=true` or `enableUrlEndpoint: true`. -- The option `streamingUpload` / `COMPANION_STREAMING_UPLOAD` now defaults to - `true`. -- The option `getKey(req, filename, metadata)` has changed signature to - `getKey({ filename, metadata, req })`. -- The option `bucket(req, metadata)` has changed signature to - `bucketOrFn({ req, metadata, filename })`. -- Custom provider breaking changes. If you have not implemented a custom - provider, you should not be affected. - - The static `getExtraConfig` property has been renamed to - `getExtraGrantConfig`. - - The static `authProvider` property has been renamed to `oauthProvider`. -- Endpoint `GET /s3/params` now returns `{ method: "POST" }` instead of - `{ method: "post" }`. This will not affect most people. -- `access-control-allow-headers` is no longer included in - `Access-Control-Expose-Headers`, and `uppy-versions` is no longer an allowed - header. We are not aware of any issues this might cause. -- Internal refactoring (probably won’t affect you) - - `getProtectedGot` parameter `blockLocalIPs` changed to `allowLocalIPs` - (inverted boolean). - - `getURLMeta` 2nd (boolean) argument inverted. - - `getProtectedHttpAgent` parameter `blockLocalIPs` changed to `allowLocalIPs` - (inverted boolean). - - `downloadURL` 2nd (boolean) argument inverted. - - `StreamHttpJsonError` renamed to `HttpError`. -- Removed the `oauthOrigin` option, as well as the (undocumented) option - `clients`. Use `corsOrigin` instead. - -### `@uppy/companion-client` - -:::info - -Unless you built a custom provider, you don’t use `@uppy/companion-client` -directly but through provider plugins such as `@uppy/google-drive`. In which -case you don’t have to do anything. - -::: - -- `supportsRefreshToken` now defaults to `false` instead of `true`. If you have - implemented a custom provider, this might affect you. -- `Socket` class is no longer in use and has been removed. Unless you used this - class you don’t need to do anything. -- Remove deprecated options `serverUrl` and `serverPattern` (they were merely - defined in Typescript, not in use). -- `RequestClient` methods `get`, `post`, `delete` no longer accepts a boolean as - the third argument. Instead, pass `{ skipPostResponse: true | false }`. This - won’t affect you unless you’ve been using `RequestClient`. -- When pausing uploads, the WebSocket towards companion will no longer be - closed. This allows paused uploads to be cancelled, but once a file has been - paused it will still occupy its place in the concurrency queue. - -## Migrate from Uppy 3.x to 4.x - -### TypeScript rewrite - -Almost all plugins have been completely rewritten in TypeScript! This means you -may run into type error all over the place, but the types now accurately show -Uppy’s state and files. - -There are too many small changes to cover, so you have to upgrade and see where -TypeScript complains. - -One important thing to note are the new generics on `@uppy/core`. - - - - - -```ts -import Uppy from '@uppy/core'; -// xhr-upload is for uploading to your own backend. -import XHRUpload from '@uppy/xhr-upload'; - -// Your own metadata on files -type Meta = { myCustomMetadata: string }; -// The response from your server -type Body = { someThingMyBackendReturns: string }; - -const uppy = new Uppy().use(XHRUpload, { - endpoint: '/upload', -}); - -const id = uppy.addFile({ - name: 'example.jpg', - data: new Blob(), - meta: { myCustomMetadata: 'foo' }, -}); - -// This is now typed -const { myCustomMetadata } = uppy.getFile(id).meta; - -await uppy.upload(); - -// This is strictly typed too -const { someThingMyBackendReturns } = uppy.getFile(id).response.body!; -``` - -### `@uppy/aws-s3` and `@uppy/aws-s3-multipart` - -- `@uppy/aws-s3` and `@uppy/aws-s3-multipart` have been combined into a single - plugin. You should now only use `@uppy/aws-s3` with the new option, - [`shouldUseMultipart()`](/docs/aws-s3-multipart/#shouldusemultipartfile), to - allow you to switch between regular and multipart uploads. You can read more - about this in the - [plugin docs](https://uppy.io/docs/aws-s3-multipart/#when-should-i-use-it). -- Remove deprecated `prepareUploadParts` option. -- Companion’s options (`companionUrl`, `companionHeaders`, and - `companionCookieRules`) are renamed to more generic names (`endpoint`, - `headers`, and `cookieRules`). - - Using Companion with the `@uppy/aws-s3` plugin only makes sense if you already - need Companion for remote providers (such as Google Drive). When using your - own backend, you can let Uppy do all the heavy lifting on the client which it - would normally do for Companion, so you don’t have to implement that yourself. - - As long as you return the JSON for the expected endpoints (see our - [server example](https://github.com/transloadit/uppy/blob/main/examples/aws-nodejs/index.js)), - you only need to set `endpoint`. - - If you are using Companion, rename the options. If you have a lot of - client-side complexity (`createMultipartUpload`, `signPart`, etc), consider - letting Uppy do this for you. - -### `@uppy/core` - -- The `close()` method has been renamed to `destroy()` to more accurately - reflect you can not recover from it without creating a new `Uppy` instance. -- The `clearUploadedFiles()` method has been renamed to `clear()` as a - convenience method to clear all the state. This can be useful when a user - navigates away and you want to clear the state on success. -- `bytesUploaded`, in `file.progress.bytesUploaded`, is now always a `boolean`, - instead of a `boolean` or `number`. - -### `@uppy/xhr-upload` - -Before the plugin had the options `getResponseData`, `getResponseError`, -`validateStatus` and `responseUrlFieldName`. These were inflexible and too -specific. Now we have hooks similar to `@uppy/tus`: - -- `onBeforeRequest` to manipulate the request before it is sent. -- `shouldRetry` to determine if a request should be retried. By default 3 - retries with exponential backoff. After three attempts it will throw an error, - regardless of whether you returned `true`. -- `onAfterResponse` called after a successful response but before Uppy resolves - the upload. - -Checkout the [docs](/docs/xhr-upload/) for more info. - -### `@uppy/transloadit` - -The options `signature`, `params`, `fields`, and `getAssemblyOptions` have been -removed in favor of [`assemblyOptions`](/docs/transloadit/#assemblyoptions), -which can be an object or an (async) function returning an object. - -When using `assemblyOptions()` as a function, it is now called only once for all -files, instead of per file. Before `@uppy/transloadit` was trying to be too -smart, creating multiple assemblies in which each assembly has files with -identical `fields`. This was done so you can use `fields` dynamically in your -template per file, instead of per assembly. - -Now we sent all metadata of a file inside the tus upload (which -`@uppy/transloadit` uses under the hood) and make it accessible in your -Transloadit template as `file_user_meta`. You should use `fields` for global -values in your template and `file_user_meta` for per file values. - -Another benefit of running `assemblyOptions()` only once, is that when -generating a -[secret](https://transloadit.com/docs/topics/signature-authentication/) on your -server (which you should), a network request is made only once for all files, -instead of per file. - -### CDN - -- We no longer build and serve the legacy build, made for IE11, on our CDN. - -### Miscellaneous - -- All uploaders plugins now consistently use - [`allowedMetaFields`](/docs/xhr-upload/#allowedmetafields). Before there were - inconsistencies between plugins. -- All plugin `titles` (what you see in the Dashboard when you open a plugin) are - now set from the `locale` option. See the - [docs](/docs/locales/#overriding-locale-strings-for-a-specific-plugin) on how - to overide a string. - -### `@uppy/angular` - -- Upgrade to Angular 18.x (17.x is still supported too) and to TS 5.4 - -### `@uppy/react` - -- Remove deprecated `useUppy` -- You can no longer set `inline` on the `Dashboard` component, use `Dashboard` - or `DashboardModal` components respectively. - -### `@uppy/svelte` - -- Make Svelte 5 the peer dependency -- Remove UMD output - -### `@uppy/vue` - -- Migrate to Composition API with TypeScript & drop Vue 2 support -- Drop Vue 2 support - -## Migrate from Robodog to Uppy plugins - -Uppy is flexible and extensible through plugins. But the integration code could -sometimes be daunting. This is what brought Robodog to life. An alternative with -the same features, but with a more ergonomic and minimal API. - -But, it didn’t come with its own set of new problems: - -- It tries to do the exact same, but it looks like a different product. -- It’s confusing for users whether they want to use Robodog or Uppy directly. -- Robodog is more ergonomic because it’s limited. When you hit such a limit, you - need to refactor everything to Uppy with plugins. - -This has now led us to deprecating Robodog and embrace Uppy for its strong -suits; modularity and flexibility. At the same time, we also introduced -something to take away some repetitive integration code: -[`@uppy/remote-sources`](/docs/remote-sources). - -To mimic the Robodog implementation with all its features, you can use the code -snippet below. But chances are Robodog did more than you need so feel free to -remove things or go through the [list of plugins](/docs/companion/) and install -and use the ones you need. - -You can also checkout how we migrated the Robodog example ourselves in this -[commit](https://github.com/transloadit/uppy/commit/089aaed615c77bafaf905e291b6b4e82aaeb2f6f). - -```js -import Uppy from '@uppy/core'; -import Dashboard from '@uppy/dashboard'; -import RemoteSources from '@uppy/remote-sources'; -import Webcam from '@uppy/webcam'; -import ScreenCapture from '@uppy/screen-capture'; -import GoldenRetriever from '@uppy/golden-retriever'; -import ImageEditor from '@uppy/image-editor'; -import Audio from '@uppy/audio'; -import Transloadit, { - COMPANION_URL, - COMPANION_ALLOWED_HOSTS, -} from '@uppy/transloadit'; - -import '@uppy/core/dist/style.css'; -import '@uppy/dashboard/dist/style.css'; -import '@uppy/audio/dist/style.css'; -import '@uppy/screen-capture/dist/style.css'; -import '@uppy/image-editor/dist/style.css'; - -new Uppy() - .use(Dashboard, { - inline: true, - target: '#app', - showProgressDetails: true, - proudlyDisplayPoweredByUppy: true, - }) - .use(RemoteSources, { - companionUrl: COMPANION_URL, - companionAllowedHosts: COMPANION_ALLOWED_HOSTS, - }) - .use(Webcam, { - showVideoSourceDropdown: true, - showRecordingLength: true, - }) - .use(Audio, { - showRecordingLength: true, - }) - .use(ScreenCapture) - .use(ImageEditor) - .use(Transloadit, { - service: 'https://api2.transloadit.com', - async getAssemblyOptions(file) { - // This is where you configure your auth key, auth secret, and template ID - // /uppy/docs/transloadit/#getAssemblyOptions-file - // - // It is important to set the secret in production: - // https://transloadit.com/docs/topics/signature-authentication/ - const response = await fetch('/some-endpoint'); - return response.json(); - }, - }); -``` - -## Migrate from Uppy 2.x to 3.x - -### Uppy is pure ESM - -Following the footsteps of many packages, we now only ship Uppy core and its -plugins as -[ECMAScript Modules](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules) -(ESM). On Uppy 2.x, we were shipping CommonJS. - -If are already using ESM yourself, or are using the CDN builds, nothing changes -for you! - -If you are using CommonJS, you might need to add some tooling for everything to -work, or you might want to refactor your codebase to ESM – refer to the -[Pure ESM package](https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c) -gist for added information and help on how to do that. - -### Robodog is deprecated - -See the [Robodog migration guide](#Migrate-from-Robodog-to-Uppy-plugins). - -### `@uppy/core` - -#### Remove `AggregateError` polyfill. - -It’s supported by most modern browsers and -[can be polyfilled by the user](https://github.com/transloadit/uppy/pull/3532#discussion_r818602636) -if needed. - -To migrate: install a `AggregateError` polyfill or use `core-js`. - -#### Remove `reset()` method. - -It’s a duplicate of `cancelAll`, but with a less intention revealing name. - -To migrate: use `cancelAll`. - -#### Remove backwards compatible exports (static properties on `Uppy`)\` - -`Uppy`, `UIPlugin`, `BasePlugin`, and `debugLogger` used to also be accessible -on the `Uppy` export. This has now been removed due to the transition to ESM. - -To migrate: import the `Uppy` class by default and/or use named exports for -everything else. - -#### `uppy.validateRestrictions()` now returns a `RestrictionError` - -This method used to return `{ result: false, reason: err.message }`, but that -felt strange as it tries to mimic an error. Instead it now return a -`RestrictionError`, which is extended `Error` class. - -To migrate: check the return value, if it’s defined you have an error, otherwise -all went well. Note that the error is `return`’ed, it’s not `throw`’n, so you -don’t have to `catch` it. - -### `@uppy/transloadit` - -Remove export of `ALLOWED_COMPANION_PATTERN`, `COMPANION`, and -`COMPANION_PATTERN` in favor of `COMPANION_URL` and `COMPANION_ALLOWED_HOSTS`. -This is to have more intention revealing names, `COMPANION` sounds like the -Companion instance, `COMPANION_URL` makes it more clear that it’s a URL. - -These are properties can now be imported and used for remote sources plugins -when using Transloadit: - -```js -import { COMPANION_URL, COMPANION_ALLOWED_HOSTS } from '@uppy/transloadit'; - -// ... -uppy.use(Dropbox, { - companionUrl: COMPANION_URL, - companionAllowedHosts: COMPANION_ALLOWED_HOSTS, -}); -``` - -### `@uppy/aws-s3-multipart` - -#### Make `headers` inside the return value of [`prepareUploadParts`](/docs/aws-s3-multipart/#prepareUploadParts-file-partData) part-indexed too. - -This is to allow custom headers to be set per part. See this -[issue](https://github.com/transloadit/uppy/issues/3881) for details. - -To migrate: make headers part indexed like `presignedUrls`: -`{ "headers": { "1": { "Content-MD5": "foo" } }}`. - -#### Remove `client` getter and setter. - -It’s internal usage only. - -To migrate: use exposed options only. - -### `@uppy/tus/`, `@uppy/aws-s3`, `@uppy/xhr-upload` - -Rename `metaFields` option to `allowedMetaFields`. Counter intuitively, -`metaFields` is for _filtering_ which `metaFields` to send along with the -request, not for adding extra meta fields to a request. As a lot of people were -confused by this, and the name overlaps with the -[`metaFields` option from Dashboard](/docs/dashboard/#metaFields), we renamed -it. - -To migrate: use `allowedMetaFields`. - -### `@uppy/react` - -#### Uppy dependencies have become peer dependencies - -`@uppy/dashboard`, `@uppy/drag-drop`, `@uppy/file-input`, `@uppy/progress-bar`, -and `@uppy/status-bar` are now peer dependencies. This means you don’t install -all these packages if you only need one. - -To migrate: install only the packages you need. If you use the Dashboard -component, you need `@uppy/dashboard`, and so onwards. - -#### Don’t expose `validProps` on the exported components. - -It’s internal usage only. - -To migrate: use exposed options only. - -### `@uppy/svelte` - -`@uppy/dashboard`, `@uppy/drag-drop`, `@uppy/progress-bar`, and -`@uppy/status-bar` are now peer dependencies. This means you don’t install all -these packages if you only need one. - -To migrate: install only the packages you need. If you use the Dashboard -component, you need `@uppy/dashboard`, and so onwards. - -### `@uppy/vue` - -`@uppy/dashboard`, `@uppy/drag-drop`, `@uppy/file-input`, `@uppy/progress-bar`, -and `@uppy/status-bar` are now peer dependencies. This means you don’t install -all these packages if you only need one. - -To migrate: install only the packages you need. If you use the Dashboard -component, you need `@uppy/dashboard`, and so onwards. - -### `@uppy/store-redux` - -Remove backwards compatible exports (static properties on `ReduxStore`). -Exports, such as `reducer`, used to also be accessible on the `ReduxStore` -export. This has now been removed due to the transition to ESM. - -To migrate: use named imports. - -### `@uppy/thumbnail-generator` - -Remove `rotateImage`, `protect`, and `canvasToBlob` from the plugin prototype. -They are internal usage only. - -To migrate: use exposed options only. - -### Known issues - -- [`ERESOLVE could not resolve` on npm install](https://github.com/transloadit/uppy/issues/4057). -- [@uppy/svelte reports a broken dependency with the Vite bundler](https://github.com/transloadit/uppy/issues/4069). - -## Migrate from Companion 3.x to 4.x - -### Minimum required Node.js version is v14.20.0 - -Aligning with the Node.js -[Long Term Support (LTS) schedule](https://nodejs.org/en/about/releases/) and to -use modern syntax features. - -### `companion.app()` returns `{ app, emitter }` instead of `app` - -Companion 3.x provides the emitter as `companionEmitter` on `app`. As of 4.x, an -object is returned with an `app` property (express middleware) and an `emitter` -property (event emitter). This provides more flexibility in the future and -follows best practices. - -### Removed `searchProviders` wrapper object inside `providerOptions` - -To use [`@uppy/unsplash`](/docs/unsplash), you had to configure Unsplash in -Companion inside `providerOptions.searchProviders`. This is redundant, Unsplash -is a provider as well so we removed the wrapper object. - -### Moved the `s3` options out of `providerOptions` - -To use AWS S3 for storage, you configured the `s3` object inside -`providerOptions`. But as S3 is not a provider but a destination. To avoid -confusion we moved the `s3` settings to the root settings object. - -### Removed compatibility for legacy Custom Provider implementations - -[Custom Provider](/docs/companion/#Adding-custom-providers) implementations must -use the Promise API. The callback API is no longer supported. - -### Default to no ACL for AWS S3 - -Default to no -[ACL](https://docs.aws.amazon.com/AmazonS3/latest/userguide/acl-overview.html) -for S3 uploads. Before the default was `public-read` but AWS now discourages -ACLs. The environment variable `COMPANION_AWS_DISABLE_ACL` is also removed, -instead Companion only uses `COMPANION_AWS_ACL`. - -### `protocol` sent from Uppy in any `get` request is now required (before it would default to Multipart). - -If you use any official Uppy plugins, then no migration is needed. For custom -plugins that talk to Companion, make to send along the `protocol` header with a -value of `multipart`, `s3Multipart`, or `tus`. - -### `emitSuccess` and `emitError` are now private methods on the `Uploader` class. - -It’s unlikely you’re using this, but it’s technically a breaking change. In -general, don’t depend on implicitly internal methods, use exposed APIs instead. - -### Removed `chunkSize` backwards compatibility for AWS S3 Multipart - -`chunkSize` option will now be used as `partSize` in AWS multipart. Before only -valid values would be respected. Invalid values would be ignored. Now any value -will be passed on to the AWS SDK, possibly throwing an error on invalid values. - -### Removed backwards compatibility for `/metrics` endpoint - -The `metrics` option is a boolean flag to tell Companion whether to provide an -endpoint `/metrics` with Prometheus metrics. Metrics will now always be served -under `options.server.path`. Before v4.x, it would always be served under the -root. - -For example: if `{ options: { metrics: true, server: { path: '/companion' }}}`, -metrics will now be served under `/companion/metrics`. In v3.x, the metrics -would be served under `/metrics`. - -## Migrate from Uppy 1.x to 2.x - -### New bundle requires manual polyfilling - -With 2.0, following in the footsteps of Microsoft, we are dropping support for -IE11. As a result, we are able to remove all built-in polyfills, and the new -bundle size is **25% smaller**! If you want your app to still support older -browsers (such as IE11), you may need to add the following polyfills to your -bundle: - -- [abortcontroller-polyfill](https://github.com/mo/abortcontroller-polyfill) -- [core-js](https://github.com/zloirock/core-js) -- [md-gum-polyfill](https://github.com/mozdevs/mediaDevices-getUserMedia-polyfill) -- [resize-observer-polyfill](https://github.com/que-etc/resize-observer-polyfill) -- [whatwg-fetch](https://github.com/github/fetch) - -If you’re using a bundler, you need import these before Uppy: - -```js -import 'core-js'; -import 'whatwg-fetch'; -import 'abortcontroller-polyfill/dist/polyfill-patch-fetch'; -// Order matters here: AbortController needs fetch, which needs Promise (provided by core-js). - -import 'md-gum-polyfill'; -import ResizeObserver from 'resize-observer-polyfill'; - -window.ResizeObserver ??= ResizeObserver; - -export { default } from '@uppy/core'; -export * from '@uppy/core'; -``` - -If you’re using Uppy from a CDN, we now provide two bundles: one for up-to-date -browsers that do not include polyfills and use modern syntax, and one for legacy -browsers. When migrating, be mindful about the types of browsers you want to -support: - -```html - - - - - - -``` - -Please note that while you may be able to get 2.0 to work in IE11 this way, we -do not officially support it anymore. - -### Use `BasePlugin` or `UIPlugin` instead of `Plugin` - -[`@uppy/core`][core] used to provide a `Plugin` class for creating plugins. This -was used for any official plugin, but also for users who want to create their -own custom plugin. But, `Plugin` always came bundled with Preact, even if the -plugin itself didn’t add any UI elements. - -`Plugin` has been replaced with `BasePlugin` and `UIPlugin`. `BasePlugin` is the -minimum you need to create a plugin and `UIPlugin` adds Preact for rendering -user interfaces. - -You can import them from [`@uppy/core`][core]: - -```js -import { BasePlugin, UIPlugin } from '@uppy/core'; -``` - -**Note:** some bundlers will include `UIPlugin` (and thus Preact) if you import -from `@uppy/core`. To make sure this does not happen, you can import `Uppy` and -`BasePlugin` directly: - -```js -import Uppy from '@uppy/core/lib/Uppy.js'; -import BasePlugin from '@uppy/core/lib/BasePlugin.js'; -``` - -### Use the latest Preact for your Uppy plugins - -Official plugins have already been upgraded. If you are using any custom -plugins, upgrade Preact to the latest version. At the time of writing this is -`10.5.13`. - -### Set plugin titles from locales - -Titles for plugins used to be set with the `title` property in the plugin -options, but all other strings are set in `locale`. This has now been aligned. -You should set your plugin title from the `locale` property. - -Before - -```js -import Webcam from '@uppy/webcam'; - -uppy.use(Webcam, { - title: 'Some title', -}); -``` - -After - -```js -import Webcam from '@uppy/webcam'; - -uppy.use(Webcam, { - locale: { - strings: { - title: 'Some title', - }, - }, -}); -``` - -### Initialize Uppy with the `new` keyword - -The default export `Uppy` is no longer callable as a function. This means you -construct the `Uppy` instance using the `new` keyword. - -```js -import Uppy from '@uppy/core'; - -const uppy = new Uppy(); // correct. - -const otherUppy = Uppy(); // incorrect, will throw. -``` - -### Rename `allowMultipleUploads` to `allowMultipleUploadBatches` - -[`allowMultipleUploadBatches`](/docs/uppy/#allowmultipleuploadbatches) means -allowing several calls to [`.upload()`](/docs/uppy/#upload), in other words, a -user can add more files after already having uploaded some. - - - -We have renamed this to be more intention revealing that this is about uploads, -and not whether a user can choose multiple files for one upload. - -```js -const uppy = new Uppy({ - allowMultipleUploadBatches: true, -}); -``` - -### New default limits for [`@uppy/xhr-upload`][xhr] and [`@uppy/tus`][tus] - -The default limit has been changed from `0` to `5`. Setting this to `0` means no -limit on concurrent uploads. - -You can change the limit on the Tus and XHR plugin options. - -```js -uppy.use(Tus, { - // ... - limit: 10, -}); -``` - -```js -uppy.use(XHRUpload, { - // ... - limit: 10, -}); -``` - -### TypeScript changes - -Uppy used to have loose types by default and strict types as an opt-in. The -default export was a function that returned the `Uppy` class, and the types came -bundled with the default export (`Uppy.SomeType`). - -```ts -import Uppy from '@uppy/core'; -import Tus from '@uppy/tus'; - -const uppy = Uppy(); - -uppy.use(Tus, { - invalidOption: null, // this will make the compilation fail! -}); -``` - -Uppy is now strictly typed by default and loose types have been removed. - -```ts -// ... - -const uppy = new Uppy(); - -uppy.use(Tus, { - invalidOption: null, // this will make the compilation fail! -}); -``` - -Uppy types are now individual exports and should be imported separately. - - - -```ts -import type { PluginOptions, UIPlugin, PluginTarget } from '@uppy/core'; -``` - -#### Event types - -[`@uppy/core`][core] provides an [`.on`](/docs/uppy/#uppy-on-39-event-39-action) -method to listen to [events](/docs/uppy/#Events). The types for these events -were loose and allowed for invalid events to be passed, such as -`uppy.on('upload-errrOOOoooOOOOOrrrr')`. - - - -```ts -// Before: - -type Meta = { myCustomMetadata: string }; - -// Invalid event -uppy.on('upload-errrOOOoooOOOOOrrrr', () => { - // ... -}); - -// After: - -// Normal event signature -uppy.on('complete', (result) => { - const successResults = result.successful; -}); - -// Custom signature -type Meta = { myCustomMetadata: string }; - -// Notice how the custom type has now become the second argument -uppy.on<'complete', Meta>('complete', (result) => { - // The passed type is now merged into the `meta` types. - const meta = result.successful[0].meta.myCustomMetadata; -}); -``` - -Plugins that add their own events can merge with existing ones in `@uppy/core` -with `declare module '@uppy/core' { ... }`. This is a TypeScript pattern called -[module augmentation](https://www.typescriptlang.org/docs/handbook/declaration-merging.html#module-augmentation). -For instance, when using [`@uppy/dashboard`][dashboard]: - - - -```ts -uppy.on('dashboard:file-edit-start', (file) => { - const fileName = file.name; -}); -``` - -### Changes to pre-signing URLs for [`@uppy/aws-s3-multipart`][aws-s3-multipart] - -See the Uppy 2.0.0 announcement post about the batch -[pre-signing URLs change](/blog/2021/08/2.0/#Batch-pre-signing-URLs-for-AWS-S3-Multipart). - -`prepareUploadPart` has been renamed to -[`prepareUploadParts`](/docs/aws-s3-multipart/#prepareUploadParts-file-partData) -(plural). See the documentation link on how to use this function. - -### Removed the `.run` method from [`@uppy/core`][core] - -The `.run` method on the `Uppy` instance has been removed. This method was -already obsolete and only logged a warning. As of this major version, it no -longer exists. - -### Removed `resume` and `removeFingerprintOnSuccess` options from [`@uppy/tus`][tus] - -Tus will now by default try to resume uploads if the upload has been started in -the past. - -This also means tus will store some data in localStorage for each upload, which -will automatically be removed on success. Making `removeFingerprintOnSuccess` -obsolete too. - -### That’s it! - -Uppy 1.0 will continue to receive bug fixes for three more months (until -), security fixes for one more -year (until ), but no more -new features after today. Exceptions are unlikely, but _can_ be made – to -accommodate those with commercial support contracts, for example. - -We hope you’ll waste no time in taking Uppy 2.0 out for a walk. When you do, -please let us know what you thought of it on -[Reddit](https://www.reddit.com/r/javascript/comments/penbr7/uppy_file_uploader_20_smaller_and_faster_modular/), -[HN](https://news.ycombinator.com/item?id=28359287), ProductHunt, or -[Twitter](https://twitter.com/uppy_io/status/1432399270846603264). We’re howling -at the moon to hear from you! - -## Migrate from Companion 1.x to 2.x - -### Prerequisite - -Since v2, you now need to be running `node.js >= v10.20.1` to use Companion. - -### ProviderOptions - -In v2 the `google` and `microsoft` [providerOptions](/docs/companion/#Options) -have been changed to `drive` and `onedrive` respectively. - -### OAuth Redirect URIs - -On your Providers’ respective developer platforms, the OAuth redirect URIs that -you should supply has now changed from: - -`http(s)://$COMPANION_HOST_NAME/connect/$AUTH_PROVIDER/callback` in v1 - -to: - -`http(s)://$COMPANION_HOST_NAME/$PROVIDER_NAME/redirect` in v2 - -#### New Redirect URIs - -
- -| Provider | New Redirect URI | -| ------------- | ---------------------------------------------------- | -| Dropbox | `https://$COMPANION_HOST_NAME/dropbox/redirect` | -| Google Drive | `https://$COMPANION_HOST_NAME/drive/redirect` | -| Google Photos | `https://$COMPANION_HOST_NAME/googlephotos/redirect` | -| OneDrive | `https://$COMPANION_HOST_NAME/onedrive/redirect` | -| Box | `https://$YOUR_COMPANION_HOST_NAME/box/redirect` | -| Facebook | `https://$COMPANION_HOST_NAME/facebook/redirect` | -| Instagram | `https://$COMPANION_HOST_NAME/instagram/redirect` | - -
- - - -[core]: /docs/uppy/ -[xhr]: /docs/xhr-upload/ -[dashboard]: /docs/dashboard/ -[aws-s3-multipart]: /docs/aws-s3-multipart/ -[tus]: /docs/tus/ diff --git a/docs/locales.mdx b/docs/locales.mdx deleted file mode 100644 index 89917aeb3..000000000 --- a/docs/locales.mdx +++ /dev/null @@ -1,1562 +0,0 @@ ---- -sidebar_position: 10 ---- - -# Internationalisation - -Uppy speaks many languages, English being the default. You can use a locale pack -to translate Uppy into your language of choice. - -:::tip - -Checkout -[`@uppy/locales`](https://github.com/transloadit/uppy/tree/main/packages/%40uppy/locales) -on GitHub to see the list of supported languages. - -::: - -## Using a locale pack from npm - -This is the recommended way. Install `@uppy/locales` package from npm, then -choose the locale you’d like to use: `@uppy/locales/lib/LANGUAGE_CODE`. - -```bash -npm i @uppy/core @uppy/locales -``` - -```js -import Uppy from '@uppy/core'; -import German from '@uppy/locales/lib/de_DE'; -// see below for the full list of locales -const uppy = new Uppy({ - debug: true, - locale: German, -}); -``` - -## Using a locale pack from CDN - -Add a ` - - - -``` - -## Overriding locale strings for a specific plugin - -Many plugins come with their own locale strings, and the packs we provide -consist of most of those strings. You can, however, override a locale string for -a specific plugin, regardless of whether you are using locale pack or not. See -the plugin documentation for the list of locale strings it uses. - -```js -import Uppy from '@uppy/core'; -import DragDrop from '@uppy/drag-drop'; -import Russian from '@uppy/locales/lib/ru_RU'; - -const uppy = new Uppy({ - debug: true, - autoProceed: true, - locale: Russian, -}); -uppy.use(DragDrop, { - target: '.UppyDragDrop', - // We are using the ru_RU locale pack (set above in Uppy options), - // but you can also override specific strings like so: - locale: { - strings: { - browse: 'выберите ;-)', - }, - }, -}); -``` - -## List of locales - -
${JSON.stringify(login)}
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
38 LocalesNPMCDNSource on GitHub
- Arabic Saudi Arabia - - - - @uppy/locales - - /lib/ar_SA - - - - ar_SA.min.js - - - ✏️{' '} - - ar_SA.js - -
- Bulgarian Bulgaria - - - - @uppy/locales - - /lib/bg_BG - - - - bg_BG.min.js - - - ✏️{' '} - - bg_BG.js - -
- Chinese China - - - - @uppy/locales - - /lib/zh_CN - - - - zh_CN.min.js - - - ✏️{' '} - - zh_CN.js - -
- Chinese Taiwan - - - - @uppy/locales - - /lib/zh_TW - - - - zh_TW.min.js - - - ✏️{' '} - - zh_TW.js - -
- Croatian Croatia - - - - @uppy/locales - - /lib/hr_HR - - - - hr_HR.min.js - - - ✏️{' '} - - hr_HR.js - -
- Czech Czechia - - - - @uppy/locales - - /lib/cs_CZ - - - - cs_CZ.min.js - - - ✏️{' '} - - cs_CZ.js - -
- Danish Denmark - - - - @uppy/locales - - /lib/da_DK - - - - da_DK.min.js - - - ✏️{' '} - - da_DK.js - -
- Dutch Netherlands - - - - @uppy/locales - - /lib/nl_NL - - - - nl_NL.min.js - - - ✏️{' '} - - nl_NL.js - -
- English United States - - - - @uppy/locales - - /lib/en_US - - - - en_US.min.js - - - ✏️{' '} - - en_US.js - -
- Finnish Finland - - - - @uppy/locales - - /lib/fi_FI - - - - fi_FI.min.js - - - ✏️{' '} - - fi_FI.js - -
- French France - - - - @uppy/locales - - /lib/fr_FR - - - - fr_FR.min.js - - - ✏️{' '} - - fr_FR.js - -
- Galician Spain - - - - @uppy/locales - - /lib/gl_ES - - - - gl_ES.min.js - - - ✏️{' '} - - gl_ES.js - -
- German Germany - - - - @uppy/locales - - /lib/de_DE - - - - de_DE.min.js - - - ✏️{' '} - - de_DE.js - -
- Greek Greece - - - - @uppy/locales - - /lib/el_GR - - - - el_GR.min.js - - - ✏️{' '} - - el_GR.js - -
- Hebrew Israel - - - - @uppy/locales - - /lib/he_IL - - - - he_IL.min.js - - - ✏️{' '} - - he_IL.js - -
- Hindi India - - - - @uppy/locales - - /lib/hi_IN - - - - hi_IN.min.js - - - ✏️{' '} - - hi_IN.js - -
- Hungarian Hungary - - - - @uppy/locales - - /lib/hu_HU - - - - hu_HU.min.js - - - ✏️{' '} - - hu_HU.js - -
- Icelandic Iceland - - - - @uppy/locales - - /lib/is_IS - - - - is_IS.min.js - - - ✏️{' '} - - is_IS.js - -
- Indonesian Indonesia - - - - @uppy/locales - - /lib/id_ID - - - - id_ID.min.js - - - ✏️{' '} - - id_ID.js - -
- Italian Italy - - - - @uppy/locales - - /lib/it_IT - - - - it_IT.min.js - - - ✏️{' '} - - it_IT.js - -
- Japanese Japan - - - - @uppy/locales - - /lib/ja_JP - - - - ja_JP.min.js - - - ✏️{' '} - - ja_JP.js - -
- Korean South Korea - - - - @uppy/locales - - /lib/ko_KR - - - - ko_KR.min.js - - - ✏️{' '} - - ko_KR.js - -
- Norwegian Bokmål Norway - - - - @uppy/locales - - /lib/nb_NO - - - - nb_NO.min.js - - - ✏️{' '} - - nb_NO.js - -
- Persian Iran - - - - @uppy/locales - - /lib/fa_IR - - - - fa_IR.min.js - - - ✏️{' '} - - fa_IR.js - -
- Polish Poland - - - - @uppy/locales - - /lib/pl_PL - - - - pl_PL.min.js - - - ✏️{' '} - - pl_PL.js - -
- Portuguese Brazil - - - - @uppy/locales - - /lib/pt_BR - - - - pt_BR.min.js - - - ✏️{' '} - - pt_BR.js - -
- Portuguese Portugal - - - - @uppy/locales - - /lib/pt_PT - - - - pt_PT.min.js - - - ✏️{' '} - - pt_PT.js - -
- Romanian Romania - - - - @uppy/locales - - /lib/ro_RO - - - - ro_RO.min.js - - - ✏️{' '} - - ro_RO.js - -
- Russian Russia - - - - @uppy/locales - - /lib/ru_RU - - - - ru_RU.min.js - - - ✏️{' '} - - ru_RU.js - -
- Serbian Serbia - (Cyrillic) - - - - @uppy/locales - - /lib/sr_RS_Cyrillic - - - - sr_RS_Cyrillic.min.js - - - ✏️{' '} - - sr_RS_Cyrillic.js - -
- Serbian Serbia - (Latin) - - - - @uppy/locales - - /lib/sr_RS_Latin - - - - sr_RS_Latin.min.js - - - ✏️{' '} - - sr_RS_Latin.js - -
- Slovak Slovakia - - - - @uppy/locales - - /lib/sk_SK - - - - sk_SK.min.js - - - ✏️{' '} - - sk_SK.js - -
- Spanish Spain - - - - @uppy/locales - - /lib/es_ES - - - - es_ES.min.js - - - ✏️{' '} - - es_ES.js - -
- Spanish Mexico - - - - @uppy/locales - - /lib/es_MX - - - - es_MX.min.js - - - ✏️{' '} - - es_MX.js - -
- Swedish Sweden - - - - @uppy/locales - - /lib/sv_SE - - - - sv_SE.min.js - - - ✏️{' '} - - sv_SE.js - -
- Thai Thailand - - - - @uppy/locales - - /lib/th_TH - - - - th_TH.min.js - - - ✏️{' '} - - th_TH.js - -
- Turkish Turkey - - - - @uppy/locales - - /lib/tr_TR - - - - tr_TR.min.js - - - ✏️{' '} - - tr_TR.js - -
- Ukrainian Ukraine - - - - @uppy/locales - - /lib/uk_UA - - - - uk_UA.min.js - - - ✏️{' '} - - uk_UA.js - -
- Uzbek Uzbekistan - - - - @uppy/locales - - /lib/uz_UZ - - - - uz_UZ.min.js - - - ✏️{' '} - - uz_UZ.js - -
- Vietnamese Vietnam - - - - @uppy/locales - - /lib/vi_VN - - - - vi_VN.min.js - - - ✏️{' '} - - vi_VN.js - -
- -## Contributing a new language - -If you speak a language we don’t yet support, you can contribute! Here’s how you -do it: - -1. Go to the - [uppy/locales](https://github.com/transloadit/uppy/tree/main/packages/%40uppy/locales/src) - directory in the Uppy GitHub repo. -2. Go to `en_US.js` and copy its contents, as English is the most up-to-date - locale. -3. Press “Create new file”, name it according to the - [`language_COUNTRY` format](http://www.i18nguy.com/unicode/language-identifiers.html), - make sure to use underscore `_` as a divider. Examples: `en_US`, `en_GB`, - `ru_RU`, `ar_AE`. Variants should be trailing, for example `sr_RS_Latin` for - Serbian Latin vs Cyrillic. -4. If your language has different pluralization rules than English, update the - `pluralize` implementation. If you are unsure how to do this, please ask us - for help in a [GitHub issue](https://github.com/transloadit/uppy/issues/new). -5. Paste what you’ve copied from `en_US.js` and use it as a starting point to - translate strings into your language. -6. When you are ready, save the file — this should create a PR that we’ll then - review 🎉 Thanks! diff --git a/docs/presets/_category_.json b/docs/presets/_category_.json deleted file mode 100644 index 467a5e48a..000000000 --- a/docs/presets/_category_.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "label": "Presets", - "position": 9 -} diff --git a/docs/presets/remote-sources.mdx b/docs/presets/remote-sources.mdx deleted file mode 100644 index 5bcb0423d..000000000 --- a/docs/presets/remote-sources.mdx +++ /dev/null @@ -1,179 +0,0 @@ ---- -sidebar_position: 1 -slug: /remote-sources ---- - -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; - -import UppyCdnExample from '/src/components/UppyCdnExample'; - -# Remote sources - -`@uppy/remote-sources` is a preset plugin (meaning it bundles and sets up other -plugins) to add all the available remote sources, such Instagram, Google Drive, -Dropbox, and others to Uppy Dashboard in one package. - -:::note - -Remote Sources requires Dashboard and automatically installs all its plugins to -it. - -::: - -## When should I use it? - -`@uppy/remote-sources` aims to simplify the setup for adding Companion plugins, -when you want to share the configuration between plugins. If you want your users -to upload files from any of the remote sources that Uppy offers, this plugin is -recommended. - -A [Companion](/docs/companion) instance is required for the Remote Sources -plugin to work. Companion handles authentication with the remote services (such -as Facebook, Dropbox, etc.), downloads the files, and uploads them to the -destination. This saves the user bandwidth, especially helpful if they are on a -mobile connection. - -You can self-host Companion or get a hosted version with any -[Transloadit plan](https://transloadit.com/pricing/). - -## Install - - - - -```shell -npm install @uppy/remote-sources -``` - - - - - -```shell -yarn add @uppy/remote-sources -``` - - - - - - {` - import { RemoteSources } from "{{UPPY_JS_URL}}" - const RemoteSources = new Uppy().use(RemoteSources) - `} - - - - -## Use - -```js {10} showLineNumbers -import Uppy from '@uppy/core'; -import Dashboard from '@uppy/dashboard'; -import RemoteSources from '@uppy/remote-sources'; - -import '@uppy/core/dist/style.min.css'; -import '@uppy/dashboard/dist/style.min.css'; - -new Uppy(); - .use(Dashboard); - .use(RemoteSources, { companionUrl: 'https://your-companion-url' }); -``` - -### Use with Transloadit - -```js -import { COMPANION_URL, COMPANION_ALLOWED_HOSTS } from '@uppy/transloadit'; -import RemoteSources from '@uppy/remote-sources'; - -uppy.use(RemoteSources, { - companionUrl: COMPANION_URL, - companionAllowedHosts: COMPANION_ALLOWED_HOSTS, -}); -``` - -You may also hit rate limits, because the OAuth application is shared between -everyone using Transloadit. - -To solve that, you can use your own OAuth keys with Transloadit’s hosted -Companion servers by using Transloadit Template Credentials. [Create a Template -Credential][template-credentials] on the Transloadit site. Select “Companion -OAuth” for the service, and enter the key and secret for the provider you want -to use. Then you can pass the name of the new credentials to that provider: - -```js -import { COMPANION_URL, COMPANION_ALLOWED_HOSTS } from '@uppy/transloadit'; -import RemoteSources from '@uppy/remote-sources'; - -uppy.use(RemoteSources, { - companionUrl: COMPANION_URL, - companionAllowedHosts: COMPANION_ALLOWED_HOSTS, - companionKeysParams: { - GoogleDrive: { key: '...', credentialsName: '...' }, - Dropbox: { key: '...', credentialsName: '...' }, - // ...etc - }, -}); -``` - -## API - -### Options - -#### `id` - -A unique identifier for this plugin (`string`, default: `RemoteSources`). - -#### `sources` - -List of remote sources that will be enabled (`array`, default: -`['Box', 'Dropbox', 'Facebook', 'GoogleDrive','Instagram', 'OneDrive', 'Unsplash', 'Url', 'Zoom']`). - -You don’t need to specify them manually or change them, but if you want to alter -the order in which they appear in the Dashboard, or disable some sources, this -option is for you. - -```js -uppy.use(RemoteSources, { - companionUrl: 'https://your-companion-url', - sources: ['Instagram', 'GoogleDrive', 'Unsplash', 'Url'], -}); -``` - -#### `companionUrl` - -URL to a [Companion](/docs/companion) instance (`string`, default: `null`). - -#### `companionHeaders` - -Custom headers that should be sent along to [Companion](/docs/companion) on -every request (`object`, default: `{}`). - -#### `companionAllowedHosts` - -The valid and authorized URL(s)/URL pattern(s) from which OAuth responses should -be accepted (`string | RegExp | Array`, Default: -`companionUrl`). - -This value can be a `string`, a `RegExp` object, or an array of both. - -This is useful when you have your [Companion](/docs/companion) running on -several hosts. Otherwise, the default value, which is `companionUrl`, should do -fine. - -#### `companionCookiesRule` - -This option correlates to the [`Request.credentials` value][], which tells the plugin -whether to send cookies to [Companion](/docs/companion) (`string`, default: `same-origin`). - -#### `target` - -DOM element, CSS selector, or plugin to place the drag and drop area into -(`string`, `Element`, `Function`, or `UIPlugin`, default: -[`Dashboard`](/docs/dashboard)). - -[`request.credentials` value]: - https://developer.mozilla.org/en-US/docs/Web/API/Request/credentials -[template-credentials]: - https://transloadit.com/docs/#how-to-create-template-credentials diff --git a/docs/quick-start.mdx b/docs/quick-start.mdx deleted file mode 100644 index 5236cf52c..000000000 --- a/docs/quick-start.mdx +++ /dev/null @@ -1,58 +0,0 @@ ---- -sidebar_position: 1 ---- - -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; -import UppyCdnExample from '/src/components/UppyCdnExample'; -import { QuickStartLinks } from '../src/components/QuickStartLinks/QuickStartLinks.tsx'; - -# Quick start - -Uppy is a sleek, modular JavaScript file uploader that integrates seamlessly -with any application. It’s fast, has a comprehensible API and lets you worry -about more important problems than building a file uploader. - -:::tip - -You can take Uppy for a walk inside StackBlitz with a -[minimal drag & drop](https://stackblitz.com/edit/vitejs-vite-yzbujq?file=main.js/g) -experience or a -[full featured dashboard](https://stackblitz.com/edit/vitejs-vite-zaqyaf?file=main.js). - -::: - - diff --git a/docs/sources/_category_.json b/docs/sources/_category_.json deleted file mode 100644 index e3db04f46..000000000 --- a/docs/sources/_category_.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "label": "Sources", - "position": 6 -} diff --git a/docs/sources/audio.mdx b/docs/sources/audio.mdx deleted file mode 100644 index c9528d3f2..000000000 --- a/docs/sources/audio.mdx +++ /dev/null @@ -1,133 +0,0 @@ ---- -sidebar_position: 3 -slug: /audio ---- - -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; - -import UppyCdnExample from '/src/components/UppyCdnExample'; - -# Audio - -The `@uppy/audio` plugin lets you record audio using a built-in or external -microphone, or any other audio device, on desktop and mobile. The UI shows real -time sound wavelength when recording. - -:::tip - -[Try out the live example](/examples) or take it for a spin in -[StackBlitz](https://stackblitz.com/edit/vitejs-vite-zaqyaf?file=main.js). - -::: - -## When should I use this? - -When you want users to record audio on their computer. - -## Install - - - - -```shell -npm install @uppy/audio -``` - - - - - -```shell -yarn add @uppy/audio -``` - - - - - - {` - import { Uppy, Dashboard, Audio } from "{{UPPY_JS_URL}}" - const uppy = new Uppy() - uppy.use(Dashboard, { inline: true, target: 'body' }) - uppy.use(Audio) - `} - - - - -## Use - -```js {3,7,11} showLineNumbers -import Uppy from '@uppy/core'; -import Dashboard from '@uppy/dashboard'; -import Audio from '@uppy/audio'; - -import '@uppy/core/dist/style.min.css'; -import '@uppy/dashboard/dist/style.min.css'; -import '@uppy/audio/dist/style.min.css'; - -new Uppy().use(Dashboard, { inline: true, target: 'body' }).use(Audio); -``` - -### API - -### Options - -#### `id` - -A unique identifier for this plugin (`string`, default: `'audio'`). - -#### `title` - -Configures the title / name shown in the UI, for instance, on Dashboard tabs -(`string`, default: `'Audio'`). - -#### `target` - -DOM element, CSS selector, or plugin to place the drag and drop area into -(`string`, `Element`, `Function`, or `UIPlugin`, default: -[`Dashboard`](/docs/dashboard)). - -#### `showAudioSourceDropdown` - -Configures whether to show a dropdown to select audio device (`boolean`, -default: `false`). - -#### `locale` - -```js -export default { - strings: { - pluginNameAudio: 'Audio', - // Used as the label for the button that starts an audio recording. - // This is not visibly rendered but is picked up by screen readers. - startAudioRecording: 'Begin audio recording', - // Used as the label for the button that stops an audio recording. - // This is not visibly rendered but is picked up by screen readers. - stopAudioRecording: 'Stop audio recording', - // Title on the “allow access” screen - allowAudioAccessTitle: 'Please allow access to your microphone', - // Description on the “allow access” screen - allowAudioAccessDescription: - 'In order to record audio, please allow microphone access for this site.', - // Title on the “device not available” screen - noAudioTitle: 'Microphone Not Available', - // Description on the “device not available” screen - noAudioDescription: - 'In order to record audio, please connect a microphone or another audio input device', - // Message about file size will be shown in an Informer bubble - recordingStoppedMaxSize: - 'Recording stopped because the file size is about to exceed the limit', - // Used as the label for the counter that shows recording length (`1:25`). - // This is not visibly rendered but is picked up by screen readers. - recordingLength: 'Recording length %{recording_length}', - // Used as the label for the submit checkmark button. - // This is not visibly rendered but is picked up by screen readers. - submitRecordedFile: 'Submit recorded file', - // Used as the label for the discard cross button. - // This is not visibly rendered but is picked up by screen readers. - discardRecordedFile: 'Discard recorded file', - }, -}; -``` diff --git a/docs/sources/companion-plugins/_category_.json b/docs/sources/companion-plugins/_category_.json deleted file mode 100644 index 22219e8a8..000000000 --- a/docs/sources/companion-plugins/_category_.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "label": "Companion plugins", - "position": 3, - "collapsed": false -} diff --git a/docs/sources/companion-plugins/box.mdx b/docs/sources/companion-plugins/box.mdx deleted file mode 100644 index 9312bbdbd..000000000 --- a/docs/sources/companion-plugins/box.mdx +++ /dev/null @@ -1,226 +0,0 @@ ---- -slug: /box ---- - -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; - -import UppyCdnExample from '/src/components/UppyCdnExample'; - -# Box - -The `@uppy/box` plugin lets users import files from their -[Box](https://www.box.com/en-nl/home) account. - -:::tip - -[Try out the live example](/examples) or take it for a spin in -[StackBlitz](https://stackblitz.com/edit/vitejs-vite-zaqyaf?file=main.js). - -::: - -## When should I use this? - -When you want to let users import files from their -[Box](https://www.box.com/en-nl/home) account. - -A [Companion](/docs/companion) instance is required for the Box plugin to work. -Companion handles authentication with Box, downloads the files, and uploads them -to the destination. This saves the user bandwidth, especially helpful if they -are on a mobile connection. - -You can self-host Companion or get a hosted version with any -[Transloadit plan](https://transloadit.com/pricing/). - - - - -```shell -npm install @uppy/box -``` - - - - - -```shell -yarn add @uppy/box -``` - - - - - - {` - import { Uppy, Box } from "{{UPPY_JS_URL}}" - const uppy = new Uppy() - uppy.use(Box, { - // Options - }) - `} - - - - -## Use - -Using Box requires setup in both Uppy and Companion. - -### Use in Uppy - -```js {10-13} showLineNumbers -import Uppy from '@uppy/core'; -import Dashboard from '@uppy/dashboard'; -import Box from '@uppy/box'; - -import '@uppy/core/dist/style.min.css'; -import '@uppy/dashboard/dist/style.min.css'; - -new Uppy() - .use(Dashboard, { inline: true, target: '#dashboard' }) - .use(Box, { companionUrl: 'https://your-companion.com' }); -``` - -### Use with Transloadit - -```js -import { COMPANION_URL, COMPANION_ALLOWED_HOSTS } from '@uppy/transloadit'; -import Box from '@uppy/box'; - -uppy.use(Box, { - companionUrl: COMPANION_URL, - companionAllowedHosts: COMPANION_ALLOWED_HOSTS, -}); -``` - -You may also hit rate limits, because the OAuth application is shared between -everyone using Transloadit. - -To solve that, you can use your own OAuth keys with Transloadit’s hosted -Companion servers by using Transloadit Template Credentials. [Create a Template -Credential][template-credentials] on the Transloadit site. Select “Companion -OAuth” for the service, and enter the key and secret for the provider you want -to use. Then you can pass the name of the new credentials to that provider: - -```js -import { COMPANION_URL, COMPANION_ALLOWED_HOSTS } from '@uppy/transloadit'; -import Box from '@uppy/box'; - -uppy.use(Box, { - companionUrl: COMPANION_URL, - companionAllowedHosts: COMPANION_ALLOWED_HOSTS, - companionKeysParams: { - key: 'YOUR_TRANSLOADIT_API_KEY', - credentialsName: 'my_companion_dropbox_creds', - }, -}); -``` - -### Use in Companion - -You can create a Box App on the -[Box Developers site](https://app.box.com/developers/console). - -Things to note: - -- Choose `Custom App` and select the `User Authentication (OAuth 2.0)` app type. -- You must enable full write access, or you will get - [403 when downloading files](https://support.box.com/hc/en-us/community/posts/360049195613-403-error-while-file-download-API-Call) - -You’ll be redirected to the app page. This page lists the client ID (app key) -and client secret (app secret), which you should use to configure Companion. - -The app page has a `"Redirect URIs"` field. Here, add: - -``` -https://$YOUR_COMPANION_HOST_NAME/box/redirect -``` - -If you are using Transloadit hosted Companion: - -``` -https://api2.transloadit.com/companion/box/redirect -``` - -You can only use the integration with your own account initially. Make sure to -apply for production status on the app page before you publish your app, or your -users will not be able to sign in! - -Configure the Box key and secret. With the standalone Companion server, specify -environment variables: - -```shell -export COMPANION_BOX_KEY="Box API key" -export COMPANION_BOX_SECRET="Box API secret" -``` - -When using the Companion Node.js API, configure these options: - -```js -companion.app({ - providerOptions: { - box: { - key: 'Box API key', - secret: 'Box API secret', - }, - }, -}); -``` - -## API - -### Options - -#### `id` - -A unique identifier for this plugin (`string`, default: `'Box'`). - -#### `title` - -Title / name shown in the UI, such as Dashboard tabs (`string`, default: -`'Box'`). - -#### `target` - -DOM element, CSS selector, or plugin to place the drag and drop area into -(`string`, `Element`, `Function`, or `UIPlugin`, default: -[`Dashboard`](/docs/dashboard)). - -#### `companionUrl` - -URL to a [Companion](/docs/companion) instance (`string`, default: `null`). - -#### `companionHeaders` - -Custom headers that should be sent along to [Companion](/docs/companion) on -every request (`Object`, default: `{}`). - -#### `companionAllowedHosts` - -The valid and authorised URL(s) from which OAuth responses should be accepted -(`string` or `RegExp` or `Array`, default: `companionUrl`). - -This value can be a `string`, a `RegExp` pattern, or an `Array` of both. This is -useful when you have your [Companion](/docs/companion) running on several hosts. -Otherwise, the default value should do fine. - -#### `companionCookiesRule` - -This option correlates to the -[RequestCredentials value](https://developer.mozilla.org/en-US/docs/Web/API/Request/credentials) -(`string`, default: `'same-origin'`). - -This tells the plugin whether to send cookies to [Companion](/docs/companion). - -#### `locale` - -```js -export default { - strings: { - pluginNameBox: 'Box', - }, -}; -``` - -[template-credentials]: - https://transloadit.com/docs/#how-to-create-template-credentials diff --git a/docs/sources/companion-plugins/dropbox.mdx b/docs/sources/companion-plugins/dropbox.mdx deleted file mode 100644 index 4a111f35d..000000000 --- a/docs/sources/companion-plugins/dropbox.mdx +++ /dev/null @@ -1,226 +0,0 @@ ---- -slug: /dropbox ---- - -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; - -import UppyCdnExample from '/src/components/UppyCdnExample'; - -# Dropbox - -The `@uppy/dropbox` plugin lets users import files from their -[Dropbox](https://www.dropbox.com) account. - -:::tip - -[Try out the live example](/examples) or take it for a spin in -[StackBlitz](https://stackblitz.com/edit/vitejs-vite-zaqyaf?file=main.js). - -::: - -## When should I use this? - -When you want to let users import files from their -[Dropbox](https://www.dropbox.com) account. - -A [Companion](/docs/companion) instance is required for the Dropbox plugin to -work. Companion handles authentication with Dropbox, downloads the files, and -uploads them to the destination. This saves the user bandwidth, especially -helpful if they are on a mobile connection. - -You can self-host Companion or get a hosted version with any -[Transloadit plan](https://transloadit.com/pricing/). - - - - -```shell -npm install @uppy/dropbox -``` - - - - - -```shell -yarn add @uppy/dropbox -``` - - - - - - {` - import { Uppy, Dropbox } from "{{UPPY_JS_URL}}" - const uppy = new Uppy() - uppy.use(Dropbox, { - // Options - }) - `} - - - - -## Use - -Using Dropbox requires setup in both Uppy and Companion. - -### Use in Uppy - -```js {10-13} showLineNumbers -import Uppy from '@uppy/core'; -import Dashboard from '@uppy/dashboard'; -import Dropbox from '@uppy/dropbox'; - -import '@uppy/core/dist/style.min.css'; -import '@uppy/dashboard/dist/style.min.css'; - -new Uppy() - .use(Dashboard, { inline: true, target: '#dashboard' }) - .use(Dropbox, { companionUrl: 'https://your-companion.com' }); -``` - -### Use with Transloadit - -```js -import { COMPANION_URL, COMPANION_ALLOWED_HOSTS } from '@uppy/transloadit'; -import Dropbox from '@uppy/dropbox'; - -uppy.use(Dropbox, { - companionUrl: COMPANION_URL, - companionAllowedHosts: COMPANION_ALLOWED_HOSTS, -}); -``` - -You may also hit rate limits, because the OAuth application is shared between -everyone using Transloadit. - -To solve that, you can use your own OAuth keys with Transloadit’s hosted -Companion servers by using Transloadit Template Credentials. [Create a Template -Credential][template-credentials] on the Transloadit site. Select “Companion -OAuth” for the service, and enter the key and secret for the provider you want -to use. Then you can pass the name of the new credentials to that provider: - -```js -import { COMPANION_URL, COMPANION_ALLOWED_HOSTS } from '@uppy/transloadit'; -import Dropbox from '@uppy/dropbox'; - -uppy.use(Dropbox, { - companionUrl: COMPANION_URL, - companionAllowedHosts: COMPANION_ALLOWED_HOSTS, - companionKeysParams: { - key: 'YOUR_TRANSLOADIT_API_KEY', - credentialsName: 'my_companion_dropbox_creds', - }, -}); -``` - -### Use in Companion - -You can create a Dropbox App on the -[Dropbox Developers site](https://www.dropbox.com/developers/apps/create). - -Things to note: - -- Choose the “Dropbox API”, not the business variant. -- Typically you’ll want “Full Dropbox” access, unless you are absolutely certain - that you need the other one. - -You’ll be redirected to the app page. This page lists the app key and app -secret, which you should use to configure Companion as shown above. - -The app page has a “Redirect URIs” field. Here, add: - -``` -https://$YOUR_COMPANION_HOST_NAME/dropbox/redirect -``` - -If you are using Transloadit hosted Companion: - -``` -https://api2.transloadit.com/companion/dropbox/redirect -``` - -You can only use the integration with your own account initially. Make sure to -apply for production status on the app page before you publish your app, or your -users will not be able to sign in! - -Configure the Dropbox key and secret. With the standalone Companion server, -specify environment variables: - -```shell -export COMPANION_DROPBOX_KEY="Dropbox API key" -export COMPANION_DROPBOX_SECRET="Dropbox API secret" -``` - -When using the Companion Node.js API, configure these options: - -```js -companion.app({ - providerOptions: { - dropbox: { - key: 'Dropbox API key', - secret: 'Dropbox API secret', - }, - }, -}); -``` - -## API - -### Options - -#### `id` - -A unique identifier for this plugin (`string`, default: `'Dropbox'`). - -#### `title` - -Title / name shown in the UI, such as Dashboard tabs (`string`, default: -`'Dropbox'`). - -#### `target` - -DOM element, CSS selector, or plugin to place the drag and drop area into -(`string`, `Element`, `Function`, or `UIPlugin`, default: -[`Dashboard`](/docs/dashboard)). - -#### `companionUrl` - -URL to a [Companion](/docs/companion) instance (`string`, default: `null`). - -#### `companionHeaders` - -Custom headers that should be sent along to [Companion](/docs/companion) on -every request (`Object`, default: `{}`). - -#### `companionAllowedHosts` - -The valid and authorised URL(s) from which OAuth responses should be accepted -(`string` or `RegExp` or `Array`, default: `companionUrl`). - -This value can be a `string`, a `RegExp` pattern, or an `Array` of both. This is -useful when you have your [Companion](/docs/companion) running on several hosts. -Otherwise, the default value should do fine. - -#### `companionCookiesRule` - -This option correlates to the -[RequestCredentials value](https://developer.mozilla.org/en-US/docs/Web/API/Request/credentials) -(`string`, default: `'same-origin'`). - -This tells the plugin whether to send cookies to [Companion](/docs/companion). - -#### `locale` - -```js -export default { - strings: { - pluginNameDropbox: 'Dropbox', - }, -}; -``` - -[template-credentials]: - https://transloadit.com/docs/#how-to-create-template-credentials diff --git a/docs/sources/companion-plugins/facebook.mdx b/docs/sources/companion-plugins/facebook.mdx deleted file mode 100644 index dcc496809..000000000 --- a/docs/sources/companion-plugins/facebook.mdx +++ /dev/null @@ -1,223 +0,0 @@ ---- -slug: /facebook ---- - -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; - -import UppyCdnExample from '/src/components/UppyCdnExample'; - -# Facebook - -The `@uppy/facebook` plugin lets users import files from their -[Facebook](https://www.facebook.com) account. - -:::tip - -[Try out the live example](/examples) or take it for a spin in -[StackBlitz](https://stackblitz.com/edit/vitejs-vite-zaqyaf?file=main.js). - -::: - -## When should I use this? - -When you want to let users import files from their -[Facebook](https://www.facebook.com) account. - -A [Companion](/docs/companion) instance is required for the Facebook plugin to -work. Companion handles authentication with Facebook, downloads the files, and -uploads them to the destination. This saves the user bandwidth, especially -helpful if they are on a mobile connection. - -You can self-host Companion or get a hosted version with any -[Transloadit plan](https://transloadit.com/pricing/). - - - - -```shell -npm install @uppy/facebook -``` - - - - - -```shell -yarn add @uppy/facebook -``` - - - - - - {` - import { Uppy, Facebook } from "{{UPPY_JS_URL}}" - const uppy = new Uppy() - uppy.use(Facebook, { - // Options - }) - `} - - - - -## Use - -Using Facebook requires setup in both Uppy and Companion. - -### Use in Uppy - -```js {10-13} showLineNumbers -import Uppy from '@uppy/core'; -import Dashboard from '@uppy/dashboard'; -import Facebook from '@uppy/facebook'; - -import '@uppy/core/dist/style.min.css'; -import '@uppy/dashboard/dist/style.min.css'; - -new Uppy() - .use(Dashboard, { inline: true, target: '#dashboard' }) - .use(Facebook, { companionUrl: 'https://your-companion.com' }); -``` - -### Use with Transloadit - -```js -import { COMPANION_URL, COMPANION_ALLOWED_HOSTS } from '@uppy/transloadit'; -import Facebook from '@uppy/facebook'; - -uppy.use(Facebook, { - companionUrl: COMPANION_URL, - companionAllowedHosts: COMPANION_ALLOWED_HOSTS, -}); -``` - -You may also hit rate limits, because the OAuth application is shared between -everyone using Transloadit. - -To solve that, you can use your own OAuth keys with Transloadit’s hosted -Companion servers by using Transloadit Template Credentials. [Create a Template -Credential][template-credentials] on the Transloadit site. Select “Companion -OAuth” for the service, and enter the key and secret for the provider you want -to use. Then you can pass the name of the new credentials to that provider: - -```js -import { COMPANION_URL, COMPANION_ALLOWED_HOSTS } from '@uppy/transloadit'; -import Facebook from '@uppy/facebook'; - -uppy.use(Facebook, { - companionUrl: COMPANION_URL, - companionAllowedHosts: COMPANION_ALLOWED_HOSTS, - companionKeysParams: { - key: 'YOUR_TRANSLOADIT_API_KEY', - credentialsName: 'my_companion_dropbox_creds', - }, -}); -``` - -### Use in Companion - -You can create a Facebook App on the -[Facebook Developers site](https://developers.facebook.com/apps). - -The app page has a “Redirect URIs” field. Here, add: - -``` -https://$YOUR_COMPANION_HOST_NAME/facebook/redirect -``` - -If you are using Transloadit hosted Companion: - -``` -https://api2.transloadit.com/companion/facebook/redirect -``` - -You can only use the integration with your own account initially. Make sure to -apply for production status on the app page before you publish your app, or your -users will not be able to sign in! - -You need to set up OAuth in your Facebook app for Companion to be able to -connect to users’ Facebook accounts. You have to enable “Advanced Access” for -the `user_photos` permission. A precondition of that is “Business Verification” -which involves setting up a Meta Business Account and submitting documents to -prove business ownership. - -Configure the Facebook key and secret. With the standalone Companion server, -specify environment variables: - -```shell -export COMPANION_FACEBOOK_KEY="Facebook API key" -export COMPANION_FACEBOOK_SECRET="Facebook API secret" -``` - -When using the Companion Node.js API, configure these options: - -```js -companion.app({ - providerOptions: { - facebook: { - key: 'Facebook API key', - secret: 'Facebook API secret', - }, - }, -}); -``` - -## API - -### Options - -#### `id` - -A unique identifier for this plugin (`string`, default: `'Facebook'`). - -#### `title` - -Title / name shown in the UI, such as Dashboard tabs (`string`, default: -`'Facebook'`). - -#### `target` - -DOM element, CSS selector, or plugin to place the drag and drop area into -(`string`, `Element`, `Function`, or `UIPlugin`, default: -[`Dashboard`](/docs/dashboard)). - -#### `companionUrl` - -URL to a [Companion](/docs/companion) instance (`string`, default: `null`). - -#### `companionHeaders` - -Custom headers that should be sent along to [Companion](/docs/companion) on -every request (`Object`, default: `{}`). - -#### `companionAllowedHosts` - -The valid and authorised URL(s) from which OAuth responses should be accepted -(`string` or `RegExp` or `Array`, default: `companionUrl`). - -This value can be a `string`, a `RegExp` pattern, or an `Array` of both. This is -useful when you have your [Companion](/docs/companion) running on several hosts. -Otherwise, the default value should do fine. - -#### `companionCookiesRule` - -This option correlates to the -[RequestCredentials value](https://developer.mozilla.org/en-US/docs/Web/API/Request/credentials) -(`string`, default: `'same-origin'`). - -This tells the plugin whether to send cookies to [Companion](/docs/companion). - -#### `locale` - -```js -export default { - strings: { - pluginNameFacebook: 'Facebook', - }, -}; -``` - -[template-credentials]: - https://transloadit.com/docs/#how-to-create-template-credentials diff --git a/docs/sources/companion-plugins/google-drive.mdx b/docs/sources/companion-plugins/google-drive.mdx deleted file mode 100644 index c682464ec..000000000 --- a/docs/sources/companion-plugins/google-drive.mdx +++ /dev/null @@ -1,227 +0,0 @@ ---- -slug: /google-drive ---- - -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; - -import UppyCdnExample from '/src/components/UppyCdnExample'; - -# Google Drive - -The `@uppy/google-drive` plugin lets users import files from their -[Google Drive](https://drive.google.com) account. - -:::tip - -[Try out the live example](/examples) or take it for a spin in -[StackBlitz](https://stackblitz.com/edit/vitejs-vite-zaqyaf?file=main.js). - -::: - -## When should I use this? - -When you want to let users import files from their -[Google Drive](https://drive.google.com) account. - -A [Companion](/docs/companion) instance is required for the Google Drive plugin -to work. Companion handles authentication with Google Drive, downloads the -files, and uploads them to the destination. This saves the user bandwidth, -especially helpful if they are on a mobile connection. - -You can self-host Companion or get a hosted version with any -[Transloadit plan](https://transloadit.com/pricing/). - - - - -```shell -npm install @uppy/google-drive -``` - - - - - -```shell -yarn add @uppy/google-drive -``` - - - - - - {` - import { Uppy, GoogleDrive } from "{{UPPY_JS_URL}}" - const uppy = new Uppy() - uppy.use(GoogleDrive, { - // Options - }) - `} - - - - -## Use - -Using Google Drive requires setup in both Uppy and Companion. - -### Use in Uppy - -```js {10-13} showLineNumbers -import Uppy from '@uppy/core'; -import Dashboard from '@uppy/dashboard'; -import GoogleDrive from '@uppy/google-drive'; - -import '@uppy/core/dist/style.min.css'; -import '@uppy/dashboard/dist/style.min.css'; - -new Uppy() - .use(Dashboard, { inline: true, target: '#dashboard' }) - .use(GoogleDrive, { companionUrl: 'https://your-companion.com' }); -``` - -### Use with Transloadit - -```js -import { COMPANION_URL, COMPANION_ALLOWED_HOSTS } from '@uppy/transloadit'; -import GoogleDrive from '@uppy/google-drive'; - -uppy.use(GoogleDrive, { - companionUrl: COMPANION_URL, - companionAllowedHosts: COMPANION_ALLOWED_HOSTS, -}); -``` - -You may also hit rate limits, because the OAuth application is shared between -everyone using Transloadit. - -To solve that, you can use your own OAuth keys with Transloadit’s hosted -Companion servers by using Transloadit Template Credentials. [Create a Template -Credential][template-credentials] on the Transloadit site. Select “Companion -OAuth” for the service, and enter the key and secret for the provider you want -to use. Then you can pass the name of the new credentials to that provider: - -```js -import { COMPANION_URL, COMPANION_ALLOWED_HOSTS } from '@uppy/transloadit'; -import GoogleDrive from '@uppy/google-drive'; - -uppy.use(GoogleDrive, { - companionUrl: COMPANION_URL, - companionAllowedHosts: COMPANION_ALLOWED_HOSTS, - companionKeysParams: { - key: 'YOUR_TRANSLOADIT_API_KEY', - credentialsName: 'my_companion_dropbox_creds', - }, -}); -``` - -### Use in Companion - -To sign up for API keys, go to the -[Google Developer Console](https://console.developers.google.com/). - -Create a project for your app if you don’t have one yet. - -- On the project’s dashboard, - [enable the Google Drive API](https://developers.google.com/drive/api/v3/enable-drive-api). -- [Set up OAuth authorization](https://developers.google.com/drive/api/v3/about-auth). - - Under scopes, add the `https://www.googleapis.com/auth/drive.readonly` Drive - API scope. - - Due to this being a sensitive scope, your app must complete Google’s OAuth - app verification before being granted access. See - [OAuth App Verification Help Center](https://support.google.com/cloud/answer/13463073) - for more information. - -The app page has a `"Redirect URIs"` field. Here, add: - -``` -https://$YOUR_COMPANION_HOST_NAME/drive/redirect -``` - -If you are using Transloadit hosted Companion: - -``` -https://api2.transloadit.com/companion/drive/redirect -``` - -Google will give you an OAuth client ID and client secret. - -Configure the Google key and secret in Companion. With the standalone Companion -server, specify environment variables: - -```shell -export COMPANION_GOOGLE_KEY="Google OAuth client ID" -export COMPANION_GOOGLE_SECRET="Google OAuth client secret" -``` - -When using the Companion Node.js API, configure these options: - -```js -companion.app({ - providerOptions: { - drive: { - key: 'Google OAuth client ID', - secret: 'Google OAuth client secret', - }, - }, -}); -``` - -## API - -### Options - -#### `id` - -A unique identifier for this plugin (`string`, default: `'GoogleDrive'`). - -#### `title` - -Title / name shown in the UI, such as Dashboard tabs (`string`, default: -`'GoogleDrive'`). - -#### `target` - -DOM element, CSS selector, or plugin to place the drag and drop area into -(`string`, `Element`, `Function`, or `UIPlugin`, default: -[`Dashboard`](/docs/dashboard)). - -#### `companionUrl` - -URL to a [Companion](/docs/companion) instance (`string`, default: `null`). - -#### `companionHeaders` - -Custom headers that should be sent along to [Companion](/docs/companion) on -every request (`Object`, default: `{}`). - -#### `companionAllowedHosts` - -The valid and authorised URL(s) from which OAuth responses should be accepted -(`string` or `RegExp` or `Array`, default: `companionUrl`). - -This value can be a `string`, a `RegExp` pattern, or an `Array` of both. This is -useful when you have your [Companion](/docs/companion) running on several hosts. -Otherwise, the default value should do fine. - -#### `companionCookiesRule` - -This option correlates to the -[RequestCredentials value](https://developer.mozilla.org/en-US/docs/Web/API/Request/credentials) -(`string`, default: `'same-origin'`). - -This tells the plugin whether to send cookies to [Companion](/docs/companion). - -#### `locale` - -```js -export default { - strings: { - pluginNameGoogleDrive: 'GoogleDrive', - }, -}; -``` - -[template-credentials]: - https://transloadit.com/docs/#how-to-create-template-credentials diff --git a/docs/sources/companion-plugins/google-photos.mdx b/docs/sources/companion-plugins/google-photos.mdx deleted file mode 100644 index e90cfb6e6..000000000 --- a/docs/sources/companion-plugins/google-photos.mdx +++ /dev/null @@ -1,223 +0,0 @@ ---- -slug: /google-photos ---- - -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; - -import UppyCdnExample from '/src/components/UppyCdnExample'; - -# Google Photos - -The `@uppy/google-photos` plugin lets users import files from their -[Google Photos](https://photos.google.com) account. - -:::tip - -[Try out the live example](/examples) or take it for a spin in -[StackBlitz](https://stackblitz.com/edit/vitejs-vite-zaqyaf?file=main.js). - -::: - -## When should I use this? - -When you want to let users import files from their -[Google Photos](https://photos.google.com) account. - -A [Companion](/docs/companion) instance is required for the Google Photos plugin -to work. Companion handles authentication with Google Photos, downloads the -photos/videos, and uploads them to the destination. This saves the user -bandwidth, especially helpful if they are on a mobile connection. - -You can self-host Companion or get a hosted version with any -[Transloadit plan](https://transloadit.com/pricing/). - - - - -```shell -npm install @uppy/google-photos -``` - - - - - -```shell -yarn add @uppy/google-photos -``` - - - - - - {` - import { Uppy, GooglePhotos } from "{{UPPY_JS_URL}}" - const uppy = new Uppy() - uppy.use(GooglePhotos, { - // Options - }) - `} - - - - -## Use - -Using Google Photos requires setup in both Uppy and Companion. - -### Use in Uppy - -```js {10-13} showLineNumbers -import Uppy from '@uppy/core'; -import Dashboard from '@uppy/dashboard'; -import GooglePhotos from '@uppy/google-photos'; - -import '@uppy/core/dist/style.min.css'; -import '@uppy/dashboard/dist/style.min.css'; - -new Uppy() - .use(Dashboard, { inline: true, target: '#dashboard' }) - .use(GooglePhotos, { - target: Dashboard, - companionUrl: 'https://your-companion.com', - }); -``` - -### Use with Transloadit - -```js -import { COMPANION_URL, COMPANION_ALLOWED_HOSTS } from '@uppy/transloadit'; -import GooglePhotos from '@uppy/google-photos'; - -uppy.use(GooglePhotos, { - companionUrl: COMPANION_URL, - companionAllowedHosts: COMPANION_ALLOWED_HOSTS, -}); -``` - -You may also hit rate limits, because the OAuth application is shared between -everyone using Transloadit. - -To solve that, you can use your own OAuth keys with Transloadit’s hosted -Companion servers by using Transloadit Template Credentials. [Create a Template -Credential][template-credentials] on the Transloadit site. Select “Companion -OAuth” for the service, and enter the key and secret for the provider you want -to use. Then you can pass the name of the new credentials to that provider: - -```js -import { COMPANION_URL, COMPANION_ALLOWED_HOSTS } from '@uppy/transloadit'; -import GooglePhotos from '@uppy/google-photos'; - -uppy.use(GooglePhotos, { - companionUrl: COMPANION_URL, - companionAllowedHosts: COMPANION_ALLOWED_HOSTS, - companionKeysParams: { - key: 'YOUR_TRANSLOADIT_API_KEY', - credentialsName: 'my_companion_dropbox_creds', - }, -}); -``` - -### Use in Companion - -To sign up for API keys, go to the -[Google Developer Console](https://console.developers.google.com/). - -Create a project for your app if you don’t have one yet. - -- On the project’s dashboard, - [enable the Google Photos API](https://developers.google.com/photos). -- [Set up OAuth authorization](https://developers.google.com/photos/library/guides/authorization). - -The app page has a `"Redirect URIs"` field. Here, add: - -``` -https://$YOUR_COMPANION_HOST_NAME/googlephotos/redirect -``` - -If you are using Transloadit hosted Companion: - -``` -https://api2.transloadit.com/companion/googlephotos/redirect -``` - -Google will give you an OAuth client ID and client secret. - -Configure the Google key and secret in Companion. With the standalone Companion -server, specify environment variables: - -```shell -export COMPANION_GOOGLE_KEY="Google OAuth client ID" -export COMPANION_GOOGLE_SECRET="Google OAuth client secret" -``` - -When using the Companion Node.js API, configure these options: - -```js -companion.app({ - providerOptions: { - googlephotos: { - key: 'Google OAuth client ID', - secret: 'Google OAuth client secret', - }, - }, -}); -``` - -## API - -### Options - -#### `id` - -A unique identifier for this plugin (`string`, default: `'GooglePhotos'`). - -#### `title` - -Title / name shown in the UI, such as Dashboard tabs (`string`, default: -`'GooglePhotos'`). - -#### `target` - -DOM element, CSS selector, or plugin to place the drag and drop area into -(`string` or `Element`, default: `null`). - -#### `companionUrl` - -URL to a [Companion](/docs/companion) instance (`string`, default: `null`). - -#### `companionHeaders` - -Custom headers that should be sent along to [Companion](/docs/companion) on -every request (`Object`, default: `{}`). - -#### `companionAllowedHosts` - -The valid and authorised URL(s) from which OAuth responses should be accepted -(`string` or `RegExp` or `Array`, default: `companionUrl`). - -This value can be a `string`, a `RegExp` pattern, or an `Array` of both. This is -useful when you have your [Companion](/docs/companion) running on several hosts. -Otherwise, the default value should do fine. - -#### `companionCookiesRule` - -This option correlates to the -[RequestCredentials value](https://developer.mozilla.org/en-US/docs/Web/API/Request/credentials) -(`string`, default: `'same-origin'`). - -This tells the plugin whether to send cookies to [Companion](/docs/companion). - -#### `locale` - -```js -export default { - strings: { - pluginNameGooglePhotos: 'GooglePhotos', - }, -}; -``` - -[template-credentials]: - https://transloadit.com/docs/#how-to-create-template-credentials diff --git a/docs/sources/companion-plugins/instagram.mdx b/docs/sources/companion-plugins/instagram.mdx deleted file mode 100644 index ca920d86c..000000000 --- a/docs/sources/companion-plugins/instagram.mdx +++ /dev/null @@ -1,236 +0,0 @@ ---- -slug: /instagram ---- - -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; - -import UppyCdnExample from '/src/components/UppyCdnExample'; - -# Instagram - -The `@uppy/instagram` plugin lets users import files from their -[Instagram](https://instagram.com) account. - -:::tip - -[Try out the live example](/examples) or take it for a spin in -[StackBlitz](https://stackblitz.com/edit/vitejs-vite-zaqyaf?file=main.js). - -::: - -## When should I use this? - -When you want to let users import files from their -[Instagram](https://instagram.com) account. - -A [Companion](/docs/companion) instance is required for the Instagram plugin to -work. Companion handles authentication with Instagram, downloads the files, and -uploads them to the destination. This saves the user bandwidth, especially -helpful if they are on a mobile connection. - -You can self-host Companion or get a hosted version with any -[Transloadit plan](https://transloadit.com/pricing/). - - - - -```shell -npm install @uppy/instagram -``` - - - - - -```shell -yarn add @uppy/instagram -``` - - - - - - {` - import { Uppy, Instagram } from "{{UPPY_JS_URL}}" - const uppy = new Uppy() - uppy.use(Instagram, { - // Options - }) - `} - - - - -## Use - -Using Instagram requires setup in both Uppy and Companion. - -### Use in Uppy - -```js {10-13} showLineNumbers -import Uppy from '@uppy/core'; -import Dashboard from '@uppy/dashboard'; -import Instagram from '@uppy/instagram'; - -import '@uppy/core/dist/style.min.css'; -import '@uppy/dashboard/dist/style.min.css'; - -new Uppy() - .use(Dashboard, { inline: true, target: '#dashboard' }) - .use(Instagram, { companionUrl: 'https://your-companion.com' }); -``` - -### Use with Transloadit - -```js -import { COMPANION_URL, COMPANION_ALLOWED_HOSTS } from '@uppy/transloadit'; -import Instagram from '@uppy/instagram'; - -uppy.use(Instagram, { - companionUrl: COMPANION_URL, - companionAllowedHosts: COMPANION_ALLOWED_HOSTS, -}); -``` - -You may also hit rate limits, because the OAuth application is shared between -everyone using Transloadit. - -To solve that, you can use your own OAuth keys with Transloadit’s hosted -Companion servers by using Transloadit Template Credentials. [Create a Template -Credential][template-credentials] on the Transloadit site. Select “Companion -OAuth” for the service, and enter the key and secret for the provider you want -to use. Then you can pass the name of the new credentials to that provider: - -```js -import { COMPANION_URL, COMPANION_ALLOWED_HOSTS } from '@uppy/transloadit'; -import Instagram from '@uppy/instagram'; - -uppy.use(Instagram, { - companionUrl: COMPANION_URL, - companionAllowedHosts: COMPANION_ALLOWED_HOSTS, - companionKeysParams: { - key: 'YOUR_TRANSLOADIT_API_KEY', - credentialsName: 'my_companion_dropbox_creds', - }, -}); -``` - -### Use in Companion - -To sign up for API keys, go to the -[Instagram Platform from Meta](https://developers.facebook.com/products/instagram/). - -Create a project for your app if you don’t have one yet. - -The app page has a `"Redirect URIs"` field. Here, add: - -``` -https://$YOUR_COMPANION_HOST_NAME/instagram/redirect -``` - -If you are using Transloadit hosted Companion: - -``` -https://api2.transloadit.com/companion/instagram/redirect -``` - -Meta will give you an OAuth client ID and client secret. - -Configure the Instagram key and secret in Companion. With the standalone -Companion server, specify environment variables: - -```shell -export COMPANION_INSTAGRAM_KEY="Instagram OAuth client ID" -export COMPANION_INSTAGRAM_SECRET="Instagram OAuth client secret" -``` - -When using the Companion Node.js API, configure these options: - -```js -companion.app({ - providerOptions: { - instagram: { - key: 'Instagram OAuth client ID', - secret: 'Instagram OAuth client secret', - }, - }, -}); -``` - -### Development - -Among Uppy-supported providers, Instagram is the only provider at the time of -writing that requires https even in dev mode. So, to test your integration in -development, you need to use some reverse proxy. The easiest way to do it is to -use [https://redirectmeto.com](https://redirectmeto.com). - -In your `.env`, set: - -```sh -COMPANION_DOMAIN="redirectmeto.com/http://localhost:3020" -COMPANION_PROTOCOL="https" -``` - -On -[https://developers.facebook.com/apps/.../instagram-basic-display/basic-display](https://developers.facebook.com/apps/.../instagram-basic-display/basic-display) -page, in the “Valid OAuth Redirect URIs” field, add -`https://redirectmeto.com/http://localhost:3020/instagram/redirect`. - -## API - -### Options - -#### `id` - -A unique identifier for this plugin (`string`, default: `'Instagram'`). - -#### `title` - -Title / name shown in the UI, such as Dashboard tabs (`string`, default: -`'Instagram'`). - -#### `target` - -DOM element, CSS selector, or plugin to place the drag and drop area into -(`string`, `Element`, `Function`, or `UIPlugin`, default: -[`Dashboard`](/docs/dashboard)). - -#### `companionUrl` - -URL to a [Companion](/docs/companion) instance (`string`, default: `null`). - -#### `companionHeaders` - -Custom headers that should be sent along to [Companion](/docs/companion) on -every request (`Object`, default: `{}`). - -#### `companionAllowedHosts` - -The valid and authorised URL(s) from which OAuth responses should be accepted -(`string` or `RegExp` or `Array`, default: `companionUrl`). - -This value can be a `string`, a `RegExp` pattern, or an `Array` of both. This is -useful when you have your [Companion](/docs/companion) running on several hosts. -Otherwise, the default value should do fine. - -#### `companionCookiesRule` - -This option correlates to the -[RequestCredentials value](https://developer.mozilla.org/en-US/docs/Web/API/Request/credentials) -(`string`, default: `'same-origin'`). - -This tells the plugin whether to send cookies to [Companion](/docs/companion). - -#### `locale` - -```js -export default { - strings: { - pluginNameInstagram: 'Instagram', - }, -}; -``` - -[template-credentials]: - https://transloadit.com/docs/#how-to-create-template-credentials diff --git a/docs/sources/companion-plugins/onedrive.mdx b/docs/sources/companion-plugins/onedrive.mdx deleted file mode 100644 index b91c3931e..000000000 --- a/docs/sources/companion-plugins/onedrive.mdx +++ /dev/null @@ -1,224 +0,0 @@ ---- -slug: /onedrive ---- - -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; - -import UppyCdnExample from '/src/components/UppyCdnExample'; - -# OneDrive - -The `@uppy/onedrive` plugin lets users import files from their -[OneDrive](https://onedrive.com) account. - -:::tip - -[Try out the live example](/examples) or take it for a spin in -[StackBlitz](https://stackblitz.com/edit/vitejs-vite-zaqyaf?file=main.js). - -::: - -## When should I use this? - -When you want to let users import files from their -[OneDrive](https://onedrive.com) account. - -A [Companion](/docs/companion) instance is required for the OneDrive plugin to -work. Companion handles authentication with OneDrive, downloads the files, and -uploads them to the destination. This saves the user bandwidth, especially -helpful if they are on a mobile connection. - -You can self-host Companion or get a hosted version with any -[Transloadit plan](https://transloadit.com/pricing/). - - - - -```shell -npm install @uppy/onedrive -``` - - - - - -```shell -yarn add @uppy/onedrive -``` - - - - - - {` - import { Uppy, OneDrive } from "{{UPPY_JS_URL}}" - const uppy = new Uppy() - uppy.use(OneDrive, { - // Options - }) - `} - - - - -## Use - -Using OneDrive requires setup in both Uppy and Companion. - -### Use in Uppy - -```js {10-13} showLineNumbers -import Uppy from '@uppy/core'; -import Dashboard from '@uppy/dashboard'; -import OneDrive from '@uppy/onedrive'; - -import '@uppy/core/dist/style.min.css'; -import '@uppy/dashboard/dist/style.min.css'; - -new Uppy() - .use(Dashboard, { inline: true, target: '#dashboard' }) - .use(OneDrive, { companionUrl: 'https://your-companion.com' }); -``` - -### Use with Transloadit - -```js -import { COMPANION_URL, COMPANION_ALLOWED_HOSTS } from '@uppy/transloadit'; -import OneDrive from '@uppy/onedrive'; - -uppy.use(OneDrive, { - companionUrl: COMPANION_URL, - companionAllowedHosts: COMPANION_ALLOWED_HOSTS, -}); -``` - -You may also hit rate limits, because the OAuth application is shared between -everyone using Transloadit. - -To solve that, you can use your own OAuth keys with Transloadit’s hosted -Companion servers by using Transloadit Template Credentials. [Create a Template -Credential][template-credentials] on the Transloadit site. Select “Companion -OAuth” for the service, and enter the key and secret for the provider you want -to use. Then you can pass the name of the new credentials to that provider: - -```js -import { COMPANION_URL, COMPANION_ALLOWED_HOSTS } from '@uppy/transloadit'; -import OneDrive from '@uppy/onedrive'; - -uppy.use(OneDrive, { - companionUrl: COMPANION_URL, - companionAllowedHosts: COMPANION_ALLOWED_HOSTS, - companionKeysParams: { - key: 'YOUR_TRANSLOADIT_API_KEY', - credentialsName: 'my_companion_dropbox_creds', - }, -}); -``` - -### Use in Companion - -To sign up for API keys, go to the -[Azure Platform from Microsoft](https://portal.azure.com/#view/Microsoft_AAD_RegisteredApps/ApplicationsListBlade). - -Create a project for your app if you don’t have one yet. - -The app page has a `"Redirect URIs"` field. Here, add: - -``` -https://$YOUR_COMPANION_HOST_NAME/onedrive/redirect -``` - -If you are using Transloadit hosted Companion: - -``` -https://api2.transloadit.com/companion/onedrive/redirect -``` - -Go to the “Manifest” tab, and find the `"signInAudience"` key. Change it to -`"signInAudience": "AzureADandPersonalMicrosoftAccount"`, and click “Save”. - -Go to the “Overview” tab. Copy the `Application (client) ID` field - this will -be your Oauth client ID. - -Go to the “Certificates & secrets” tab, and click “+ New client secret”. Copy -the `Value` field - this will be your OAuth client secret. - -Configure the OneDrive key and secret in Companion. With the standalone -Companion server, specify environment variables: - -```shell -export COMPANION_ONEDRIVE_KEY="OneDrive Application ID" -export COMPANION_ONEDRIVE_SECRET="OneDrive OAuth client secret value" -``` - -When using the Companion Node.js API, configure these options: - -```js -companion.app({ - providerOptions: { - onedrive: { - key: 'OneDrive Application ID', - secret: 'OneDrive OAuth client secret value', - }, - }, -}); -``` - -## API - -### Options - -#### `id` - -A unique identifier for this plugin (`string`, default: `'OneDrive'`). - -#### `title` - -Title / name shown in the UI, such as Dashboard tabs (`string`, default: -`'OneDrive'`). - -#### `target` - -DOM element, CSS selector, or plugin to place the drag and drop area into -(`string`, `Element`, `Function`, or `UIPlugin`, default: -[`Dashboard`](/docs/dashboard)). - -#### `companionUrl` - -URL to a [Companion](/docs/companion) instance (`string`, default: `null`). - -#### `companionHeaders` - -Custom headers that should be sent along to [Companion](/docs/companion) on -every request (`Object`, default: `{}`). - -#### `companionAllowedHosts` - -The valid and authorised URL(s) from which OAuth responses should be accepted -(`string` or `RegExp` or `Array`, default: `companionUrl`). - -This value can be a `string`, a `RegExp` pattern, or an `Array` of both. This is -useful when you have your [Companion](/docs/companion) running on several hosts. -Otherwise, the default value should do fine. - -#### `companionCookiesRule` - -This option correlates to the -[RequestCredentials value](https://developer.mozilla.org/en-US/docs/Web/API/Request/credentials) -(`string`, default: `'same-origin'`). - -This tells the plugin whether to send cookies to [Companion](/docs/companion). - -#### `locale` - -```js -export default { - strings: { - pluginNameOneDrive: 'OneDrive', - }, -}; -``` - -[template-credentials]: - https://transloadit.com/docs/#how-to-create-template-credentials diff --git a/docs/sources/companion-plugins/unsplash.mdx b/docs/sources/companion-plugins/unsplash.mdx deleted file mode 100644 index 1287729ae..000000000 --- a/docs/sources/companion-plugins/unsplash.mdx +++ /dev/null @@ -1,202 +0,0 @@ ---- -slug: /unsplash ---- - -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; - -import UppyCdnExample from '/src/components/UppyCdnExample'; - -# Unsplash - -The `@uppy/unsplash` plugin lets users import files from their -[Unsplash](https://unsplash.com) account. - -:::tip - -[Try out the live example](/examples) or take it for a spin in -[StackBlitz](https://stackblitz.com/edit/vitejs-vite-zaqyaf?file=main.js). - -::: - -## When should I use this? - -When you want to let users import files from their -[Unsplash](https://unsplash.com) account. - -A [Companion](/docs/companion) instance is required for the Unsplash plugin to -work. Companion handles authentication with Unsplash, downloads the files, and -uploads them to the destination. This saves the user bandwidth, especially -helpful if they are on a mobile connection. - -You can self-host Companion or get a hosted version with any -[Transloadit plan](https://transloadit.com/pricing/). - - - - -```shell -npm install @uppy/unsplash -``` - - - - - -```shell -yarn add @uppy/unsplash -``` - - - - - - {` - import { Uppy, Unsplash } from "{{UPPY_JS_URL}}" - const uppy = new Uppy() - uppy.use(Unsplash, { - // Options - }) - `} - - - - -## Use - -Using Unsplash requires setup in both Uppy and Companion. - -### Use in Uppy - -```js {10-13} showLineNumbers -import Uppy from '@uppy/core'; -import Dashboard from '@uppy/dashboard'; -import Unsplash from '@uppy/unsplash'; - -import '@uppy/core/dist/style.min.css'; -import '@uppy/dashboard/dist/style.min.css'; - -new Uppy() - .use(Dashboard, { inline: true, target: '#dashboard' }) - .use(Unsplash, { companionUrl: 'https://your-companion.com' }); -``` - -### Use with Transloadit - -```js -import { COMPANION_URL, COMPANION_ALLOWED_HOSTS } from '@uppy/transloadit'; -import Unsplash from '@uppy/unsplash'; - -uppy.use(Unsplash, { - companionUrl: COMPANION_URL, - companionAllowedHosts: COMPANION_ALLOWED_HOSTS, -}); -``` - -You may also hit rate limits, because the OAuth application is shared between -everyone using Transloadit. - -To solve that, you can use your own OAuth keys with Transloadit’s hosted -Companion servers by using Transloadit Template Credentials. [Create a Template -Credential][template-credentials] on the Transloadit site. Select “Companion -OAuth” for the service, and enter the key and secret for the provider you want -to use. Then you can pass the name of the new credentials to that provider: - -```js -import { COMPANION_URL, COMPANION_ALLOWED_HOSTS } from '@uppy/transloadit'; -import Unsplash from '@uppy/unsplash'; - -uppy.use(Unsplash, { - companionUrl: COMPANION_URL, - companionAllowedHosts: COMPANION_ALLOWED_HOSTS, - companionKeysParams: { - key: 'YOUR_TRANSLOADIT_API_KEY', - credentialsName: 'my_companion_dropbox_creds', - }, -}); -``` - -### Use in Companion - -You can create a Unsplash App on the -[Unsplash Developers site](https://unsplash.com/developers). You’ll be -redirected to the app page, this page lists the app key and app secret. - -Configure the Unsplash key and secret. With the standalone Companion server, -specify environment variables: - -```shell -export COMPANION_UNSPLASH_KEY="Unsplash API key" -export COMPANION_UNSPLASH_SECRET="Unsplash API secret" -``` - -When using the Companion Node.js API, configure these options: - -```js -companion.app({ - providerOptions: { - unsplash: { - key: 'Unsplash API key', - secret: 'Unsplash API secret', - }, - }, -}); -``` - -## API - -### Options - -#### `id` - -A unique identifier for this plugin (`string`, default: `'Unsplash'`). - -#### `title` - -Title / name shown in the UI, such as Dashboard tabs (`string`, default: -`'Unsplash'`). - -#### `target` - -DOM element, CSS selector, or plugin to place the drag and drop area into -(`string`, `Element`, `Function`, or `UIPlugin`, default: -[`Dashboard`](/docs/dashboard)). - -#### `companionUrl` - -URL to a [Companion](/docs/companion) instance (`string`, default: `null`). - -#### `companionHeaders` - -Custom headers that should be sent along to [Companion](/docs/companion) on -every request (`Object`, default: `{}`). - -#### `companionAllowedHosts` - -The valid and authorised URL(s) from which OAuth responses should be accepted -(`string` or `RegExp` or `Array`, default: `companionUrl`). - -This value can be a `string`, a `RegExp` pattern, or an `Array` of both. This is -useful when you have your [Companion](/docs/companion) running on several hosts. -Otherwise, the default value should do fine. - -#### `companionCookiesRule` - -This option correlates to the -[RequestCredentials value](https://developer.mozilla.org/en-US/docs/Web/API/Request/credentials) -(`string`, default: `'same-origin'`). - -This tells the plugin whether to send cookies to [Companion](/docs/companion). - -#### `locale` - -```js -export default { - strings: { - pluginNameUnsplash: 'Unsplash', - }, -}; -``` - -[template-credentials]: - https://transloadit.com/docs/#how-to-create-template-credentials diff --git a/docs/sources/companion-plugins/url.mdx b/docs/sources/companion-plugins/url.mdx deleted file mode 100644 index 52892db58..000000000 --- a/docs/sources/companion-plugins/url.mdx +++ /dev/null @@ -1,188 +0,0 @@ ---- -slug: /url ---- - -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; - -import UppyCdnExample from '/src/components/UppyCdnExample'; - -# Import from URL - -The `@uppy/url` plugin allows users to import files from the internet. Paste any -URL and it will be added! - -:::tip - -[Try out the live example](/examples) or take it for a spin in -[StackBlitz](https://stackblitz.com/edit/vitejs-vite-zaqyaf?file=main.js). - -::: - -## When should I use this? - -When you want to let users import files any URL. - -A [Companion](/docs/companion) instance is required for the URL plugin to work. -This saves the user bandwidth, especially helpful if they are on a mobile -connection. - -You can self-host Companion or get a hosted version with any -[Transloadit plan](https://transloadit.com/pricing/). - -:::note - -Companion has -[Server Side Request Forgery](https://owasp.org/www-community/attacks/Server_Side_Request_Forgery) -(SSRF) protections built-in so you don’t have to worry about the security -implications of arbitrary URLs. - -::: - - - - -```shell -npm install @uppy/url -``` - - - - - -```shell -yarn add @uppy/url -``` - - - - - - {` - import { Uppy, Url } from "{{UPPY_JS_URL}}" - const uppy = new Uppy() - uppy.use(Url, { - // Options - }) - `} - - - - -## Use - -Using `@uppy/url` only requires setup in Uppy. - -### Use in Uppy - -```js {10-13} showLineNumbers -import Uppy from '@uppy/core'; -import Dashboard from '@uppy/dashboard'; -import Url from '@uppy/url'; - -import '@uppy/core/dist/style.min.css'; -import '@uppy/dashboard/dist/style.min.css'; -import '@uppy/url/dist/style.min.css'; - -new Uppy() - .use(Dashboard, { inline: true, target: '#dashboard' }) - .use(Url, { companionUrl: 'https://your-companion.com' }); -``` - -### Use with Transloadit - -```js -import { COMPANION_URL, COMPANION_ALLOWED_HOSTS } from '@uppy/transloadit'; -import Url from '@uppy/url'; - -uppy.use(Url, { - companionUrl: COMPANION_URL, - companionAllowedHosts: COMPANION_ALLOWED_HOSTS, -}); -``` - -You may also hit rate limits, because the OAuth application is shared between -everyone using Transloadit. - -To solve that, you can use your own OAuth keys with Transloadit’s hosted -Companion servers by using Transloadit Template Credentials. [Create a Template -Credential][template-credentials] on the Transloadit site. Select “Companion -OAuth” for the service, and enter the key and secret for the provider you want -to use. Then you can pass the name of the new credentials to that provider: - -```js -import { COMPANION_URL, COMPANION_ALLOWED_HOSTS } from '@uppy/transloadit'; -import Url from '@uppy/url'; - -uppy.use(Url, { - companionUrl: COMPANION_URL, - companionAllowedHosts: COMPANION_ALLOWED_HOSTS, - companionKeysParams: { - key: 'YOUR_TRANSLOADIT_API_KEY', - credentialsName: 'my_companion_dropbox_creds', - }, -}); -``` - -### Use in Companion - -Companion supports this plugin out-of-the-box, however it must be enabled in -Companion with the `enableUrlEndpoint` / `COMPANION_ENABLE_URL_ENDPOINT` option. - -## API - -### Options - -#### `id` - -A unique identifier for this plugin (`string`, default: `'Url'`). - -#### `title` - -Title / name shown in the UI, such as Dashboard tabs (`string`, default: -`'Url'`). - -#### `target` - -DOM element, CSS selector, or plugin to place the drag and drop area into -(`string`, `Element`, `Function`, or `UIPlugin`, default: -[`Dashboard`](/docs/dashboard)). - -#### `companionUrl` - -URL to a [Companion](/docs/companion) instance (`string`, default: `null`). - -#### `companionHeaders` - -Custom headers that should be sent along to [Companion](/docs/companion) on -every request (`Object`, default: `{}`). - -#### `companionAllowedHosts` - -The valid and authorised URL(s) from which OAuth responses should be accepted -(`string` or `RegExp` or `Array`, default: `companionUrl`). - -This value can be a `string`, a `RegExp` pattern, or an `Array` of both. This is -useful when you have your [Companion](/docs/companion) running on several hosts. -Otherwise, the default value should do fine. - -#### `companionCookiesRule` - -This option correlates to the -[RequestCredentials value](https://developer.mozilla.org/en-US/docs/Web/API/Request/credentials) -(`string`, default: `'same-origin'`). - -This tells the plugin whether to send cookies to [Companion](/docs/companion). - -#### `locale` - -```js -export default { - strings: { - pluginNameUrl: 'Url', - }, -}; -``` - -[template-credentials]: - https://transloadit.com/docs/#how-to-create-template-credentials diff --git a/docs/sources/companion-plugins/zoom.mdx b/docs/sources/companion-plugins/zoom.mdx deleted file mode 100644 index 83dbbc7c0..000000000 --- a/docs/sources/companion-plugins/zoom.mdx +++ /dev/null @@ -1,227 +0,0 @@ ---- -slug: /zoom ---- - -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; - -import UppyCdnExample from '/src/components/UppyCdnExample'; - -# Zoom - -The `@uppy/zoom` plugin lets users import cloud video recordings from their -[Zoom](https://zoom.com) account. Note that -[only licensed](https://support.zoom.com/hc/en/article?id=zm_kb&sysparm_article=KB0063923) -Zoom accounts can store their recordings in the cloud, so this functionality -will only be available to users with a paid Zoom account. - -:::tip - -[Try out the live example](/examples) or take it for a spin in -[StackBlitz](https://stackblitz.com/edit/vitejs-vite-zaqyaf?file=main.js). - -::: - -## When should I use this? - -When you want to let users import cloud video recordings from their -[Zoom](https://zoom.com) account. - -A [Companion](/docs/companion) instance is required for the Zoom plugin to work. -Companion handles authentication with Zoom, downloads the files, and uploads -them to the destination. This saves the user bandwidth, especially helpful if -they are on a mobile connection. - -You can self-host Companion or get a hosted version with any -[Transloadit plan](https://transloadit.com/pricing/). - - - - -```shell -npm install @uppy/zoom -``` - - - - - -```shell -yarn add @uppy/zoom -``` - - - - - - {` - import { Uppy, Zoom } from "{{UPPY_JS_URL}}" - const uppy = new Uppy() - uppy.use(Zoom, { - // Options - }) - `} - - - - -## Use - -Using Zoom requires setup in both Uppy and Companion. - -### Use in Uppy - -```js {10-13} showLineNumbers -import Uppy from '@uppy/core'; -import Dashboard from '@uppy/dashboard'; -import Zoom from '@uppy/zoom'; - -import '@uppy/core/dist/style.min.css'; -import '@uppy/dashboard/dist/style.min.css'; - -new Uppy() - .use(Dashboard, { inline: true, target: '#dashboard' }) - .use(Zoom, { companionUrl: 'https://your-companion.com' }); -``` - -### Use with Transloadit - -```js -import { COMPANION_URL, COMPANION_ALLOWED_HOSTS } from '@uppy/transloadit'; -import Zoom from '@uppy/zoom'; - -uppy.use(Zoom, { - companionUrl: COMPANION_URL, - companionAllowedHosts: COMPANION_ALLOWED_HOSTS, -}); -``` - -You may also hit rate limits, because the OAuth application is shared between -everyone using Transloadit. - -To solve that, you can use your own OAuth keys with Transloadit’s hosted -Companion servers by using Transloadit Template Credentials. [Create a Template -Credential][template-credentials] on the Transloadit site. Select “Companion -OAuth” for the service, and enter the key and secret for the provider you want -to use. Then you can pass the name of the new credentials to that provider: - -```js -import { COMPANION_URL, COMPANION_ALLOWED_HOSTS } from '@uppy/transloadit'; -import Zoom from '@uppy/zoom'; - -uppy.use(Zoom, { - companionUrl: COMPANION_URL, - companionAllowedHosts: COMPANION_ALLOWED_HOSTS, - companionKeysParams: { - key: 'YOUR_TRANSLOADIT_API_KEY', - credentialsName: 'my_companion_dropbox_creds', - }, -}); -``` - -### Use in Companion - -To sign up for API keys, go through the following steps: - -1. Sign up on [Zoom Marketplace](https://marketplace.zoom.us) - -2. Go to [https://marketplace.zoom.us](https://marketplace.zoom.us). There will - be a dropdown in the header called “Develop”. From that dropdown, select - “Build app”. - -3. In the “Basic Information” tab, Zoom shows your new “Client ID” and “Client - Secret” - copy them. - - With the standalone Companion server, specify environment variables: - - ```shell - export COMPANION_ZOOM_KEY="Zoom API key" - export COMPANION_ZOOM_SECRET="Zoom API secret" - ``` - - When using the Companion Node.js API, configure these options: - - ```js - companion.app({ - providerOptions: { - zoom: { - key: 'Zoom API key', - secret: 'Zoom API secret', - }, - }, - }); - ``` - -4. In the “Basic Information” tab, set “OAuth Redirect URL” input field to: - - ``` - https://$YOUR_COMPANION_HOST_NAME/zoom/redirect - ``` - - If you are using Transloadit hosted Companion: - - ``` - https://api2.transloadit.com/companion/zoom/redirect - ``` - -5. In the “Scopes” tab, add “cloud_recording:read:list_user_recordings” and - “user:read:user” scopes. If Zoom asks for further permissions when you - interact with your Zoom integration - add those too. - -## API - -### Options - -#### `id` - -A unique identifier for this plugin (`string`, default: `'Zoom'`). - -#### `title` - -Title / name shown in the UI, such as Dashboard tabs (`string`, default: -`'Zoom'`). - -#### `target` - -DOM element, CSS selector, or plugin to place the drag and drop area into -(`string`, `Element`, `Function`, or `UIPlugin`, default: -[`Dashboard`](/docs/dashboard)). - -#### `companionUrl` - -URL to a [Companion](/docs/companion) instance (`string`, default: `null`). - -#### `companionHeaders` - -Custom headers that should be sent along to [Companion](/docs/companion) on -every request (`Object`, default: `{}`). - -#### `companionAllowedHosts` - -The valid and authorised URL(s) from which OAuth responses should be accepted -(`string` or `RegExp` or `Array`, default: `companionUrl`). - -This value can be a `string`, a `RegExp` pattern, or an `Array` of both. This is -useful when you have your [Companion](/docs/companion) running on several hosts. -Otherwise, the default value should do fine. - -#### `companionCookiesRule` - -This option correlates to the -[RequestCredentials value](https://developer.mozilla.org/en-US/docs/Web/API/Request/credentials) -(`string`, default: `'same-origin'`). - -This tells the plugin whether to send cookies to [Companion](/docs/companion). - -#### `locale` - -```js -export default { - strings: { - pluginNameZoom: 'Zoom', - }, -}; -``` - -[template-credentials]: - https://transloadit.com/docs/#how-to-create-template-credentials diff --git a/docs/sources/file-input.mdx b/docs/sources/file-input.mdx deleted file mode 100644 index c764b23b9..000000000 --- a/docs/sources/file-input.mdx +++ /dev/null @@ -1,166 +0,0 @@ ---- -sidebar_position: 3 -slug: /file-input ---- - -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; - -import UppyCdnExample from '/src/components/UppyCdnExample'; - -# File input - -The `@uppy/file-input` plugin is the most barebones UI for selecting files — it -shows a single button that, when clicked, opens up the browser’s file selector. - -## When should I use it? - -When you want users to select files from their local machine with a minimal UI. - -## Install - - - - -```shell -npm install @uppy/file-input -``` - - - - - -```shell -yarn add @uppy/file-input -``` - - - - - - {` - import { Uppy, FileInput } from "{{UPPY_JS_URL}}" - const uppy = new Uppy() - uppy.use(FileInput, { target: document.body }) - `} - - - - -## Use - -```js showLineNumbers -import Uppy from '@uppy/core'; -import FileInput from '@uppy/file-input'; - -import '@uppy/core/dist/style.min.css'; -import '@uppy/file-input/dist/style.css'; - -new Uppy().use(FileInput, { target: '#uppy-file-input' }); -``` - -:::note - -The `@uppy/file-input` plugin includes some basic styles for use with the -[`pretty`](#pretty-true) option, like shown in the -[example](/examples/xhrupload). You can also choose not to use it and provide -your own styles instead. - -Import general Core styles from `@uppy/core/dist/style.css` first, then add the -File Input styles from `@uppy/file-input/dist/style.css`. A minified version is -also available as `style.min.css` at the same path. The way to do import depends -on your build system. - -::: - -## API - -### Options - -#### `id` - -A unique identifier for this plugin (`string`, default: `'FileInput'`). - -#### `title` - -Configures the title / name shown in the UI, for instance, on Dashboard tabs -(`string`, default: `'File Input'`). - -#### `target` - -DOM element, CSS selector, or plugin to place the audio into (`string` or -`Element`, default: `null`). - -#### `pretty` - -When true, display a styled button that, when clicked, opens the file selector -UI. When false, a plain old browser `` element is shown -(`boolean`, default: `true`). - -#### `inputName` - -The `name` attribute for the `` element (`string`, default: -`'files[]'`). - -#### `locale` - -```js -export default { - strings: { - // The same key is used for the same purpose by @uppy/robodog's `form()` API, but our - // locale pack scripts can't access it in Robodog. If it is updated here, it should - // also be updated there! - chooseFiles: 'Choose files', - }, -}; -``` - -## Custom file inpt - -If you don’t like the look/feel of the button rendered by `@uppy/file-input`, -feel free to forgo the plugin and use your own custom button on a page, like so: - -```html - -``` - -Then add this JS to attach it to Uppy: - -```js -const uppy = new Uppy(/* ... */); -const fileInput = document.querySelector('#my-file-input'); - -fileInput.addEventListener('change', (event) => { - const files = Array.from(event.target.files); - - files.forEach((file) => { - try { - uppy.addFile({ - source: 'file input', - name: file.name, - type: file.type, - data: file, - }); - } catch (err) { - if (err.isRestriction) { - // handle restrictions - console.log('Restriction error:', err); - } else { - // handle other errors - console.error(err); - } - } - }); -}); - -// Clear the `` after the upload or when the file was removed -// so the file can be uploaded again (see -// https://github.com/transloadit/uppy/issues/2640#issuecomment-731034781). -uppy.on('file-removed', () => { - fileInput.value = null; -}); - -uppy.on('complete', () => { - fileInput.value = null; -}); -``` diff --git a/docs/sources/screen-capture.mdx b/docs/sources/screen-capture.mdx deleted file mode 100644 index a2329f733..000000000 --- a/docs/sources/screen-capture.mdx +++ /dev/null @@ -1,150 +0,0 @@ ---- -sidebar_position: 2 -slug: /screen-capture ---- - -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; - -import UppyCdnExample from '/src/components/UppyCdnExample'; - -# Screen capture - -The `@uppy/screen-capture` plugin can record your screen or an application and -save it as a video. - -:::tip - -[Try out the live example](/examples) or take it for a spin in -[StackBlitz](https://stackblitz.com/edit/vitejs-vite-zaqyaf?file=main.js). - -::: - -## When should I use this? - -When you want users record their screen on their computer. This plugin only -works on desktop browsers which support -[`getDisplayMedia API`](https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getDisplayMedia). -If no support for the API is detected, Screen Capture won’t be installed, so you -don’t have to do anything. - -:::note - -To use the screen capture plugin in a Chromium-based browser, -[your site must be served over https](https://developers.google.com/web/updates/2015/10/chrome-47-webrtc#public_service_announcements). -This restriction does not apply on `localhost`, so you don’t have to jump -through many hoops during development. - -::: - -## Install - - - - -```shell -npm install @uppy/screen-capture -``` - - - - - -```shell -yarn add @uppy/screen-capture -``` - - - - - - {` - import { Uppy, Dashboard, ScreenCapture } from "{{UPPY_JS_URL}}" - const uppy = new Uppy() - uppy.use(Dashboard, { inline: true, target: 'body' }) - uppy.use(ScreenCapture) - `} - - - - -## Use - -```js {3,7,11} showLineNumbers -import Uppy from '@uppy/core'; -import Dashboard from '@uppy/dashboard'; -import ScreenCapture from '@uppy/screen-capture'; - -import '@uppy/core/dist/style.min.css'; -import '@uppy/dashboard/dist/style.min.css'; -import '@uppy/screen-capture/dist/style.min.css'; - -new Uppy().use(Dashboard, { inline: true, target: 'body' }).use(ScreenCapture); -``` - -### API - -### Options - -#### `id` - -A unique identifier for this plugin (`string`, default: `'ScreenCapture'`). - -#### `title` - -Configures the title / name shown in the UI, for instance, on Dashboard tabs -(`string`, default: `'Screen Capture'`). - -#### `target` - -DOM element, CSS selector, or plugin to place the drag and drop area into -(`string`, `Element`, `Function`, or `UIPlugin`, default: -[`Dashboard`](/docs/dashboard)). - -#### `displayMediaConstraints` - -Options passed to -[`MediaDevices.getDisplayMedia()`](https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getDisplayMedia) -(`Object`, default: `null`). - -See the -[`MediaTrackConstraints`](https://developer.mozilla.org/en-US/docs/Web/API/MediaTrackConstraints) -for a list of options. - -#### `userMediaConstraints` - -Options passed to -[`MediaDevices.getUserMedia()`](https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getUserMedia) -(`Object`, default: `null`). - -See the -[`MediaTrackConstraints`](https://developer.mozilla.org/en-US/docs/Web/API/MediaTrackConstraints) -for a list of options. - -#### `preferredVideoMimeType` - -Set the preferred mime type for video recordings, for example `'video/webm'` -(`string`, default: `null`). - -If the browser supports the given mime type, the video will be recorded in this -format. If the browser does not support it, it will use the browser default. - -If no preferred video mime type is given, the ScreenCapture plugin will prefer -types listed in the [`allowedFileTypes` restriction](/docs/uppy/#restrictions), -if any. - -#### `locale` - -```js -export default { - strings: { - startCapturing: 'Begin screen capturing', - stopCapturing: 'Stop screen capturing', - submitRecordedFile: 'Submit recorded file', - streamActive: 'Stream active', - streamPassive: 'Stream passive', - micDisabled: 'Microphone access denied by user', - recording: 'Recording', - }, -}; -``` diff --git a/docs/sources/webcam.mdx b/docs/sources/webcam.mdx deleted file mode 100644 index 08cf5e3bf..000000000 --- a/docs/sources/webcam.mdx +++ /dev/null @@ -1,252 +0,0 @@ ---- -sidebar_position: 1 -slug: /webcam ---- - -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; - -import UppyCdnExample from '/src/components/UppyCdnExample'; - -# Webcam - -The `@uppy/webcam` plugin lets you take photos and record videos with a built-in -camera on desktop and mobile devices. - -:::tip - -[Try out the live example](/examples) or take it for a spin in -[StackBlitz](https://stackblitz.com/edit/vitejs-vite-zaqyaf?file=main.js). - -::: - -## When should I use it? - -When you want your users to able to use their camera. This plugin is published -separately but made specifically for the [Dashboard](/docs/dashboard). You can -technically use it without it, but it’s not officially supported. - -## Install - - - - -```shell -npm install @uppy/webcam -``` - - - - - -```shell -yarn add @uppy/webcam -``` - - - - - - {` - import { Uppy, Dashboard, Webcam } from "{{UPPY_JS_URL}}" - const uppy = new Uppy() - uppy.use(Dashboard, { inline: true, target: 'body' }) - uppy.use(Webcam) - `} - - - - -## Use - -:::note - -To use the Webcam plugin in Chrome, -[your site must be served over https](https://developers.google.com/web/updates/2015/10/chrome-47-webrtc#public_service_announcements). -This restriction does not apply on `localhost`, so you don’t have to jump -through many hoops during development. - -::: - -```js {3,7,11} showLineNumbers -import Uppy from '@uppy/core'; -import Dashboard from '@uppy/dashboard'; -import Webcam from '@uppy/webcam'; - -import '@uppy/core/dist/style.min.css'; -import '@uppy/dashboard/dist/style.min.css'; -import '@uppy/webcam/dist/style.min.css'; - -new Uppy().use(Dashboard, { inline: true, target: 'body' }).use(Webcam); -``` - -## API - -### Options - -#### `id` - -A unique identifier for this plugin (`string`, default: `'Webcam'`). - -#### `target` - -DOM element, CSS selector, or plugin to place the drag and drop area into -(`string`, `Element`, `Function`, or `UIPlugin`, default: -[`Dashboard`](/docs/dashboard)). - -#### `countdown` - -When taking a picture: the amount of seconds to wait before actually taking a -snapshot (`boolean`, default: `false`). - -If set to `false` or 0, the snapshot is taken right away. This also shows a -`Smile!` message through the [Informer](/docs/informer) before the picture is -taken. - -#### `onBeforeSnapshot` - -A hook function to call before a snapshot is taken (`Function`, default: -`Promise.resolve`). - -The Webcam plugin will wait for the returned Promise to resolve before taking -the snapshot. This can be used to carry out variations on the `countdown` option -for example. - -#### `modes` - -The types of recording modes to allow (`Array`, default: `[]`). - -- `video-audio` - Record a video file, capturing both audio and video. -- `video-only` - Record a video file with the webcam, but don’t record audio. -- `audio-only` - Record an audio file with the user’s microphone. -- `picture` - Take a picture with the webcam. - -By default, all modes are allowed, and the Webcam plugin will show controls for -recording video as well as taking pictures. - -#### `mirror` - -Configures whether to mirror preview image from the camera (`boolean`, default: -`true`). - -This option is useful when taking a selfie with a front camera: when you wave -your right hand, you will see your hand on the right on the preview screen, like -in the mirror. But when you actually take a picture, it will not be mirrored. -This is how smartphone selfie cameras behave. - -#### `videoConstraints` - -Configure the [`MediaTrackConstraints`][] (`Object`, default: `{}`). - -You can specify acceptable ranges for the resolution of the video stream using -the [`aspectRatio`][], [`width`][], and [`height`][] properties. Each property -takes an object with `{ min, ideal, max }` properties. For example, use -`width: { min: 720, max: 1920, ideal: 1920 }` to allow any width between 720 and -1920 pixels wide, while preferring the highest resolution. - -Devices sometimes have several cameras, front and back, for example. -[`facingMode`][] lets you specify which should be used: - -- `user`: The video source is facing toward the user; this includes, for - example, the front-facing camera on a smartphone. -- `environment`: The video source is facing away from the user, thereby viewing - their environment. This is the back camera on a smartphone. -- `left`: The video source is facing toward the user but to their left, such as - a camera aimed toward the user but over their left shoulder. -- `right`: The video source is facing toward the user but to their right, such - as a camera aimed toward the user but over their right shoulder. - -For a full list of available properties, check out MDN documentation for -[`MediaTrackConstraints`][]. - -[`mediatrackconstraints`]: - https://developer.mozilla.org/en-US/docs/Web/API/MediaTrackConstraints#Properties_of_video_tracks -[`aspectratio`]: - https://developer.mozilla.org/en-US/docs/Web/API/MediaTrackConstraints/aspectRatio -[`width`]: - https://developer.mozilla.org/en-US/docs/Web/API/MediaTrackConstraints/width -[`height`]: - https://developer.mozilla.org/en-US/docs/Web/API/MediaTrackConstraints/height -[`facingmode`]: - https://developer.mozilla.org/en-US/docs/Web/API/MediaTrackConstraints/facingMode - -#### `showVideoSourceDropdown` - -Configures whether to show a dropdown which enables to choose the video device -to use (`boolean`, default: `false`). - -This option will have priority over `facingMode` if enabled. - -#### `showRecordingLength` - -Configures whether to show the length of the recording while the recording is in -progress (`boolean`, default: `false`). - -#### `preferredVideoMimeType` - -Set the preferred mime type for video recordings, for example `'video/webm'` -(`string`, default: `null`). - -If the browser supports the given mime type, the video will be recorded in this -format. If the browser does not support it, it will use the browser default. If -no preferred video mime type is given, the Webcam plugin will prefer types -listed in the [`allowedFileTypes` restriction](/docs/uppy/#restrictions), if -any. - -#### `preferredImageMimeType` - -Set the preferred mime type for images, for example `'image/png'` (`string`, -default: `image/jpeg`). - -If the browser supports rendering the given mime type, the image will be stored -in this format. Else `image/jpeg` is used by default. If no preferred image mime -type is given, the Webcam plugin will prefer types listed in the -[`allowedFileTypes` restriction](/docs/uppy/#restrictions), if any. - -#### `mobileNativeCamera` - -Replaces Uppy’s custom camera UI on mobile and tablet with the native device -camera (`Function: boolean` or `boolean`, default: `isMobile()`). - -This will show the “Take Picture” and / or “Record Video” buttons, which ones -show depends on the [`modes`](#modes) option. - -You can set a boolean to forcefully enable / disable this feature, or a function -which returns a boolean. By default we use the -[`is-mobile`](https://github.com/juliangruber/is-mobile) package. - -#### `locale` - -```js -export default { - strings: { - pluginNameCamera: 'Camera', - noCameraTitle: 'Camera Not Available', - noCameraDescription: - 'In order to take pictures or record video, please connect a camera device', - recordingStoppedMaxSize: - 'Recording stopped because the file size is about to exceed the limit', - submitRecordedFile: 'Submit recorded file', - discardRecordedFile: 'Discard recorded file', - // Shown before a picture is taken when the `countdown` option is set. - smile: 'Smile!', - // Used as the label for the button that takes a picture. - // This is not visibly rendered but is picked up by screen readers. - takePicture: 'Take a picture', - // Used as the label for the button that starts a video recording. - // This is not visibly rendered but is picked up by screen readers. - startRecording: 'Begin video recording', - // Used as the label for the button that stops a video recording. - // This is not visibly rendered but is picked up by screen readers. - stopRecording: 'Stop video recording', - // Used as the label for the recording length counter. See the showRecordingLength option. - // This is not visibly rendered but is picked up by screen readers. - recordingLength: 'Recording length %{recording_length}', - // Title on the “allow access” screen - allowAccessTitle: 'Please allow access to your camera', - // Description on the “allow access” screen - allowAccessDescription: - 'In order to take pictures or record video with your camera, please allow camera access for this site.', - }, -}; -``` diff --git a/docs/uploader/_category_.json b/docs/uploader/_category_.json deleted file mode 100644 index 6b7e5d328..000000000 --- a/docs/uploader/_category_.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "label": "Uploaders", - "position": 7 -} diff --git a/docs/uploader/aws-s3-multipart.mdx b/docs/uploader/aws-s3-multipart.mdx deleted file mode 100644 index 6e7712b3c..000000000 --- a/docs/uploader/aws-s3-multipart.mdx +++ /dev/null @@ -1,487 +0,0 @@ ---- -sidebar_position: 4 -slug: /aws-s3 ---- - -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; -import UppyCdnExample from '/src/components/UppyCdnExample'; - -# AWS S3 - -The `@uppy/aws-s3` plugin can be used to upload files directly to a S3 bucket or -a S3-compatible provider, such as Google Cloud Storage or DigitalOcean Spaces. -Uploads can be signed using either [Companion][companion docs], temporary -credentials, or a custom signing function. - -## When should I use it? - -:::tip - -Not sure which uploader is best for you? Read -“[Choosing the uploader you need](/docs/guides/choosing-uploader)”. - -::: - -You can use this plugin when you prefer a _client-to-storage_ over a -_client-to-server-to-storage_ (such as [Transloadit](/docs/transloadit) or -[Tus](/docs/tus)) setup. This may in some cases be preferable, for instance, to -reduce costs or the complexity of running a server and load balancer with -[Tus](/docs/tus). - -Multipart uploads start to become valuable for larger files (100 MiB+) as -it uploads a single object as a set of parts. This has certain benefits, such as -improved throughput (uploading parts in parallel) and quick recovery from -network issues (only the failed parts need to be retried). The downside is -request overhead, as it needs to do creation, signing (unless you are [signing -on the client][]), and completion requests besides the upload requests. For example, -if you are uploading files that are only a couple kilobytes with a 100ms roundtrip -latency, you are spending 400ms on overhead and only a few milliseconds on uploading. - -**In short** - -- We recommend the default value of [`shouldUseMultipart`][], which enable - multipart uploads only for large files. -- If you prefer to have less overhead (+20% upload speed) you can use temporary - S3 credentials with [`getTemporarySecurityCredentials`][]. This means users - get a single token which allows them to do bucket operations for longer, - instead of short lived signed URL per resource. This is a security trade-off. - -## Install - - - - -```shell -npm install @uppy/aws-s3 -``` - - - - - -```shell -yarn add @uppy/aws-s3 -``` - - - - - - {` - import { Uppy, AwsS3 } from "{{UPPY_JS_URL}}" - new Uppy().use(AwsS3, { /* see options */ }) - `} - - - - -## Use - -### Setting up your S3 bucket - -To use this plugin with S3 we need to setup a bucket with the right permissions -and CORS settings. - -S3 buckets do not allow public uploads for security reasons. To allow Uppy and -the browser to upload directly to a bucket, its CORS permissions need to be -configured. - -CORS permissions can be found in the -[S3 Management Console](https://console.aws.amazon.com/s3/home). Click the -bucket that will receive the uploads, then go into the `Permissions` tab and -select the `CORS configuration` button. A JSON document will be shown that -defines the CORS configuration. (AWS used to use XML but now only allow JSON). -More information about the -[S3 CORS format here](https://docs.amazonaws.cn/en_us/AmazonS3/latest/userguide/ManageCorsUsing.html). - -The configuration required for Uppy and Companion is this: - -```json -[ - { - "AllowedOrigins": ["https://my-app.com"], - "AllowedMethods": ["GET", "PUT"], - "MaxAgeSeconds": 3000, - "AllowedHeaders": [ - "Authorization", - "x-amz-date", - "x-amz-content-sha256", - "content-type" - ], - "ExposeHeaders": ["ETag", "Location"] - }, - { - "AllowedOrigins": ["*"], - "AllowedMethods": ["GET"], - "MaxAgeSeconds": 3000 - } -] -``` - -A good practice is to use two CORS rules: one for viewing the uploaded files, -and one for uploading files. This is done above where the first object in the -array defines the rules for uploading, and the second for viewing. The example -above **makes files publicly viewable**. You can change it according to your -needs. - -If you are using an IAM policy to allow access to the S3 bucket, the policy must -have at least the `s3:PutObject` and `s3:PutObjectAcl` permissions scoped to the -bucket in question. In-depth documentation about CORS rules is available on the -[AWS documentation site](https://docs.aws.amazon.com/AmazonS3/latest/dev/cors.html). - -### Use with your own server - -The recommended approach is to integrate `@uppy/aws-s3` with your own server. -You will need to do the following things: - -1. [Setup a S3 bucket](#setting-up-your-s3-bucket). -2. [Setup your server](https://github.com/transloadit/uppy/blob/main/examples/aws-nodejs/index.js) -3. [Setup Uppy client](https://github.com/transloadit/uppy/blob/main/examples/aws-nodejs/public/index.html). - -### Use with Companion - -[Companion](/docs/companion) has S3 routes built-in for a plug-and-play -experience with Uppy. - -:::caution - -Generally it’s better for access control, observability, and scaling to -integrate `@uppy/aws-s3` with your own server. You may want to use -[Companion](/docs/companion) for creating, signing, and completing your S3 -uploads if you already need Companion for remote files (such as from Google -Drive). Otherwise it’s not worth the hosting effort. - -::: - -```js {10} showLineNumbers -import Uppy from '@uppy/core'; -import Dashboard from '@uppy/dashboard'; -import AwsS3 from '@uppy/aws-s3'; - -import '@uppy/core/dist/style.min.css'; -import '@uppy/dashboard/dist/style.min.css'; - -const uppy = new Uppy() - .use(Dashboard, { inline: true, target: 'body' }) - .use(AwsS3, { - endpoint: 'https://companion.uppy.io', - }); -``` - -### Use with TypeScript - -Uppy always puts the response to an upload in `file.response.body`. If you want -this to be type safe with `@uppy/aws-s3`, you can import the `AwsBody` type and -pass it as the second genric to `Uppy`. - -```ts {3,12} showLineNumbers -import Uppy from '@uppy/core'; -import Dashboard from '@uppy/dashboard'; -import AwsS3, { type AwsBody } from '@uppy/aws-s3'; - -import '@uppy/core/dist/style.min.css'; -import '@uppy/dashboard/dist/style.min.css'; - -// Set this to `any` or `Record` -// if you do not set any metadata yourself -type Meta = { license: string }; - -const uppy = new Uppy() - .use(Dashboard, { inline: true, target: 'body' }) - .use(AwsS3, { endpoint: '...' }); - -const id = uppy.addFile(/* ... */); - -await uppy.upload(); - -const body = uppy.getFile(id).response.body!; -const { location } = body; // This is now type safe -``` - -## API - -### Options - -#### `shouldUseMultipart(file)` - -A boolean, or a function that returns a boolean which is called for each file -that is uploaded with the corresponding `UppyFile` instance as argument. - -By default, all files with a `file.size` ≤ 100 MiB will be uploaded in a -single chunk, all files larger than that as multipart. - -Here’s how to use it: - -```js -uppy.use(AwsS3, { - shouldUseMultipart(file) { - // Use multipart only for files larger than 100MiB. - return file.size > 100 * 2 ** 20; - }, -}); -``` - -#### `limit` - -The maximum amount of files to upload in parallel (`number`, default: `6`). - -Note that the amount of files is not the same as the amount of concurrent -connections. Multipart uploads can use many requests per file. For example, for -a 100 MiB file with a part size of 5 MiB: - -- 1 `createMultipartUpload` request -- 100/5 = 20 sign requests (unless you are [signing on the client][]) -- 100/5 = 20 upload requests -- 1 `completeMultipartUpload` request - -:::caution - -Unless you have a good reason and are well informed about the average internet -speed of your users, do not set this higher. S3 uses HTTP/1.1, which means a -limit to concurrent connections and your uploads may expire before they are -uploaded. - -::: - -#### `endpoint` - -URL to your backend or to [Companion](/docs/companion) (`string`, default: -`null`). - -#### `headers` - -Custom headers that should be sent along to the [`endpoint`](#endpoint) on every -request (`Object`, default: `{}`). - -#### `cookiesRule` - -This option correlates to the -[RequestCredentials value](https://developer.mozilla.org/en-US/docs/Web/API/Request/credentials) -(`string`, default: `'same-origin'`). - -This tells the plugin whether to send cookies to the [`endpoint`](#endpoint). - -#### `retryDelays` - -`retryDelays` are the intervals in milliseconds used to retry a failed chunk -(`array`, default: `[0, 1000, 3000, 5000]`). - -This is also used for [`signPart()`](#signpartfile-partdata). Set to `null` to -disable automatic retries, and fail instantly if any chunk fails to upload. - -#### `getChunkSize(file)` - -A function that returns the minimum chunk size to use when uploading the given -file as multipart. - -For multipart uploads, chunks are sent in batches to have presigned URLs -generated with [`signPart()`](#signpartfile-partdata). To reduce the amount of -requests for large files, you can choose a larger chunk size, at the cost of -having to re-upload more data if one chunk fails to upload. - -S3 requires a minimum chunk size of 5MiB, and supports at most 10,000 chunks per -multipart upload. If `getChunkSize()` returns a size that’s too small, Uppy will -increase it to S3’s minimum requirements. - -#### `getUploadParameters(file, options)` - -:::note - -When using [Companion][companion docs] to sign S3 uploads, you should not define -this option. - -::: - -A function that will be called for each non-multipart upload. - -- `file`: `UppyFile` the file that will be uploaded -- `options`: `object` - - `signal`: `AbortSignal` -- **Returns:** `object | Promise` - - `method`: `string`, the HTTP method to be used for the upload. This should - be one of either `PUT` or `POST`, depending on the type of upload used. - - `url`: `string`, the URL to which the upload request will be sent. When - using a presigned PUT upload, this should be the URL to the S3 object with - signing parameters included in the query string. When using a POST upload - with a policy document, this should be the root URL of the bucket. - - `fields` `object`, an object with form fields to send along with the upload - request. For presigned PUT uploads (which are default), this should be left - empty. - - `headers`: `object`, an object with request headers to send along with the - upload request. When using a presigned PUT upload, it’s a good idea to - provide `headers['content-type']`. That will make sure that the request uses - the same content-type that was used to generate the signature. Without it, - the browser may decide on a different content-type instead, causing S3 to - reject the upload. - -#### `createMultipartUpload(file)` - -A function that calls the S3 Multipart API to create a new upload. - -`file` is the file object from Uppy’s state. The most relevant keys are -`file.name` and `file.type`. - -Return a Promise for an object with keys: - -- `uploadId` - The UploadID returned by S3. -- `key` - The object key for the file. This needs to be returned to allow it to - be different from the `file.name`. - -The default implementation calls out to Companion’s S3 signing endpoints. - -#### `listParts(file, { uploadId, key })` - -A function that calls the S3 Multipart API to list the parts of a file that have -already been uploaded. - -Receives the `file` object from Uppy’s state, and an object with keys: - -- `uploadId` - The UploadID of this Multipart upload. -- `key` - The object key of this Multipart upload. - -Return a Promise for an array of S3 Part objects, as returned by the S3 -Multipart API. Each object has keys: - -- `PartNumber` - The index in the file of the uploaded part. -- `Size` - The size of the part in bytes. -- `ETag` - The ETag of the part, used to identify it when completing the - multipart upload and combining all parts into a single file. - -The default implementation calls out to Companion’s S3 signing endpoints. - -#### `signPart(file, partData)` - -A function that generates a signed URL for the specified part number. The -`partData` argument is an object with the keys: - -- `uploadId` - The UploadID of this Multipart upload. -- `key` - The object key in the S3 bucket. -- `partNumber` - can’t be zero. -- `body` – The data that will be signed. -- `signal` – An `AbortSignal` that may be used to abort an ongoing request. - -This function should return a object, or a promise that resolves to an object, -with the following keys: - -- `url` – the presigned URL, as a `string`. -- `headers` – **(Optional)** Custom headers to send along with the request to S3 - endpoint. - -An example of what the return value should look like: - -```json -{ - "url": "https://bucket.region.amazonaws.com/path/to/file.jpg?partNumber=1&...", - "headers": { "Content-MD5": "foo" } -} -``` - -#### `abortMultipartUpload(file, { uploadId, key })` - -A function that calls the S3 Multipart API to abort a Multipart upload, and -removes all parts that have been uploaded so far. - -Receives the `file` object from Uppy’s state, and an object with keys: - -- `uploadId` - The UploadID of this Multipart upload. -- `key` - The object key of this Multipart upload. - -This is typically called when the user cancels an upload. Cancellation cannot -fail in Uppy, so the result of this function is ignored. - -The default implementation calls out to Companion’s S3 signing endpoints. - -#### `completeMultipartUpload(file, { uploadId, key, parts })` - -A function that calls the S3 Multipart API to complete a Multipart upload, -combining all parts into a single object in the S3 bucket. - -Receives the `file` object from Uppy’s state, and an object with keys: - -- `uploadId` - The UploadID of this Multipart upload. -- `key` - The object key of this Multipart upload. -- `parts` - S3-style list of parts, an array of objects with `ETag` and - `PartNumber` properties. This can be passed straight to S3’s Multipart API. - -Return a Promise for an object with properties: - -- `location` - **(Optional)** A publicly accessible URL to the object in the S3 - bucket. - -The default implementation calls out to Companion’s S3 signing endpoints. - -#### `allowedMetaFields: null` - -Pass an array of field names to limit the metadata fields that will be added to -upload as query parameters. - -- Set it to `false` to not send any fields (or an empty array). -- Set it to `['name']` to only send the `name` field. -- Set it to `true` (the default) to send _all_ metadata fields. - -#### `getTemporarySecurityCredentials(options)` - -:::note - -When using [Companion][companion docs] as a backend, you can pass `true` instead -of a function. Setting up Companion will not simplify the process of getting -signing on the client. - -::: - -A boolean (when using Companion), or an (async) function to retrieve temporary -security credentials used for all uploads instead of signing every part. This -results in less request overhead which can lead to around 20% faster uploads. -This is a security tradeoff. We recommend to not use this option unless you are -familiar with the security implications of temporary credentials, and how to -setup your bucket to make it work. See the -[Requesting temporary security credentials](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_temp_request.html) -AWS guide for more information. - -It’s strongly recommended to have some sort of caching process to avoid -requesting more temporary token than necessary. - -- `options`: `object` - - `signal`: `AbortSignal` -- **Returns:** `object | Promise` - - `credentials`: `object` - - `AccessKeyId`: `string` - - `SecretAccessKey`: `string` - - `SessionToken`: `string` - - `Expiration`: `string` - - `bucket`: `string` - - `region`: `string` - -If you are using Companion (for example because you want to support remote -upload sources), you can pass a boolean: - -```js -uppy.use(AwsS3, { - // This is an example using Companion: - endpoint: 'http://companion.uppy.io', - getTemporarySecurityCredentials: true, - shouldUseMultipart: (file) => file.size > 100 * 2 ** 20, -}); -``` - -In the most common case, you are using a different backend, in which case you -need to specify a function: - -```js -uppy.use(AwsS3, { - // This is an example not using Companion: - async getTemporarySecurityCredentials({ signal }) { - const response = await fetch('/sts-token', { signal }); - if (!response.ok) - throw new Error('Failed to fetch STS', { cause: response }); - return response.json(); - }, - shouldUseMultipart: (file) => file.size > 100 * 2 ** 20, -}); -``` - -[`gettemporarysecuritycredentials`]: #gettemporarysecuritycredentialsoptions -[`shouldusemultipart`]: #shouldusemultipartfile -[companion docs]: /docs/companion -[signing on the client]: #gettemporarysecuritycredentialsoptions diff --git a/docs/uploader/transloadit.mdx b/docs/uploader/transloadit.mdx deleted file mode 100644 index 4464ca25a..000000000 --- a/docs/uploader/transloadit.mdx +++ /dev/null @@ -1,662 +0,0 @@ ---- -sidebar_position: 1 -slug: /transloadit ---- - -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; -import UppyCdnExample from '/src/components/UppyCdnExample'; - -# Transloadit - -The `@uppy/transloadit` plugin can be used to upload files directly to -[Transloadit](https://transloadit.com/) for all kinds of processing, such as -transcoding video, resizing images, zipping/unzipping, [and much -more][transloadit-services]. - -## When should I use it? - -:::tip - -Not sure which uploader is best for you? Read -“[Choosing the uploader you need](/docs/guides/choosing-uploader)”. - -::: - -Transloadit’s strength is versatility. By doing video, audio, images, documents, -and more, you only need one vendor for [all your file processing -needs][transloadit-services]. The `@uppy/transloadit` plugin directly uploads to -Transloadit so you only have to worry about creating a -[Template][transloadit-concepts]. Transloadit accepts the files, processes -according to the instructions in the Template, and stores the results in storage -of your choosing, such as a self-owned S3 bucket. The Transloadit plugin uses -[Tus](/docs/tus) under the hood so you don’t have to sacrifice reliable, -resumable uploads. - -You should use `@uppy/transloadit` if you don’t want to host your own Tus or -Companion servers, (optionally) need file processing, and store it in the -service (such as S3 or GCS) of your liking. All with minimal effort. - -## Install - - - - -```shell -npm install @uppy/transloadit -``` - - - - - -```shell -yarn add @uppy/transloadit -``` - - - - - - {` - import { Uppy, Transloadit } from "{{UPPY_JS_URL}}" - new Uppy().use(Transloadit, { /* see options */ }) - `} - - - - -## Use - -A quick overview of the complete API. - -```js {10-17} showLineNumbers -import Uppy from '@uppy/core'; -import Dashboard from '@uppy/dashboard'; -import Transloadit from '@uppy/transloadit'; - -import '@uppy/core/dist/style.min.css'; -import '@uppy/dashboard/dist/style.min.css'; - -const uppy = new Uppy() - .use(Dashboard, { inline: true, target: 'body' }) - .use(Transloadit, { - assemblyOptions: { - params: { - auth: { key: 'your-transloadit-key' }, - template_id: 'your-template-id', - }, - }, - }); -// Optionally listen to events -uppy.on('transloadit:assembly-created', (assembly, fileIDs) => {}); -uppy.on('transloadit:upload', (file, assembly) => {}); -uppy.on('transloadit:assembly-executing', (assembly) => {}); -uppy.on('transloadit:result', (stepName, result, assembly) => {}); -uppy.on('transloadit:complete', (assembly) => {}); -``` - -### Use with Companion - -:::note - -All [Transloadit plans](https://transloadit.com/pricing/) come with a hosted -version of Companion. - -::: - -You can use this plugin together with Transloadit’s hosted Companion service to -let your users import files from third party sources across the web. To do so -each provider plugin must be configured with Transloadit’s Companion URLs: - -```js -import { COMPANION_URL, COMPANION_ALLOWED_HOSTS } from '@uppy/transloadit'; -import Dropbox from '@uppy/dropbox'; - -uppy.use(Dropbox, { - companionUrl: COMPANION_URL, - companionAllowedHosts: COMPANION_ALLOWED_HOSTS, -}); -``` - -This will already work. Transloadit’s OAuth applications are used to -authenticate your users by default. Your users will be asked to provide -Transloadit access to their files. Since your users are probably not aware of -Transloadit, this may be confusing or decrease trust. You may also hit rate -limits, because the OAuth application is shared between everyone using -Transloadit. - -To solve that, you can use your own OAuth keys with Transloadit’s hosted -Companion servers by using Transloadit Template Credentials. [Create a Template -Credential][template-credentials] on the Transloadit site. Select “Companion -OAuth” for the service, and enter the key and secret for the provider you want -to use. Then you can pass the name of the new credentials to that provider: - -```js -import { COMPANION_URL, COMPANION_ALLOWED_HOSTS } from '@uppy/transloadit'; -import Dropbox from '@uppy/dropbox'; - -uppy.use(Dropbox, { - companionUrl: COMPANION_URL, - companionAllowedHosts: COMPANION_ALLOWED_HOSTS, - companionKeysParams: { - key: 'YOUR_TRANSLOADIT_API_KEY', - credentialsName: 'my_companion_dropbox_creds', - }, -}); -``` - -## API - -### Options - -#### `id` - -A unique identifier for this plugin (`string`, default: `'Transloadit'`). - -#### `service` - -The Transloadit API URL to use (`string`, default: -`https://api2.transloadit.com`). - -The default will try to route traffic efficiently based on the location of your -users. You could for instance set it to `https://api2-us-east-1.transloadit.com` -if you need the traffic to stay inside a particular region. - -#### `limit` - -Limit the amount of uploads going on at the same time (`number`, default: `20`). - -Setting this to `0` means no limit on concurrent uploads, but we recommend a -value between `5` and `20`. This option is passed through to the -[`@uppy/tus`](/docs/tus) plugin, which this plugin uses internally. - -#### `assemblyOptions` - -Configure the -[Assembly Instructions](https://transloadit.com/docs/topics/assembly-instructions/), -the fields to send along to the assembly, and authentication -(`object | function`, default: `null`). - -The object you can pass or return from a function has this structure: - -```js -{ - params: { - auth: { key: 'key-from-transloadit' }, - template_id: 'id-from-transloadit', - steps: { - // Overruling Template at runtime - }, - notify_url: 'https://your-domain.com/assembly-status', - }, - signature: 'generated-signature', - fields: { - // Dynamic or static fields to send along - }, -} -``` - -- `params` is used to authenticate with Transloadit and using your desired - [template](https://transloadit.com/docs/topics/templates/). - - `auth.key` _(required)_ is your authentication key which you can find on the - “Credentials” page of your account. - - `template_id` _(required)_ is the unique identifier to use the right - template from your account. - - `steps` _(optional)_ can be used to - [overrule Templates at runtime](https://transloadit.com/docs/topics/templates/#overruling-templates-at-runtime). - A typical use case might be changing the storage path on the fly based on - the session user id. For most use cases, we recommend to let your Templates - handle dynamic cases (they can accept `fields` and execute arbitrary - JavaScript as well), and not pass in `steps` from a browser. The template - editor also has extra validations and context. - - `notify_url` _(optional)_ is a pingback with the assembly status as JSON. - For instance, if you don’t want to block the user experience by letting them - wait for your template to complete with - [`waitForEncoding`](#waitForEncoding), but you do want to want to - asynchrounously have an update, you can provide an URL which will be - “pinged” with the assembly status. -- `signature` _(optional, but recommended)_ is a cryptographic signature to - provide further trust in unstrusted environments. Refer to - “[Signature Authentication”](https://transloadit.com/docs/topics/signature-authentication/) - for more information. -- `fields` _(optional)_ can be used to to send along key/value pairs, which can - be - [used dynamically in your template](https://transloadit.com/docs/topics/assembly-instructions/#form-fields-in-instructions). - -:::info - -All your files end up in a single assembly and your `fields` are available -globally in your template. The metadata in your Uppy files is also sent along so -you can do things dynamically per file with `file.user_meta` in your template. - -::: - -
- Examples - -**As a function** - -Most likely you want to use a function to call your backend to generate a -signature and return your configuration. - -```js -uppy.use(Transloadit, { - async assemblyOptions(file) { - const res = await fetch('/transloadit-params'); - return res.json(); - }, -}); -``` - -**As an object** - -If you don’t need to change anything dynamically, you can also pass an object -directly. - -```js -uppy.use(Transloadit, { - assemblyOptions: { - params: { auth: { key: 'transloadit-key' } }, - }, -}); -``` - -**Use with @uppy/form** - -Combine the `assemblyOptions()` option with the [Form](/docs/form) plugin to -pass user input from a `` to a Transloadit Assembly: - -```js -// This will add form field values to each file's `.meta` object: -uppy.use(Form, { getMetaFromForm: true }); -uppy.use(Transloadit, { - async assemblyOptions() { - const res = await fetch('/transloadit-params'); - return res.json(); - }; -}); -``` - -
- -:::caution - -When you go to production always make sure to set the `signature`. **Not using -[Signature Authentication](https://transloadit.com/docs/topics/signature-authentication/) -can be a security risk**. Signature Authentication is a security measure that -can prevent outsiders from tampering with your Assembly Instructions. While -Signature Authentication is not implemented (yet), we recommend to disable -`allow_steps_override` in your Templates to avoid outsiders being able to pass -in any Instructions and storage targets on your behalf. - -::: - -#### `waitForEncoding` - -Wait for the template to finish, rather than only the upload, before marking the -upload complete (`boolean`, default: `false`). - -- When `false`, the Assemblies will complete (or error) in the background but - Uppy won’t know or care about it. You may have to let Transloadit ping you via - a `notify_url` and asynchronously inform your user (email, in-app - notification). -- When `true`, the Transloadit plugin waits for Assemblies to complete before - the files are marked as completed. This means users have to wait for a - potentially long time, depending on how complicated your Assembly instructions - are. But, you can receive the final status and transcoding results on the - client side with less effort. - -When this is enabled, you can listen for the -[`transloadit:result`](#transloaditresult) and -[`transloadit:complete`](#transloaditcomplete) events. - -#### `waitForMetadata` - -Wait for Transloadit’s backend to catch early errors, not the entire Assembly to -complete. (`boolean`, default: `false`) - -When set to `true`, the Transloadit plugin waits for Transloadit’s backend to -extract metadata from all the uploaded files. This is mostly handy if you want -to have a quick user experience (so your users don’t necessarily need to wait -for all the encoding to complete), but you do want to let users know about some -types of errors that can be caught early on, like file format issues. - -You you can listen for the [`transloadit:upload`](#transloaditupload) event when -this or `waitForEncoding` is enabled. - -#### `importFromUploadURLs` - -Allow another plugin to upload files, and then import those files into the -Transloadit Assembly (`boolean`, default: `false`). - -When enabling this option, Transloadit will _not_ configure the Tus plugin to -upload to Transloadit. Instead, a separate upload plugin must be used. Once the -upload completes, the Transloadit plugin adds the uploaded file to the Assembly. - -For example, to upload files to an S3 bucket and then transcode them: - -```js -uppy.use(AwsS3, { - getUploadParameters(file) { - return { - /* upload parameters */ - }; - }, -}); -uppy.use(Transloadit, { - importFromUploadURLs: true, - assemblyOptions: { - params: { - auth: { key: 'YOUR_API_KEY' }, - template_id: 'YOUR_TEMPLATE_ID', - }, - }, -}); -``` - -Tranloadit will download the files and expose them to your Template as -`:original`, as if they were directly uploaded from the Uppy client. - -:::note - -For this to work, the upload plugin must assign a publicly accessible -`uploadURL` property to the uploaded file object. The Tus and S3 plugins both do -this automatically, but you must configure your S3 bucket to have publicly -readable objects. For the XHRUpload plugin, you may have to specify a custom -`getResponseData` function. - -::: - -#### `alwaysRunAssembly` - -Always create and run an Assembly when `uppy.upload()` is called, even if no -files were selected (`boolean`, default: `false`). - -This allows running Assemblies that do not receive files, but instead use a -robot like [`/s3/import`](https://transloadit.com/docs/transcoding/#s3-import) -to download the files from elsewhere, for example, for a bulk transcoding job. - -#### `locale` - -```js -export default { - strings: { - // Shown while Assemblies are being created for an upload. - creatingAssembly: 'Preparing upload...', - // Shown if an Assembly could not be created. - creatingAssemblyFailed: 'Transloadit: Could not create Assembly', - // Shown after uploads have succeeded, but when the Assembly is still executing. - // This only shows if `waitForMetadata` or `waitForEncoding` was enabled. - encoding: 'Encoding...', - }, -}; -``` - -#### `clientName` - -Append a custom client name to the `Transloadit-Client` header field when -creating an Assembly (`string`, default: `null`). - -The `Transloadit-Client` header includes by default information about the used -SDK and is included in the Assembly Status under the `transloadit_client` -property. By providing a value, such as `homepage-file-uploader`, you can -identify the client and SDK that created a given Assembly. - -
- Deprecated options - -These options have been deprecated in favor of -[`assemblyOptions`](#assemblyoptions), which we now recommend for all use cases. -You can still use these options, but they will be removed in the next major -version. - -#### `getAssemblyOptions` - -This function behaves the same as passing a function to -[`assemblyOptions`](#assemblyoptions). - -#### `params` - -The Assembly parameters to use for the upload (`object`, default: `null`) See -the Transloadit documentation on -[Assembly Instructions](https://transloadit.com/docs/#14-assembly-instructions) -for further information. - -The `auth.key` Assembly parameter is required. You can also use the `steps` or -`template_id` options here as described in the Transloadit documentation. - -```js -uppy.use(Transloadit, { - params: { - auth: { key: 'YOUR_TRANSLOADIT_KEY' }, - steps: { - encode: { - robot: '/video/encode', - use: { - steps: [':original'], - fields: ['file_input_field2'], - }, - preset: 'iphone', - }, - }, - }, -}); -``` - -#### `signature` - -An optional signature for the Assembly parameters. See the Transloadit -documentation on -[Signature Authentication](https://transloadit.com/docs/#26-signature-authentication) -for further information. - -If a `signature` is provided, `params` should be a JSON string instead of a -JavaScript object, as otherwise the generated JSON in the browser may be -different from the JSON string that was used to generate the signature. - -#### `fields` - -An object of form fields to send along to the Assembly. Keys are field names, -and values are field values. See also the Transloadit documentation on -[Form Fields In Instructions](https://transloadit.com/docs/#23-form-fields-in-instructions). - -```js -uppy.use(Transloadit, { - // ... - fields: { - message: 'This is a form field', - }, -}); -``` - -You can also pass an array of field names to send global or file metadata along -to the Assembly. Global metadata is set using the -[`meta` option](/docs/uppy/#meta) in the Uppy constructor, or using the -[`setMeta` method](/docs/uppy/#uppy-setMeta-data). File metadata is set using -the [`setFileMeta`](/docs/uppy/#uppy-setFileMeta-fileID-data) method. The -[Form](/docs/form) plugin also sets global metadata based on the values of -``s in the form, providing a handy way to use values from HTML form -fields: - -```js -uppy.use(Form, { target: 'form#upload-form', getMetaFromForm: true }); -uppy.use(Transloadit, { - fields: ['field_name', 'other_field_name'], - params: { - /* ... */ - }, -}); -``` - -Form fields can also be computed dynamically using custom logic, by using the -[`getAssemblyOptions(file)`](/docs/transloadit/#getAssemblyOptions-file) option. - -
- -### Static exports - -#### `COMPANION_URL` - -The main endpoint for Transloadit’s hosted companions. You can use this constant -in remote provider options, like so: - -```js -import Dropbox from '@uppy/dropbox'; -import { COMPANION_URL } from '@uppy/transloadit'; - -uppy.use(Dropbox, { - companionUrl: COMPANION_URL, -}); -``` - -When using `COMPANION_URL`, you should also configure -[`companionAllowedHosts`](#companion_allowed_hosts). - -The value of this constant is `https://api2.transloadit.com/companion`. If you -are using a custom [`service`](#service) option, you should also set a custom -host option in your provider plugins, by taking a Transloadit API url and -appending `/companion`: - -```js -uppy.use(Dropbox, { - companionUrl: 'https://api2-us-east-1.transloadit.com/companion', -}); -``` - -#### `COMPANION_ALLOWED_HOSTS` - -A RegExp pattern matching Transloadit’s hosted companion endpoints. The pattern -is used in remote provider `companionAllowedHosts` options, to make sure that -third party authentication messages cannot be faked by an attacker’s page but -can only originate from Transloadit’s servers. - -Use it whenever you use `companionUrl: COMPANION_URL`, like so: - -```js -import Dropbox from '@uppy/dropbox'; -import { COMPANION_ALLOWED_HOSTS } from '@uppy/transloadit'; - -uppy.use(Dropbox, { - companionAllowedHosts: COMPANION_ALLOWED_HOSTS, -}); -``` - -The value of this constant covers _all_ Transloadit’s Companion servers, so it -does not need to be changed if you are using a custom [`service`](#service) -option. But, if you are not using the Transloadit Companion servers at -`*.transloadit.com`, make sure to set the `companionAllowedHosts` option to -something that matches what you do use. - -### Events - -#### `transloadit:assembly-created` - -Fired when an Assembly is created. - -**Parameters** - -- `assembly` - The initial [Assembly Status][assembly-status]. -- `fileIDs` - The IDs of the files that will be uploaded to this Assembly. - -```js -uppy.on('transloadit:assembly-created', (assembly, fileIDs) => { - console.group('Created', assembly.assembly_id, 'for files:'); - for (const id of fileIDs) { - console.log(uppy.getFile(id).name); - } - console.groupEnd(); -}); -``` - -#### `transloadit:upload` - -Fired when Transloadit has received an upload. Requires -[`waitForMetadata`](#waitformetadata) to be set. - -**Parameters** - -- `file` - The Transloadit file object that was uploaded. -- `assembly` - The [Assembly Status][assembly-status] of the Assembly to which - the file was uploaded. - -#### `transloadit:assembly-executing` - -Fired when Transloadit has received all uploads, and is executing the Assembly. - -**Parameters** - -- `assembly` - The - [Assembly Status](https://transloadit.com/docs/api/#assembly-status-response) - of the Assembly that is executing. - -#### `transloadit:result` - -Fired when a result came in from an Assembly. Requires -[`waitForEncoding`](#waitforencoding) to be set. - -**Parameters** - -- `stepName` - The name of the Assembly step that generated this result. -- `result` - The result object from Transloadit. This result object has one more - property, namely `localId`. This is the ID of the file in Uppy’s local state, - and can be used with `uppy.getFile(id)`. -- `assembly` - The [Assembly Status][assembly-status] of the Assembly that - generated this result. - -```js -uppy.on('transloadit:result', (stepName, result) => { - const file = uppy.getFile(result.localId); - document.body.appendChild(html` -
-

From ${file.name}

- View -
- `); -}); -``` - -#### `transloadit:complete` - -Fired when an Assembly completed. Requires [`waitForEncoding`](#waitForEncoding) -to be set. - -**Parameters** - -- `assembly` - The final [Assembly Status][assembly-status] of the completed - Assembly. - -```js -uppy.on('transloadit:complete', (assembly) => { - // Could do something fun with this! - console.log(assembly.results); -}); -``` - -## Frequently Asked Questions - -### Accessing the assembly when an error occurred - -If an error occurs when an Assembly has already started, you can find the -Assembly Status on the error object’s `assembly` property. - -```js -uppy.on('error', (error) => { - if (error.assembly) { - console.log(`Assembly ID ${error.assembly.assembly_id} failed!`); - console.log(error.assembly); - } -}); -``` - -### Assembly behavior when Uppy is closed - -When integrating `@uppy/transloadit` with `@uppy/dashboard`, closing the -dashboard will result in continuing assemblies on the server. When the user -manually cancels the upload any running assemblies will be cancelled. - -[assembly-status]: https://transloadit.com/docs/api/#assembly-status-response -[template-credentials]: - https://transloadit.com/docs/#how-to-create-template-credentials -[transloadit-services]: https://transloadit.com/services/ -[transloadit-concepts]: https://transloadit.com/docs/getting-started/concepts/ diff --git a/docs/uploader/tus.mdx b/docs/uploader/tus.mdx deleted file mode 100644 index 9c3abf39f..000000000 --- a/docs/uploader/tus.mdx +++ /dev/null @@ -1,308 +0,0 @@ ---- -sidebar_position: 2 -slug: /tus ---- - -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; -import UppyCdnExample from '/src/components/UppyCdnExample'; - -# Tus - -The `@uppy/tus` plugin brings resumable file uploading with [Tus](http://tus.io) -to Uppy by wrapping the [`tus-js-client`][]. - -## When should I use it? - -:::tip - -Not sure which uploader is best for you? Read -“[Choosing the uploader you need](/docs/guides/choosing-uploader)”. - -::: - -[Tus][tus] is an open protocol for resumable uploads built on HTTP. This means -accidentally closing your tab or losing connection let’s you continue, for -instance, your 10GB upload instead of starting all over. - -Tus supports any language, any platform, and any network. It requires a client -and server integration to work. You can checkout the client and server -[implementations][] to find the server in your preferred language. You can store -files on the Tus server itself, but you can also use service integrations (such -as S3) to store files externally. If you don’t want to host your own server, see -“[Are there hosted Tus servers?](#are-there-hosted-tus-servers)”. - -If you want reliable, resumable uploads: use `@uppy/tus` to connect to your Tus -server in a few lines of code. - -## Install - - - - -```shell -npm install @uppy/tus -``` - - - - - -```shell -yarn add @uppy/tus -``` - - - - - - {` - import { Uppy, Tus } from "{{UPPY_JS_URL}}" - new Uppy().use(Tus, { endpoint: 'https://tusd.tusdemo.net/files' }) - `} - - - - -## Use - -A quick overview of the complete API. - -```js {10} showLineNumbers -import Uppy from '@uppy/core'; -import Dashboard from '@uppy/dashboard'; -import Tus from '@uppy/tus'; - -import '@uppy/core/dist/style.min.css'; -import '@uppy/dashboard/dist/style.min.css'; - -new Uppy() - .use(Dashboard, { inline: true, target: 'body' }) - .use(Tus, { endpoint: 'https://tusd.tusdemo.net/files/' }); -``` - -## API - -### Options - -:::info - -All options are passed to `tus-js-client` and we document the ones here that are -required, added, or changed. This means you can also pass functions like -[`onAfterResponse`](https://github.com/tus/tus-js-client/blob/master/docs/api.md#onafterresponse). - -We recommended taking a look at the -[API reference](https://github.com/tus/tus-js-client/blob/master/docs/api.md) -from `tus-js-client` to know what is supported. - -::: - -#### `id` - -A unique identifier for this plugin (`string`, default: `'Tus'`). - -#### `endpoint` - -URL of the tus server (`string`, default: `null`). - -#### `headers` - -An object or function returning an object with HTTP headers to send along -requests (`object | function`, default: `null`). - -Keys are header names, values are header values. - -```js -const headers = { - authorization: `Bearer ${window.getCurrentUserToken()}`, -}; -``` - -Header values can also be derived from file data by providing a function. The -function receives an [Uppy file][] and must return an object where the keys are header -names, and values are header values. - -```js -const headers = (file) => { - return { - authorization: `Bearer ${window.getCurrentUserToken()}`, - expires: file.meta.expires, - }; -}; -``` - -#### `chunkSize` - -A number indicating the maximum size of a `PATCH` request body in bytes -(`number`, default: `Infinity`). Note that this option only affects local -browser uploads. If you need a max chunk size for remote (Companion) uploads, -you must set the `chunkSize` Companion option as well. - -:::caution - -Do not set this value unless you are forced to. The two valid reasons are -described in the -[`tus-js-client` docs](https://github.com/tus/tus-js-client/blob/master/docs/api.md#chunksize). - -::: - -#### `withCredentials` - -Configure the requests to send Cookies using the -[`xhr.withCredentials`](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/withCredentials) -property (`boolean`, default: `false`). - -The remote server must accept CORS and credentials. - -#### `retryDelays` - -When uploading a chunk fails, automatically try again after the defined -millisecond intervals (`Array`, default: `[0, 1000, 3000, 5000]`). - -By default, we first retry instantly; if that fails, we retry after 1 second; if -that fails, we retry after 3 seconds, etc. - -Set to `null` to disable automatic retries, and fail instantly if any chunk -fails to upload. - -#### `onBeforeRequest(req, file)` - -Behaves like the -[`onBeforeRequest`](https://github.com/tus/tus-js-client/blob/master/docs/api.md#onbeforerequest) -function from `tus-js-client` but with the added `file` argument. - -#### `onShouldRetry: (err, retryAttempt, options, next)` - -When an upload fails `onShouldRetry` is called with the error and the default -retry logic as the last argument (`function`). - -The default retry logic is an -[exponential backoff](https://en.wikipedia.org/wiki/Exponential_backoff) -algorithm triggered on HTTP 429 (Too Many Requests) errors. Meaning if your -server (or proxy) returns HTTP 429 because it’s being overloaded, @uppy/tus will -find the ideal sweet spot to keep uploading without overloading. - -If you want to extend this functionality, for instance to retry on unauthorized -requests (to retrieve a new authentication token): - -```js -import Uppy from '@uppy/core'; -import Tus from '@uppy/tus'; -new Uppy().use(Tus, { - endpoint: '', - async onBeforeRequest(req) { - const token = await getAuthToken(); - req.setHeader('Authorization', `Bearer ${token}`); - }, - onShouldRetry(err, retryAttempt, options, next) { - if (err?.originalResponse?.getStatus() === 401) { - return true; - } - return next(err); - }, - async onAfterResponse(req, res) { - if (res.getStatus() === 401) { - await refreshAuthToken(); - } - }, -}); -``` - -#### `allowedMetaFields` - -Pass an array of field names to limit the metadata fields that will be added to -uploads as -[Tus Metadata](https://tus.io/protocols/resumable-upload.html#upload-metadata) -(`Array`, default: `null`). - -- Set it to `false` to not send any fields (or an empty array). -- Set it to `['name']` to only send the `name` field. -- Set it to `true` (the default) to send _all_ metadata fields. - -#### `limit` - -Limit the amount of uploads going on at the same time (`number`, default: `20`). - -Setting this to `0` means no limit on concurrent uploads (not recommended). - -## Frequently Asked Questions - -:::info - -The Tus website has extensive [FAQ section](https://tus.io/faq.html), we -recommend taking a look there as well if something is unclear. - -::: - -### How is file meta data stored? - -Tus uses unique identifiers for the file names to prevent naming collisions. To -still keep the meta data in place, Tus also uploads an extra `.info` file with -the original file name and other meta data: - -```json -{ - "ID": "00007a99d16d4eeb5a3e3c080b6f69da+JHZavdqPSK4VMtarg2yYcNiP8t_kDjN51lBYMJdEyr_wqEotVl8ZBRBSTnWKWenZBwHvbLNz5tQXYp2N7Vdol.04ysQAuw__suTJ4IsCljj0rjyWA6LvV4IwF5P2oom2", - "Size": 1679852, - "SizeIsDeferred": false, - "Offset": 0, - "MetaData": { - "filename": "cat.jpg", - "filetype": "image/jpeg" - }, - "IsPartial": false, - "IsFinal": false, - "PartialUploads": null, - "Storage": { - "Bucket": "your-bucket", - "Key": "some-key", - "Type": "s3store" - } -} -``` - -### How do I change files before sending them? - -If you want to change the file names, you want to do that in -[`onBeforeFileAdded`](/docs/uppy#onbeforefileaddedfile-files). - -If you want to send extra headers with the request, use [`headers`](#headers) or -[`onBeforeRequest`](#onbeforerequestreq-file). - -### How do I change (or move) files after sending them? - -If you want to preserve files names, extract meta data, or move files to a -different place you generally can with hooks or events. It depends on the Tus -server you use how it’s done exactly. [`tusd`][], for instance, exposes -[hooks](https://github.com/tus/tusd/blob/master/docs/hooks.md) and -[`tus-node-server`](https://github.com/tus/tus-node-server) has -[events](https://github.com/tus/tus-node-server#events). - -### Which server do you recommend? - -[Transloadit](https://transloadit.com) runs [`tusd`][] in production, where it -serves millions of requests globally. So we recommend `tusd` as battle-tested -from our side, but other companies have had success with other -[implementations][] so it depends on your needs. - -### Are there hosted Tus servers? - -All [Transloadit plans](https://transloadit.com/pricing) come with a hosted -[`tusd`][] server. You don’t have to do anything to leverage it, using -[`@uppy/transloadit`](/docs/transloadit) automatically uses Tus under the hood. - -### Why Tus instead of directly uploading to AWS S3? - -First: reliable, resumable uploads. This means accidentally closing your tab or -losing connection let’s you continue, for instance, your 10GB upload instead of -starting all over. - -Tus is also efficient with lots of files (such as 8K) and large files. Uploading -to AWS S3 directly from the client also introduces quite a bit of overhead, as -more requests are needed for the flow to work. - -[`tus-js-client`]: https://github.com/tus/tus-js-client -[uppy file]: /docs/uppy#working-with-uppy-files -[tus]: https://tus.io/ -[`tusd`]: https://github.com/tus/tusd -[implementations]: https://tus.io/implementations.html diff --git a/docs/uploader/xhr.mdx b/docs/uploader/xhr.mdx deleted file mode 100644 index d65c7e3e4..000000000 --- a/docs/uploader/xhr.mdx +++ /dev/null @@ -1,380 +0,0 @@ ---- -sidebar_position: 5 -slug: /xhr-upload ---- - -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; -import UppyCdnExample from '/src/components/UppyCdnExample'; - -# XHR - -The `@uppy/xhr-upload` plugin is for regular uploads to a HTTP server. - -## When should I use it? - -:::tip - -Not sure which uploader is best for you? Read -“[Choosing the uploader you need](/docs/guides/choosing-uploader)”. - -::: - -When you have an existing HTTP server and you don’t need Transloadit services or -want to run a [tus][] server. Note that it’s still possible to use [tus][] -without running an extra server by integrating tus into your existing one. For -instance, if you have a Node.js server (or server-side framework like Next.js) -you could integrate [tus-node-server][]. - -## Install - - - - -```shell -npm install @uppy/xhr-upload -``` - - - - - -```shell -yarn add @uppy/xhr-upload -``` - - - - - - {` - import { Uppy, XHRUpload } from "{{UPPY_JS_URL}}" - new Uppy().use(XHRUpload, { endpoint: 'https://tusd.tusdemo.net/files' }) - `} - - - - -## Use - -A quick overview of the complete API. - -```js {10} showLineNumbers -import Uppy from '@uppy/core'; -import Dashboard from '@uppy/dashboard'; -import XHR from '@uppy/xhr-upload'; - -import '@uppy/core/dist/style.min.css'; -import '@uppy/dashboard/dist/style.min.css'; - -new Uppy() - .use(Dashboard, { inline: true, target: 'body' }) - .use(XHR, { endpoint: 'https://your-domain.com/upload' }); -``` - -## API - -### Options - -#### `id` - -A unique identifier for this plugin (`string`, default: `'XHRUpload'`). - -#### `endpoint` - -URL of the HTTP server (`string`, default: `null`). - -#### `method` - -Configures which HTTP method to use for the upload (`string`, default: -`'POST'`). - -#### `formData` - -Configures whether to use a multipart form upload, using [FormData][] -(`boolean`, default: `true`). - -This works similarly to using a `` element with an `` -for uploads. When set to `true`, file metadata is also sent to the endpoint as -separate form fields. When set to `false`, only the file contents are sent. - -#### `fieldName` - -When [`formData`](#formData-true) is set to true, this is used as the form field -name for the file to be uploaded. - -It defaults to `'files[]'` if `bundle` option is set to `true`, otherwise it -defaults to `'file'`. - -#### `allowedMetaFields` - -Pass an array of field names to limit the metadata fields that will be added to -upload. - -- Set it to `false` to not send any fields (or an empty array). -- Set it to `['name']` to only send the `name` field. -- Set it to `true` (the default) to send _all_ metadata fields. - -If the [`formData`](#formData-true) option is set to false, `metaFields` is -ignored. - -#### `headers` - -An object containing HTTP headers to use for the upload request. Keys are header -names, values are header values. - -```js -const headers = { - authorization: `Bearer ${window.getCurrentUserToken()}`, -}; -``` - -Header values can also be derived from file data by providing a function. The -function receives an [Uppy file][] and must return an object where the keys are header -names, and values are header values. - -```js -const headers = (file) => { - return { - authorization: `Bearer ${window.getCurrentUserToken()}`, - expires: file.meta.expires, - }; -}; -``` - -:::note - -The function syntax is not available when [`bundle`](#bundle) is set to `true`. - -::: - -:::note - -Failed requests are retried with the same headers. If you want to change the -headers on retry, -[such as refreshing an auth token](#how-can-I-refresh-auth-tokens-after-they-expire), -you can use [`onBeforeRequest`](#onbeforerequest). - -::: - -#### `bundle` - -Send all files in a single multipart request (`boolean`, default: `false`). - -All files will be appended to the provided `fieldName` field in the request. - -:::caution - -When `bundle` is set to `true`: - -- [`formData`](#formData-true) must also be set to `true`. -- Uppy won’t be able to bundle remote files (such as Google Drive) and will - throw an error in this case. -- Only [global uppy metadata](/docs/uppy/#meta) is sent to the endpoint. - Individual per-file metadata is ignored. - -::: - -To upload files on different fields, use -[`uppy.setFileState()`](/docs/uppy#uppy-setFileState-fileID-state) to set the -`xhrUpload.fieldName` property on the file: - -```js -uppy.setFileState(fileID, { - xhrUpload: { fieldName: 'pic0' }, -}); -``` - -#### `timeout: 30 * 1000` - -Abort the connection if no upload progress events have been received for this -milliseconds amount (`number`, default: `30_000`). - -Note that unlike the [`XMLHttpRequest.timeout`][xhr.timeout] property, this is a -timer between progress events: the total upload can take longer than this value. -Set to `0` to disable this check. - -#### `limit` - -The maximum amount of files to upload in parallel (`number`, default: `5`). - -#### `responseType` - -The response type expected from the server, determining how the `xhr.response` -property should be filled (`string`, default: `'text'`). - -The `xhr.response` property can be accessed in a custom -[`getResponseData()`](#getResponseData-responseText-response) callback. This -option sets the [`XMLHttpRequest.responseType`][xhr.responsetype] property. Only -`''`, `'text'`, `'arraybuffer'`, `'blob'` and `'document'` are widely supported -by browsers, so it’s recommended to use one of those. - -#### `withCredentials` - -Indicates whether cross-site Access-Control requests should be made using -credentials (`boolean`, default: `false`). - -#### `onBeforeRequest` - -An optional function that will be called before a HTTP request is sent out -(`(xhr: XMLHttpRequest, retryCount: number) => void | Promise`). - -#### `shouldRetry` - -An optional function called once an error appears and before retrying -(`(xhr: XMLHttpRequesT) => boolean`). - -The amount of retries is 3, even if you continue to return `true`. The default -behavior uses -[exponential backoff](https://en.wikipedia.org/wiki/Exponential_backoff) with a -maximum of 3 retries. - -#### `onAfterResponse` - -An optional function that will be called after a HTTP response has been received -(`(xhr: XMLHttpRequest, retryCount: number) => void | Promise`). - -#### `locale: {}` - -```js -export default { - strings: { - // Shown in the Informer if an upload is being canceled because it stalled for too long. - timedOut: 'Upload stalled for %{seconds} seconds, aborting.', - }, -}; -``` - -#### `getResponseData` - -An optional function to turn your non-JSON response into JSON with a `url` -property pointing to the uploaded file -(`(xhr: XMLHttpRequest) => { url: string }`). - -You can also return properties other than `url` and they will end up in -`file.response.body`. If you do, make sure to set the `Body` generic on `Uppy` -to make it typesafe when using TS. - -## Frequently Asked Questions - -### How can I refresh auth tokens after they expire? - -```js -import Uppy from '@uppy/core'; -import XHR from '@uppy/xhr-upload'; - -let token = null; - -async function getAuthToken() { - const res = await fetch('/auth/token'); - const json = await res.json(); - return json.token; -} - -new Uppy().use(XHR, { - endpoint: '', - // Called again for every retry too. - async onBeforeRequest(xhr) { - if (!token) { - token = await getAuthToken(); - } - xhr.setRequestHeader('Authorization', `Bearer ${token}`); - }, - async onAfterResponse(xhr) { - if (xhr.status === 401) { - token = await getAuthToken(); - } - }, -}); -``` - -### How to send along meta data with the upload? - -When using XHRUpload with [`formData: true`](#formData-true), file metadata is -sent along with each upload request. You can set metadata for a file using -[`uppy.setFileMeta(fileID, data)`](/docs/uppy#uppy-setFileMeta-fileID-data), or -for all files simultaneously using -[`uppy.setMeta(data)`](/docs/uppy#uppy-setMeta-data). - -It may be useful to set metadata depending on some file properties, such as the -size. You can use the [`file-added`](/docs/uppy/#file-added) event and the -[`uppy.setFileMeta(fileID, data)`](/docs/uppy#uppy-setFileMeta-fileID-data) -method to do this: - -```js -uppy.on('file-added', (file) => { - uppy.setFileMeta(file.id, { - size: file.size, - }); -}); -``` - -Now, a form field named `size` will be sent along to the -[`endpoint`](#endpoint-39-39) once the upload starts. - -By default, all metadata is sent, including Uppy’s default `name` and `type` -metadata. If you do not want the `name` and `type` metadata properties to be -sent to your upload endpoint, you can use the [`metaFields`](#metaFields-null) -option to restrict the field names that should be sent. - -```js -uppy.use(XHRUpload, { - // Only send our own `size` metadata field. - allowedMetaFields: ['size'], -}); -``` - -### How to upload to a PHP server? - -The XHRUpload plugin works similarly to a `` upload. You can use the -`$_FILES` variable on the server to work with uploaded files. See the PHP -documentation on [Handling file uploads][php.file-upload]. - -The default form field for file uploads is `files[]`, which means you have to -access the `$_FILES` array as described in [Uploading many files][php.multiple]: - -```php - - - -```shell -npm install @uppy/core -``` - - - - - -```shell -yarn add @uppy/core -``` - - - - - - {` - import { Uppy } from "{{UPPY_JS_URL}}" - const uppy = new Uppy() - `} - - - - -## Use - -`@uppy/core` has four exports: `Uppy`, `UIPlugin`, `BasePlugin`, and -`debugLogger`. The default export is the `Uppy` class. - -### Working with Uppy files - -Uppy keeps files in state with the [`File`][] browser API, but it’s wrapped in -an `Object` to be able to add more data to it, which we call an _Uppy file_. All -these properties can be useful for plugins and side-effects (such as -[events](#events)). - -Mutating these properties should be done through [methods](#methods). - -
- Uppy file properties - -#### `file.source` - -Name of the plugin that was responsible for adding this file. Typically a remote -provider plugin like `'GoogleDrive'` or a UI plugin like `'DragDrop'`. - -#### `file.id` - -Unique ID for the file. - -#### `file.name` - -The name of the file. - -#### `file.meta` - -Object containing standard as well as user-defined metadata for each file. Any -custom file metadata should be JSON-serializable. The following standard -metadata will be stored on all file objects, but plugins may add more metadata. - -- `file.meta.name` - - Same as `file.name`. -- `file.meta.type` - - Same as `file.type`. -- `file.meta.relativePath` - - For any local folder that was drag-dropped or opened in Uppy, the files - inside the folder will have the `relativePath` metadata field set to their - path, relative to the folder. `relativePath` begins with the folder’s name - and ends with the file’s name. If opening or drag-dropping a file instead of - a folder, `relativePath` will be `null`. The same behaviour exists for - remote (provider) files, but the path will instead be relative to the user’s - selection (checkboxes). No leading or trailing slashes. - - **Local file example:** When drag-dropping a local folder `folder1` which - has a folder inside of it named `folder2` which has a file named `file` - inside of it, the `relativePath` meta field of the file will be - `folder1/folder2/file`. However if drag-dropping or opening `file` directly, - `relativePath` will be `null`. - - **Remote file example:** Suppose we have a remote provider folder structure - such as `/folder1/folder2/file`. Then, if the user checks the checkbox next - to `folder1`, `file`’s `relativePath` will be `"folder1/folder2/file"`. - However if the user first navigates into `folder1`, and only then checks the - checkbox next to `folder2`, `relativePath` will be `"folder2/file"`. -- `file.meta.absolutePath` - - The `absolutePath` meta field will only be set for remote files. Regardless - of user selection, it will always be the path relative to the root of the - provider’s list of files, as presented to the user. `absolutePath` always - begins with a `/` and will always end with the file’s name. To clarify: The - difference between `absolutePath` and `relativePath` is that `absolutePath` - only exists for remote files, and always has the full path to the file, - while `relativePath` is the file’s path _relative to the user’s selected - folder_. - -#### `file.type` - -MIME type of the file. This may actually be guessed if a file type was not -provided by the user’s browser, so this is a best-effort value and not -guaranteed to be correct. - -#### `file.data` - -For local files, this is the actual [`File`][] or [`Blob`][] object representing -the file contents. - -For files that are imported from remote providers, the file data is not -available in the browser. - -[`file`]: https://developer.mozilla.org/en-US/docs/Web/API/File -[`blob`]: https://developer.mozilla.org/en-US/docs/Web/API/Blob - -#### `file.progress` - -An object with upload progress data. - -**Properties** - -- `bytesUploaded` - Number of bytes uploaded so far. -- `bytesTotal` - Number of bytes that must be uploaded in total. -- `uploadStarted` - Null if the upload has not started yet. Once started, this - property stores a UNIX timestamp. Note that this is only set _after_ - preprocessing. -- `uploadComplete` - Boolean indicating if the upload has completed. Note this - does _not_ mean that postprocessing has completed, too. -- `percentage` - Integer percentage between 0 and 100. - -#### `file.size` - -Size in bytes of the file. - -#### `file.isRemote` - -Boolean: is this file imported from a remote provider? - -#### `file.remote` - -Grab bag of data for remote providers. Generally not interesting for end users. - -#### `file.preview` - -An optional URL to a visual thumbnail for the file. - -#### `file.uploadURL` - -When an upload is completed, this may contain a URL to the uploaded file. -Depending on server configuration it may not be accessible or correct. - -
- -## `new Uppy(options?)` - -```js -import Uppy from '@uppy/core'; - -const uppy = new Uppy(); -``` - -### Options - -#### `id` - -A site-wide unique ID for the instance (`string`, default: `uppy`). - -:::note - -If several Uppy instances are being used, for instance, on two different pages, -an `id` should be specified. This allows Uppy to store information in -`localStorage` without colliding with other Uppy instances. - -This ID should be persistent across page reloads and navigation—it shouldn’t be -a random number that is different every time Uppy is loaded. - -::: - -#### `autoProceed` - -Upload as soon as files are added (`boolean`, default: `false`). - -By default Uppy will wait for an upload button to be pressed in the UI, or the -`.upload()` method to be called before starting an upload. Setting this to -`true` will start uploading automatically after the first file is selected - -#### `allowMultipleUploadBatches` - -Whether to allow several upload batches (`boolean`, default: `true`). - -This means several calls to `.upload()`, or a user adding more files after -already uploading some. An upload batch is made up of the files that were added -since the earlier `.upload()` call. - -With this option set to `true`, users can upload some files, and then add _more_ -files and upload those as well. A model use case for this is uploading images to -a gallery or adding attachments to an email. - -With this option set to `false`, users can upload some files, and you can listen -for the [`'complete'`](#complete) event to continue to the next step in your -app’s upload flow. A typical use case for this is uploading a new profile -picture. If you are integrating with an existing HTML form, this option gives -the closest behaviour to a bare ``. - -#### `debug` - -Whether to send debugging and warning logs (`boolean`, default: `false`). - -Setting this to `true` sets the [`logger`](#logger) to -[`debugLogger`](#debuglogger). - -#### `logger` - -Logger used for [`uppy.log`](#logmessage-type) (`Object`, default: -`justErrorsLogger`). - -By providing your own `logger`, you can send the debug information to a server, -choose to log errors only, etc. - -:::note - -Set `logger` to [`debugLogger`](#debuglogger) to get debug info output to the -browser console: - -::: - -:::note - -You can also provide your own logger object: it should expose `debug`, `warn` -and `error` methods, as shown in the examples below. - -Here’s an example of a `logger` that does nothing: - -```js -const nullLogger = { - debug: (...args) => {}, - warn: (...args) => {}, - error: (...args) => {}, -}; -``` - -::: - -#### `restrictions` - -Conditions for restricting an upload (`Object`, default: `{}`). - -| Property | Value | Description | -| -------------------- | --------------- | -------------------------------------------------------------------------------------------------------------------------------- | -| `maxFileSize` | `number` | maximum file size in bytes for each individual file | -| `minFileSize` | `number` | minimum file size in bytes for each individual file | -| `maxTotalFileSize` | `number` | maximum file size in bytes for all the files that can be selected for upload | -| `maxNumberOfFiles` | `number` | total number of files that can be selected | -| `minNumberOfFiles` | `number` | minimum number of files that must be selected before the upload | -| `allowedFileTypes` | `Array` | wildcards `image/*`, or exact mime types `image/jpeg`, or file extensions `.jpg`: `['image/*', '.jpg', '.jpeg', '.png', '.gif']` | -| `requiredMetaFields` | `Array` | make keys from the `meta` object in every file required before uploading | - -:::note - -`maxNumberOfFiles` also affects the number of files a user is able to select via -the system file dialog in UI plugins like `DragDrop`, `FileInput` and -`Dashboard`. When set to `1`, they will only be able to select a single file. -When `null` or another number is provided, they will be able to select several -files. - -::: - -:::note - -`allowedFileTypes` gets passed to the file system dialog via the -[``](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/file#Limiting_accepted_file_types) -accept attribute, so only types supported by the browser will work. - -::: - -:::tip - -If you’d like to force a certain meta field data to be entered before the -upload, you can -[do so using `onBeforeUpload`](https://github.com/transloadit/uppy/issues/1703#issuecomment-507202561). - -::: - -:::tip - -If you need to restrict `allowedFileTypes` to a file extension with double dots, -like `.nii.gz`, you can do so by -[setting `allowedFileTypes` to the last part of the extension, `allowedFileTypes: ['.gz']`, and then using `onBeforeFileAdded` to filter for `.nii.gz`](https://github.com/transloadit/uppy/issues/1822#issuecomment-526801208). - -::: - -#### `meta` - -Key/value pairs to add to each file’s `metadata` (`Object`, default: `{}`). - -:::note - -Metadata from each file is then attached to uploads in the [Tus](/docs/tus) and -[XHR](/docs/xhr-upload) plugins. - -::: - -:::info - -Two methods also exist for updating `metadata`: [`setMeta`](#setmetadata) and -[`setFileMeta`](#setfilemetafileid-data). - -::: - -:::info - -Metadata can also be added from a `` element on your page, through the -[Form](#) plugin or through the UI if you are using Dashboard with the -[`metaFields`](/docs/dashboard#metafields) option. - -::: - - - -#### `onBeforeFileAdded(file, files)` - -A function called before a file is added to Uppy (`Function`, default: -`(files, file) => !Object.hasOwn(files, file.id)`). - -Use this function to run any number of custom checks on the selected file, or -manipulate it, for instance, by optimizing a file name. You can also allow -duplicate files with this. - -You can return `true` to keep the file as is, `false` to remove the file, or -return a modified file. - -:::caution - -This method is intended for quick synchronous checks and modifications only. If -you need to do an async API call, or heavy work on a file (like compression or -encryption), you should use a [custom plugin](/docs/guides/building-plugins) -instead. - -::: - -:::info - -No notification will be shown to the user about a file not passing validation by -default. We recommend showing a message using -[`uppy.info()`](#infomessage-type-duration) and logging to console for debugging -purposes via [`uppy.log()`](#logmessage-type). - -::: - -
- Filter, change, and abort example - -Allow all files, also duplicate files. This will replace the file if it has not -been uploaded. If you upload a duplicate file again it depends on your upload -plugin and backend how it is handled. - -```js -const uppy = new Uppy({ - // ... - onBeforeFileAdded: () => true, -``` - -Keep only files under a condition: - -```js -const uppy = new Uppy({ - // ... - onBeforeFileAdded: (currentFile, files) => { - if (currentFile.name === 'forest-IMG_0616.jpg') { - return true - } - return false - }, -``` - -Change all file names: - -```js -const uppy = new Uppy({ - // ... - onBeforeFileAdded: (currentFile, files) => { - const modifiedFile = { - ...currentFile, - name: `${currentFile.name}__${Date.now()}`, - } - return modifiedFile - }, -``` - -Abort a file: - -```js -const uppy = new Uppy({ - // ... - onBeforeFileAdded: (currentFile, files) => { - if (!currentFile.type) { - // log to console - uppy.log(`Skipping file because it has no type`); - // show error message to the user - uppy.info(`Skipping file because it has no type`, 'error', 500); - return false; - } - }, -}); -``` - -
- -#### `onBeforeUpload(files)` - -A function called before when upload is initiated (`Function`, default: -`(files) => files`). - -Use this to check if all files or their total number match your requirements, or -manipulate all the files at once before upload. - -You can return `true` to continue the upload, `false` to cancel it, or return -modified files. - -:::caution - -This method is intended for quick synchronous checks and modifications only. If -you need to do an async API call, or heavy work on a file (like compression or -encryption), you should use a [custom plugin](/docs/guides/building-plugins) -instead. - -::: - -:::info - -No notification will be shown to the user about a file not passing validation by -default. We recommend showing a message using -[`uppy.info()`](#infomessage-type-duration) and logging to console for debugging -purposes via [`uppy.log()`](#logmessage-type). - -::: - -
- Change and abort example - -Change all file names: - -```js -const uppy = new Uppy({ - // ... - onBeforeUpload: (files) => { - // We’ll be careful to return a new object, not mutating the original `files` - const updatedFiles = {}; - Object.keys(files).forEach((fileID) => { - updatedFiles[fileID] = { - ...files[fileID], - name: `${myCustomPrefix}__${files[fileID].name}`, - }; - }); - return updatedFiles; - }, -}); -``` - -Abort an upload: - -```js -const uppy = new Uppy({ - // ... - onBeforeUpload: (files) => { - if (Object.keys(files).length < 2) { - // log to console - uppy.log( - `Aborting upload because only ${ - Object.keys(files).length - } files were selected`, - ); - // show error message to the user - uppy.info(`You have to select at least 2 files`, 'error', 500); - return false; - } - return true; - }, -}); -``` - -
- -#### `locale` - -You can override locale strings by passing the `strings` object with the keys -you want to override. - -:::note - -Array indexed objects are used for pluralisation. - -::: - -:::info - -If you want a different language it’s better to use [locales](/docs/locales). - -::: - -```js -module.exports = { - strings: { - addBulkFilesFailed: { - 0: 'Failed to add %{smart_count} file due to an internal error', - 1: 'Failed to add %{smart_count} files due to internal errors', - }, - youCanOnlyUploadX: { - 0: 'You can only upload %{smart_count} file', - 1: 'You can only upload %{smart_count} files', - }, - youHaveToAtLeastSelectX: { - 0: 'You have to select at least %{smart_count} file', - 1: 'You have to select at least %{smart_count} files', - }, - exceedsSize: '%{file} exceeds maximum allowed size of %{size}', - missingRequiredMetaField: 'Missing required meta fields', - missingRequiredMetaFieldOnFile: - 'Missing required meta fields in %{fileName}', - inferiorSize: 'This file is smaller than the allowed size of %{size}', - youCanOnlyUploadFileTypes: 'You can only upload: %{types}', - noMoreFilesAllowed: 'Cannot add more files', - noDuplicates: - "Cannot add the duplicate file '%{fileName}', it already exists", - companionError: 'Connection with Companion failed', - authAborted: 'Authentication aborted', - companionUnauthorizeHint: - 'To unauthorize to your %{provider} account, please go to %{url}', - failedToUpload: 'Failed to upload %{file}', - noInternetConnection: 'No Internet connection', - connectedToInternet: 'Connected to the Internet', - // Strings for remote providers - noFilesFound: 'You have no files or folders here', - selectX: { - 0: 'Select %{smart_count}', - 1: 'Select %{smart_count}', - }, - allFilesFromFolderNamed: 'All files from folder %{name}', - openFolderNamed: 'Open folder %{name}', - cancel: 'Cancel', - logOut: 'Log out', - filter: 'Filter', - resetFilter: 'Reset filter', - loading: 'Loading...', - authenticateWithTitle: - 'Please authenticate with %{pluginName} to select files', - authenticateWith: 'Connect to %{pluginName}', - signInWithGoogle: 'Sign in with Google', - searchImages: 'Search for images', - enterTextToSearch: 'Enter text to search for images', - search: 'Search', - emptyFolderAdded: 'No files were added from empty folder', - folderAlreadyAdded: 'The folder "%{folder}" was already added', - folderAdded: { - 0: 'Added %{smart_count} file from %{folder}', - 1: 'Added %{smart_count} files from %{folder}', - }, - }, -}; -``` - -#### `store` - -The store that is used to keep track of internal state (`Object`, default: -[`DefaultStore`](/docs/guides/custom-stores)). - -This option can be used to plug Uppy state into an external state management -library, such as [Redux](/docs/guides/custom-stores). - -{/* TODO document store API */} - -#### `infoTimeout` - -How long an [Informer](/docs/informer) notification will be visible (`number`, -default: `5000`). - -### Methods - -#### `use(plugin, opts)` - -Add a plugin to Uppy, with an optional plugin options object. - -```js -import Uppy from '@uppy/core'; -import DragDrop from '@uppy/drag-drop'; - -const uppy = new Uppy(); -uppy.use(DragDrop, { target: 'body' }); -``` - -#### `removePlugin(instance)` - -Uninstall and remove a plugin. - -#### `getPlugin(id)` - -Get a plugin by its `id` to access its methods. - -#### `getID()` - -Get the Uppy instance ID, see the [`id`](#id) option. - -#### `addFile(file)` - -Add a new file to Uppy’s internal state. `addFile` will return the generated id -for the file that was added. - -`addFile` gives an error if the file cannot be added, either because -`onBeforeFileAdded(file)` gave an error, or because `uppy.opts.restrictions` -checks failed. - -```js -uppy.addFile({ - name: 'my-file.jpg', // file name - type: 'image/jpeg', // file type - data: blob, // file blob - meta: { - // optional, store the directory path of a file so Uppy can tell identical files in different directories apart. - relativePath: webkitFileSystemEntry.relativePath, - }, - source: 'Local', // optional, determines the source of the file, for example, Instagram. - isRemote: false, // optional, set to true if actual file is not in the browser, but on some remote server, for example, - // when using companion in combination with Instagram. -}); -``` - -:::note - -If you try to add a file that already exists, `addFile` will throw an error. -Unless that duplicate file was dropped with a folder — duplicate files from -different folders are allowed, when selected with that folder. This is because -we add `file.meta.relativePath` to the `file.id`. - -::: - -:::info - -Checkout [working with Uppy files](#working-with-uppy-files). - -::: - -:::info - -If `uppy.opts.autoProceed === true`, Uppy will begin uploading automatically -when files are added. - -::: - -:::info - -Sometimes you might need to add a remote file to Uppy. This can be achieved by -[fetching the file, then creating a Blob object, or using the Url plugin with Companion](https://github.com/transloadit/uppy/issues/1006#issuecomment-413495493). - -::: - -:::info - -Sometimes you might need to mark some files as “already uploaded”, so that the -user sees them, but they won’t actually be uploaded by Uppy. This can be -achieved by -[looping through files and setting `uploadComplete: true, uploadStarted: true` on them](https://github.com/transloadit/uppy/issues/1112#issuecomment-432339569) - -::: - -#### `removeFile(fileID)` - -Remove a file from Uppy. Removing a file that is already being uploaded cancels -that upload. - -```js -uppy.removeFile('uppyteamkongjpg1501851828779'); -``` - -#### `clear()` - -Clear the state. Can be useful for manually resetting Uppy after a successful -upload. Note that this method might throw an error if you try to call it while -an upload is ongoing. - -Upload plugins may choose to throw an error if called during an upload. - -#### `getFile(fileID)` - -Get a specific [Uppy file](#working-with-uppy-files) by its ID. - -```js -const file = uppy.getFile('uppyteamkongjpg1501851828779'); -``` - -#### `getFiles()` - -Get an array of all added [Uppy files](#working-with-uppy-files). - -```js -const files = uppy.getFiles(); -``` - -#### `upload()` - -Start uploading added files. - -Returns a Promise `result` that resolves with an object containing two arrays of -uploaded files: - -- `result.successful` - Files that were uploaded successfully. -- `result.failed` - Files that did not upload successfully. These files will - have a `.error` property describing what went wrong. - -```js -uppy.upload().then((result) => { - console.info('Successful uploads:', result.successful); - - if (result.failed.length > 0) { - console.error('Errors:'); - result.failed.forEach((file) => { - console.error(file.error); - }); - } -}); -``` - -#### `pauseResume(fileID)` - -Toggle pause/resume on an upload. Will only work if resumable upload plugin, -such as [Tus](/docs/tus/), is used. - -#### `pauseAll()` - -Pause all uploads. Will only work if a resumable upload plugin, such as -[Tus](/docs/tus/), is used. - -#### `resumeAll()` - -Resume all uploads. Will only work if resumable upload plugin, such as -[Tus](/docs/tus/), is used. - -#### `retryUpload(fileID)` - -Retry an upload (after an error, for example). - -#### `retryAll()` - -Retry all uploads (after an error, for example). - -#### `cancelAll()` - -Cancel all uploads, reset progress and remove all files. If you are using the -Transloadit plugin, this will also cancel all running assemblies, even after an -upload has finished. - -#### `setState(patch)` - -Update Uppy’s internal state. Usually, this method is called internally, but in -some cases it might be useful to alter something directly, especially when -implementing your own plugins. - -Uppy’s default state on initialization: - -```js -const state = { - plugins: {}, - files: {}, - currentUploads: {}, - capabilities: { - resumableUploads: false, - }, - totalProgress: 0, - meta: { ...this.opts.meta }, - info: { - isHidden: true, - type: 'info', - message: '', - }, -}; -``` - -Updating state: - -```js -uppy.setState({ smth: true }); -``` - -:::note - -State in Uppy is considered to be immutable. When updating values, make sure not -mutate them, but instead create copies. See -[Redux docs](http://redux.js.org/docs/recipes/UsingObjectSpreadOperator.html) -for more info on this. - -::: - -#### `getState()` - -Returns the current state from the [Store](#store). - -#### `setFileState(fileID, state)` - -Update the state for a single file. This is mostly useful for plugins that may -want to store data on [Uppy files](#working-with-uppy-files), or need to pass -file-specific configurations to other plugins that support it. - -`fileID` is the string file ID. `state` is an object that will be merged into -the file’s state object. - -#### `setMeta(data)` - -Alters global `meta` object in state, the one that can be set in Uppy options -and gets merged with all newly added files. Calling `setMeta` will also merge -newly added meta data with files that had been selected before. - -```js -uppy.setMeta({ resize: 1500, token: 'ab5kjfg' }); -``` - -#### `setFileMeta(fileID, data)` - -Update metadata for a specific file. - -```js -uppy.setFileMeta('myfileID', { resize: 1500 }); -``` - -#### `setOptions(opts)` - -Change the options Uppy initialized with. - -```js -const uppy = new Uppy(); - -uppy.setOptions({ - restrictions: { maxNumberOfFiles: 3 }, - autoProceed: true, -}); - -uppy.setOptions({ - locale: { - strings: { - cancel: 'Отмена', - }, - }, -}); -``` - -You can also change options for plugin: - -```js -// Change width of the Dashboard drag-and-drop aread on the fly -uppy.getPlugin('Dashboard').setOptions({ - width: 300, -}); -``` - -#### `destroy()` - -Uninstall all plugins and close down this Uppy instance. Also runs -`uppy.cancelAll()` before uninstalling. Note that this method should not -normally be used. If you only want reset the Uppy instance so that you can start -a new upload, you probably want to use `clear()` method instead. - -#### `logout()` - -Calls `provider.logout()` on each remote provider plugin (Google Drive, -Instagram, etc). Useful, for example, after your users log out of their account -in your app — this will clean things up with Uppy cloud providers as well, for -extra security. - -#### `log(message, type)` - -| Argument | Type | Description | -| --------- | --------- | --------------------------- | -| `message` | `string` | message to log | -| `type` | `string?` | `debug`, `warn`, or `error` | - -See [`logger`](#logger) docs for details. - -```js -uppy.log('[Dashboard] adding files...'); -``` - -#### `info(message, type, duration)` - -Sets a message in state, with optional details, that can be shown by -notification UI plugins. It’s using the [Informer](/docs/informer) plugin, -included by default in Dashboard. - -| Argument | Type | Description | -| ---------- | ------------------ | --------------------------------------------------------------------------------- | -| `message` | `string`, `Object` | `'info message'` or `{ message: 'Oh no!', details: 'File couldn’t be uploaded' }` | -| `type` | `string?` | `'info'`, `'warning'`, `'success'` or `'error'` | -| `duration` | `number?` | in milliseconds | - -`info-visible` and `info-hidden` events are emitted when this info message -should be visible or hidden. - -```js -this.info('Oh my, something good happened!', 'success', 3000); -``` - -```js -this.info( - { - message: 'Oh no, something bad happened!', - details: - 'File couldn’t be uploaded because there is no internet connection', - }, - 'error', - 5000, -); -``` - -#### `addPreProcessor(fn)` - -Add a preprocessing function. `fn` gets called with a list of file IDs before an -upload starts. `fn` should return a Promise. Its resolution value is ignored. - -:::info - -To change file data and such, use Uppy state updates, for example using -[`setFileState`](#setfilestatefileid-state). - -::: - -#### `addUploader(fn)` - -Add an uploader function. `fn` gets called with a list of file IDs when an -upload should start. Uploader functions should do the actual uploading work, -such as creating and sending an XMLHttpRequest or calling into some upload -service SDK. `fn` should return a Promise that resolves once all files have been -uploaded. - -:::tip - -You may choose to still resolve the Promise if some file uploads fail. This way, -any postprocessing will still run on the files that were uploaded successfully, -while uploads that failed will be retried when [`retryAll`](#retryall) is -called. - -::: - -#### `addPostProcessor(fn)` - -Add a postprocessing function. `fn` is called with a list of file IDs when an -upload has finished. `fn` should return a Promise that resolves when the -processing work is complete. The value of the Promise is ignored. - -For example, you could wait for file encoding or CDN propagation to complete, or -you could do an HTTP API call to create an album containing all images that were -uploaded. - -#### `removePreProcessor/removeUploader/removePostProcessor(fn)` - -Remove a processor or uploader function that was added before. Normally, this -should be done in the [`uninstall()`](#uninstall) method. - -#### `on('event', action)` - -Subscribe to an uppy-event. See below for the full list of events. - -#### `once('event', action)` - -Create an event listener that fires once. See below for the full list of events. - -#### `off('event', action)` - -Unsubscribe to an uppy-event. See below for the full list of events. - -### Events - -Uppy exposes events that you can subscribe to for side-effects. - -#### `file-added` - -Fired each time a file is added. - -**Parameters** - -- `file` - The [Uppy file](#working-with-uppy-files) that was added. - -```js -uppy.on('file-added', (file) => { - console.log('Added file', file); -}); -``` - -#### `files-added` - -**Parameters** - -- `files` - Array of [Uppy files](#working-with-uppy-files) which were added at - once, in a batch. - -Fired each time when one or more files are added — one event, for all files - -#### `file-removed` - -Fired each time a file is removed. - -**Parameters** - -- `file` - The [Uppy file](#working-with-uppy-files) that was removed. - -**Example** - -```js -uppy.on('file-removed', (file) => { - console.log('Removed file', file); -}); -``` - -```js -uppy.on('file-removed', (file) => { - removeFileFromUploadingCounterUI(file); - sendDeleteRequestForFile(file); -}); -``` - -#### `upload` - -Fired when the upload starts. - -**Parameters** - -- `uploadID` (`string)` -- `files` (`UppyFile`) - -#### `preprocess-progress` - -Progress of the pre-processors. - -**Parameters** - -`progress` is an object with properties: - -- `mode` - Either `'determinate'` or `'indeterminate'`. -- `message` - A message to show to the user. Something like - `'Preparing upload...'`, but be more specific if possible. - -When `mode` is `'determinate'`, also add the `value` property: - -- `value` - A progress value between 0 and 1. - -#### `progress` - -Fired each time the total upload progress is updated: - -**Parameters** - -- `progress` - An integer (0-100) representing the total upload progress. - -**Example** - -```js -uppy.on('progress', (progress) => { - // progress: integer (total progress percentage) - console.log(progress); -}); -``` - -#### `upload-progress` - -Fired each time an individual file upload progress is available: - -**Parameters** - -- `file` - The [Uppy file](#working-with-uppy-files) that has progressed. -- `progress` - The same object as in `file.progress`. - -**Example** - -```js -uppy.on('upload-progress', (file, progress) => { - // file: { id, name, type, ... } - // progress: { uploader, bytesUploaded, bytesTotal } - console.log(file.id, progress.bytesUploaded, progress.bytesTotal); -}); -``` - -#### `upload-pause` - -Fired when an individual upload is (un)paused. - -**Parameters** - -- `file` (`UppyFile`) -- `isPaused` (`boolean`) - -#### `postprocess-progress` - -Progress of the post-processors. - -**Parameters** - -`progress` is an object with properties: - -- `mode` - Either `'determinate'` or `'indeterminate'`. -- `message` - A message to show to the user. Something like - `'Preparing upload...'`, but be more specific if possible. - -When `mode` is `'determinate'`, also add the `value` property: - -- `value` - A progress value between 0 and 1. - -#### `upload-success` - -Fired each time a single upload is completed. - -**Parameters** - -- `file` - The [Uppy file](#working-with-uppy-files) that was uploaded. -- `response` - An object with response data from the remote endpoint. The actual - contents depend on the upload plugin that is used. - -For `@uppy/xhr-upload`, the shape is: - -```json -{ - "status": 200, // HTTP status code (0, 200, 300) - "body": "…", // response body - "uploadURL": "…" // the file url, if it was returned -} -``` - -**Example** - -```js -uppy.on('upload-success', (file, response) => { - console.log(file.name, response.uploadURL); - const img = new Image(); - img.width = 300; - img.alt = file.id; - img.src = response.uploadURL; - document.body.appendChild(img); -}); -``` - -#### `complete` - -Fired when all uploads are complete. - -The `result` parameter is an object with arrays of `successful` and `failed` -files, as in [`uppy.upload()`](#upload)’s return value. - -```js -uppy.on('complete', (result) => { - console.log('successful files:', result.successful); - console.log('failed files:', result.failed); -}); -``` - -#### `error` - -Fired when Uppy fails to upload/encode the entire upload. - -**Parameters** - -- `error` - The error object. - -**Example** - -```js -uppy.on('error', (error) => { - console.error(error.stack); -}); -``` - -#### `upload-error` - -Fired each time a single upload failed. - -**Parameters** - -- `file` - The [Uppy file](#working-with-uppy-files) which didn’t upload. -- `error` - The error object. -- `response` - an optional parameter with response data from the upload - endpoint. - -It may be undefined or contain different data depending on the upload plugin in -use. - -For `@uppy/xhr-upload`, the shape is: - -```json -{ - "status": 200, // HTTP status code (0, 200, 300) - "body": "…" // response body -} -``` - -**Example** - -```js -uppy.on('upload-error', (file, error, response) => { - console.log('error with file:', file.id); - console.log('error message:', error); -}); -``` - -If the error is related to network conditions — endpoint unreachable due to -firewall or ISP blockage, for instance — the error will have -`error.isNetworkError` property set to `true`. Here’s how you can check for -network errors: - -```js -uppy.on('upload-error', (file, error, response) => { - if (error.isNetworkError) { - // Let your users know that file upload could have failed - // due to firewall or ISP issues - alertUserAboutPossibleFirewallOrISPIssues(error); - } -}); -``` - -#### `upload-retry` - -Fired when an upload has been retried (after an error, for example). - -:::note - -This event is not triggered when the user retries all uploads, it will trigger -the `retry-all` event instead. - -::: - -**Parameters** - -- `file` (`UppyFile`) - -**Example** - -```js -uppy.on('upload-retry', (fileID) => { - console.log('upload retried:', fileID); -}); -``` - -#### `upload-stalled` - -Fired when an upload has not received any progress in some time (in -`@uppy/xhr-upload`, the delay is defined by the `timeout` option). Use this -event to display a message on the UI to tell the user they might want to retry -the upload. - -```js -uppy.on('upload-stalled', (error, files) => { - console.log('upload seems stalled', error, files); - const noLongerStalledEventHandler = (file) => { - if (files.includes(file)) { - console.log('upload is no longer stalled'); - uppy.off('upload-progress', noLongerStalledEventHandler); - } - }; - uppy.on('upload-progress', noLongerStalledEventHandler); -}); -``` - -#### `retry-all` - -Fired when all failed uploads are retried - -**Parameters** - -- `files` (`UppyFile[]`) - -**Example** - -```js -uppy.on('retry-all', (fileIDs) => { - console.log('upload retried:', fileIDs); -}); -``` - -#### `info-visible` - -Fired when “info” message should be visible in the UI. By default, `Informer` -plugin is displaying these messages (enabled by default in `Dashboard` plugin). -You can use this event to show messages in your custom UI: - -```js -uppy.on('info-visible', () => { - const { info } = uppy.getState(); - // info: { - // isHidden: false, - // type: 'error', - // message: 'Failed to upload', - // details: 'Error description' - // } - console.log(`${info.message} ${info.details}`); -}); -``` - -#### `info-hidden` - -Fired when “info” message should be hidden in the UI. See -[`info-visible`](#info-visible). - -#### `cancel-all` - -Fired when `cancelAll()` is called, all uploads are canceled, files removed and -progress is reset. - -#### `restriction-failed` - -Fired when a file violates certain restrictions when added. This event is -providing another choice for those who want to customize the behavior of file -upload restrictions. - -```js -uppy.on('restriction-failed', (file, error) => { - // do some customized logic like showing system notice to users -}); -``` - -## `new BasePlugin(uppy, options?)` - -The initial building block for a plugin. - -`BasePlugin` does not contain DOM rendering so it can be used for plugins -without an user interface. - -:::info - -See [`UIPlugin`][] for the extended version with Preact rendering for -interfaces. - -::: - -:::info - -Checkout the [building plugins](/docs/guides/building-plugins) guide. - -::: - -:::note - -If you don’t use any UI plugins, any modern bundler should be able to tree-shake -Preact code away. If you are not using a bundler that supports tree-shaking, -it’s also possible to import `BasePlugin` like this: -`import BasePlugin from '@uppy/core/lib/BasePlugin.js`. - -::: - -### Options - -The options passed to `BasePlugin` are all you options you wish to support in -your plugin. - -You should pass the options to `super` in your plugin class: - -```js -class MyPlugin extends BasePlugin { - constructor(uppy, opts) { - super(uppy, opts); - } -} -``` - -### Methods - -#### `setOptions(options)` - -Options passed during initialization can also be altered dynamically with -`setOptions`. - -#### `getPluginState()` - -Retrieves the plugin state from the `Uppy` class. Uppy keeps a `plugins` object -in state in which each key is the plugin’s `id`, and the value its state. - -#### `setPluginState()` - -Set the plugin state in the `Uppy` class. Uppy keeps a `plugins` object in state -in which each key is the plugin’s `id`, and the value its state. - -#### `install()` - -The `install` method is ran once, when the plugin is added to Uppy with -`.use()`. Use this to initialize the plugin. - -For example, if you are creating a pre-processor (such as -[@uppy/compressor](/docs/compressor)) you must add it: - -```js -install () { - this.uppy.addPreProcessor(this.prepareUpload) -} -``` - -Another common thing to do when creating a -[UI plugin](#new-uipluginuppy-options) is to [`mount`](#mounttarget) it to the -DOM: - -```js -install () { - const { target } = this.opts - if (target) { - this.mount(target, this) - } -} -``` - -#### `uninstall()` - -The `uninstall` method is ran once, when the plugin is removed from Uppy. This -happens when `.close()` is called or when the plugin is destroyed in a framework -integration. - -Use this to clean things up. - -For instance when creating a pre-processor, uploader, or post-processor to -remove it: - -```js -uninstall () { - this.uppy.removePreProcessor(this.prepareUpload) -} -``` - -When creating a [UI plugin](#new-uipluginuppy-options) you should -[`unmount`](#unmount) it from the DOM: - -```js -uninstall () { - this.unmount() -} -``` - -#### `i18nInit` - -Call `this.i18nInit()` once in the constructor of your plugin class to -initialize [internationalisation](/docs/locales). - -#### `addTarget` - -You can use this method to make your plugin a `target` for other plugins. This -is what `@uppy/dashboard` uses to add other plugins to its UI. - -#### `update` - -Called on each state update. You will rarely need to use this, unless if you -want to build a UI plugin using something other than Preact. - -#### `afterUpdate` - -Called after every state update with a debounce, after everything has mounted. - -## `new UIPlugin(uppy, options?)` - -`UIPlugin` extends [`BasePlugin`][] to add rendering with -[Preact](https://preactjs.com/). Use this when you want to create an user -interface or an addition to one, such as [Dashboard][]. - -:::info - -See [`BasePlugin`][] for the initial building block for all plugins. - -::: - -:::info - -Checkout the [building plugins](/docs/guides/building-plugins) guide. - -::: - -### Options - -The options passed to `UIPlugin` are all you options you wish to support in your -plugin. - -You should pass the options to `super` in your plugin class: - -```js -class MyPlugin extends UIPlugin { - constructor(uppy, opts) { - super(uppy, opts); - } -} -``` - -In turn these are also passed to the underlying `BasePlugin`. - -### Methods - -All the methods from [`BasePlugin`][] are also inherited into `UIPlugin`. - -#### `mount(target)` - -Mount this plugin to the `target` element. `target` can be a CSS query selector, -a DOM element, or another Plugin. If `target` is a Plugin, the source (current) -plugin will register with the target plugin, and the latter can decide how and -where to render the source plugin. - -#### `onMount()` - -Called after Preact has rendered the components of the plugin. - -#### `unmount` - -Removing the plugin from the DOM. You generally don’t need to override it but -you should call it from [`uninstall`](#uninstall). - -The default is: - -```js -unmount () { - if (this.isTargetDOMEl) { - this.el?.remove() - } - this.onUnmount() -} -``` - -#### `onUnmount()` - -Called after the elements have been removed from the DOM. Can be used to do some -clean up or other side-effects. - -#### `render()` - -Render the UI of the plugin. Uppy uses [Preact](https://preactjs.com) as its -view engine, so `render()` should return a Preact element. `render` is -automatically called by Uppy on each state change. - -#### `update(state)` - -Called on each state update. You will rarely need to use this, unless if you -want to build a UI plugin using something other than Preact. - -## `debugLogger()` - -Logger with extra debug and warning logs for during development. - -```js -import { Uppy, debugLogger } from '@uppy/core'; - -new Uppy({ logger: debugLogger }); -``` - -:::info - -You can also enable this logger by setting [`debug`](#debug) to `true`. - -::: - -The default value of [`logger`](#logger) is `justErrorsLogger`, which looks like -this: - -```js -// Swallow all logs, except errors. -// default if logger is not set or debug: false -const justErrorsLogger = { - debug: () => {}, - warn: () => {}, - error: (...args) => console.error(`[Uppy] [${getTimeStamp()}]`, ...args), -}; -``` - -`debugLogger` sends extra debugging and warning logs which could be helpful -during development: - -```js -// Print logs to console with namespace + timestamp, -// set by logger: Uppy.debugLogger or debug: true -const debugLogger = { - debug: (...args) => console.debug(`[Uppy] [${getTimeStamp()}]`, ...args), - warn: (...args) => console.warn(`[Uppy] [${getTimeStamp()}]`, ...args), - error: (...args) => console.error(`[Uppy] [${getTimeStamp()}]`, ...args), -}; -``` - -## Frequently asked questions - -### How do I allow duplicate files? - -You can allow all files, even duplicate files, with -[`onBeforeFileAdded`](#onbeforefileadded). This will override the file if it has -not been uploaded. If you upload a duplicate file again it depends on your -upload plugin and backend how it is handled. - -```js -const uppy = new Uppy({ - // ... - onBeforeFileAdded: () => true, -``` - -[dashboard]: /docs/dashboard -[`baseplugin`]: #new-basepluginuppy-options -[`uiplugin`]: #new-uipluginuppy-options diff --git a/docs/user-interfaces/_category_.json b/docs/user-interfaces/_category_.json deleted file mode 100644 index 7ffa7b4ce..000000000 --- a/docs/user-interfaces/_category_.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "label": "User interfaces", - "position": 5 -} diff --git a/docs/user-interfaces/dashboard.mdx b/docs/user-interfaces/dashboard.mdx deleted file mode 100644 index 340efbd4e..000000000 --- a/docs/user-interfaces/dashboard.mdx +++ /dev/null @@ -1,755 +0,0 @@ ---- -sidebar_position: 1 -slug: /dashboard ---- - -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; - -import UppyCdnExample from '/src/components/UppyCdnExample'; - -# Dashboard - -The all you need Dashboard — powerful, responsive, and pluggable. Kickstart your -uploading experience and gradually add more functionality. Add files from -[remote sources](/docs/companion), [edit images](/docs/image-editor), -[generate thumbnails](/docs/thumbnail-generator), and more. - -Checkout [integrations](#integrations) for the full list of plugins you can -integrate. - -:::tip - -[Try out the live example with all plugins](/examples) or take it for a spin in -[StackBlitz](https://stackblitz.com/edit/vitejs-vite-zaqyaf?file=main.js). - -::: - -## When should I use this? - -There could be many reasons why you may want to use the Dashboard, but some -could be: - -- when you need a battle tested plug-and-play uploading UI to save time. -- when your users need to add files from [remote sources](/docs/companion), such - [Google Drive](/docs/google-drive), [Dropbox](/docs/dropbox), and others. -- when you need to collect [meta data](#metafields) from your users per file. -- when your users want to take a picture with their [webcam](/docs/webcam) or - [capture their screen](/docs/screen-capture). - -## Install - - - - -```shell -npm install @uppy/core @uppy/dashboard -``` - - - - - -```shell -yarn add @uppy/core @uppy/dashboard -``` - - - - - - {` - import { Uppy, Dashboard } from "{{UPPY_JS_URL}}" - const uppy = new Uppy() - uppy.use(Dashboard, { target: '#uppy', inline: true }) - `} - - - - -## Use - -```js showLineNumbers -import Uppy from '@uppy/core'; -import Dashboard from '@uppy/dashboard'; - -import '@uppy/core/dist/style.min.css'; -import '@uppy/dashboard/dist/style.min.css'; - -new Uppy().use(Dashboard, { inline: true, target: '#uppy-dashboard' }); -``` - -:::note - -The `@uppy/dashboard` plugin includes CSS for the Dashboard itself, and the -various plugins used by the Dashboard, such as -([`@uppy/status-bar`](/docs/status-bar) and [`@uppy/informer`](/docs/informer)). -If you also use the `@uppy/status-bar` or `@uppy/informer` plugin directly, you -should not include their CSS files, but instead only use the one from the -`@uppy/dashboard` plugin. - -::: - -:::note - -Styles for Provider plugins, like Google Drive and Instagram, are also bundled -with Dashboard styles. Styles for other plugins, such as `@uppy/url` and -`@uppy/webcam`, are not included. If you are using those, please see their docs -and make sure to include styles for them as well. - -::: - -## API - -### Options - -#### `id` - -A unique identifier for this plugin (`string`, default: `'Dashboard'`). - -Plugins that are added by the Dashboard get unique IDs based on this ID, like -`'Dashboard:StatusBar'` and `'Dashboard:Informer'`. - -#### `target` - -Where to render the Dashboard (`string` or `Element`, default: `'body'`). - -You can pass an element, class, or id as a string. Dashboard is rendered into -`body`, because it’s hidden by default and only opened as a modal when `trigger` -is clicked. - -#### `inline` - -Render the Dashboard as a modal or inline (`boolean`, default: `false`). - -When `false`, Dashboard is opened by clicking on [`trigger`](#trigger). If -`inline: true`, Dashboard will be rendered into [`target`](#target) and fit -right in. - -#### `trigger` - -A CSS selector for a button that will trigger opening the Dashboard modal -(`string`, default: `null`). - -Several buttons or links can be used, as long as they are selected using the -same selector (`.select-file-button`, for example). - -#### `width` - -Width of the Dashboard in pixels (`number`, default: `750`). Used when -`inline: true`. - -#### `height` - -Height of the Dashboard in pixels (`number`, default: `550`). Used when -`inline: true`. - -#### `waitForThumbnailsBeforeUpload` - -Whether to wait for all thumbnails from `@uppy/thumbnail-generator` to be ready -before starting the upload (`boolean`, default `false`). - -If set to `true`, Thumbnail Generator will envoke Uppy’s internal processing -stage, displaying “Generating thumbnails...” message, and wait for -`thumbnail:all-generated` event, before proceeding to the uploading stage. - -This is useful because Thumbnail Generator also adds EXIF data to images, and if -we wait until it’s done processing, this data will be available on the server -after the upload. - -#### `showLinkToFileUploadResult` - -Turn the file icon and thumbnail in the Dashboard into a link to the uploaded -file (`boolean`, default: `false`). - -Please make sure to return the `url` key (or the one set via -`responseUrlFieldName`) from your server. - -#### `showProgressDetails` - -Show or hide progress details in the status bar (`boolean`, default: `false`). - -By default, progress in Status Bar is shown as a percentage. If you would like -to also display remaining upload size and time, set this to `true`. - -`showProgressDetails: false`: Uploading: 45% - -`showProgressDetails: true`: Uploading: 45%・43 MB of 101 MB・8s left - -#### `hideUploadButton` - -Show or hide the upload button (`boolean`, default: `false`). - -Use this if you are providing a custom upload button somewhere, and are using -the `uppy.upload()` API. - -#### `hideRetryButton` - -Hide the retry button in the status bar and on each individual file (`boolean`, -default: `false`). - -Use this if you are providing a custom retry button somewhere and if you are -using the `uppy.retryAll()` or `uppy.retryUpload(fileID)` API. - -#### `hidePauseResumeButton` - -Hide the pause/resume button (for resumable uploads, via [tus](http://tus.io), -for example) in the status bar and on each individual file (`boolean`, default: -`false`). - -Use this if you are providing custom cancel or pause/resume buttons somewhere, -and using the `uppy.pauseResume(fileID)` or `uppy.removeFile(fileID)` API. - -#### `hideCancelButton` - -Hide the cancel button in status bar and on each individual file (`boolean`, -default: `false`). - -Use this if you are providing a custom retry button somewhere, and using the -`uppy.cancelAll()` API. - -#### `hideProgressAfterFinish` - -Hide the status bar after the upload has finished (`boolean`, default: `false`). - -#### `doneButtonHandler` - -This option is passed to the status bar and will render a “Done” button in place -of pause/resume/cancel buttons, once the upload/encoding is done. The behaviour -of this “Done” button is defined by this handler function, for instance to close -the file picker modals or clear the upload state. - -This is what the Dashboard sets by default: - -```js -const doneButtonHandler = () => { - this.uppy.cancelAll(); - this.requestCloseModal(); -}; -``` - -Set to `null` to disable the “Done” button. - -#### `showSelectedFiles` - -Show the list of added files with a preview and file information (`boolean`, -default: `true`). - -In case you are showing selected files in your own app’s UI and want the Uppy -Dashboard to only be a picker, the list can be hidden with this option. - -See also [`disableStatusBar`](#disablestatusbar) option, which can hide the -progress and upload button. - -#### `showRemoveButtonAfterComplete` - -Show the remove button on every file after a successful upload (`boolean`, -default: `false`). - -Enabling this option only shows the remove `X` button in the Dashboard UI, but -to actually send a request you should listen to -[`file-removed`](https://uppy.io/docs/uppy/#file-removed) event and add your -logic there. - -Example: - -```js -uppy.on('file-removed', (file, reason) => { - if (reason === 'removed-by-user') { - sendDeleteRequestForFile(file); - } -}); -``` - -For an implementation example, please see -[#2301](https://github.com/transloadit/uppy/issues/2301#issue-628931176). - -#### `singleFileFullScreen` - -When only one file is selected, its preview and meta information will be -centered and enlarged (`boolean`, default: `true`). - -Often times Uppy used for photo / profile image uploads, or maybe a single -document. Then it makes sense to occupy the whole space of the available -Dashboard UI, giving the stage to this one file. This feature is automatically -disabled when Dashboard is small in height, since there’s not enough room. - -#### `note` - -A string of text to be placed in the Dashboard UI (`string`, default: `null`). - -This could for instance be used to explain any [`restrictions`](#restrictions) -that are put in place. For example: -`'Images and video only, 2–3 files, up to 1 MB'`. - -#### `metaFields` - -Create text or custom input fields for the user to fill in (`Array` or -`Function`, default: `null`). - -This will be shown when a user clicks the “edit” button on that file. - -:::note - -The meta data will only be set on a file object if it’s entered by the user. If -the user doesn’t edit a file’s metadata, it will not have default values; -instead everything will be `undefined`. If you want to set a certain meta field -to each file regardless of user actions, set -[`meta` in the Uppy constructor options](/docs/uppy/#meta). - -::: - -Each object can contain: - -- `id`. The name of the meta field. This will also be used in CSS/HTML as part - of the `id` attribute, so it’s better to - [avoid using characters like periods, semicolons, etc](https://stackoverflow.com/a/79022). -- `name`. The label shown in the interface. -- `placeholder`. The text shown when no value is set in the field. (Not needed - when a custom render function is provided) -- `render: ({value, onChange, required, form}, h) => void` (optional). A - function for rendering a custom form element. - - `value` is the current value of the meta field - - `onChange: (newVal) => void` is a function saving the new value and `h` is - the `createElement` function from - [Preact](https://preactjs.com/guide/v10/api-reference#h--createelement). - - `required` is a boolean that’s true if the field `id` is in the - `restrictedMetaFields` restriction - - `form` is the `id` of the associated `` element. - - `h` can be useful when using Uppy from plain JavaScript, where you cannot - write JSX. - -
-Example: meta fields configured as an `Array` - -```js -uppy.use(Dashboard, { - trigger: '#pick-files', - metaFields: [ - { id: 'name', name: 'Name', placeholder: 'file name' }, - { id: 'license', name: 'License', placeholder: 'specify license' }, - { - id: 'caption', - name: 'Caption', - placeholder: 'describe what the image is about', - }, - { - id: 'public', - name: 'Public', - render({ value, onChange, required, form }, h) { - return h('input', { - type: 'checkbox', - required, - form, - onChange: (ev) => onChange(ev.target.checked ? 'on' : ''), - defaultChecked: value === 'on', - }); - }, - }, - ], -}); -``` - -
- -
-Example: dynamic meta fields based on file type with a `Function` - -```js -uppy.use(Dashboard, { - trigger: '#pick-files', - metaFields: (file) => { - const fields = [{ id: 'name', name: 'File name' }]; - if (file.type.startsWith('image/')) { - fields.push({ id: 'location', name: 'Photo Location' }); - fields.push({ id: 'alt', name: 'Alt text' }); - fields.push({ - id: 'public', - name: 'Public', - render: ({ value, onChange, required, form }, h) => { - return h('input', { - type: 'checkbox', - onChange: (ev) => onChange(ev.target.checked ? 'on' : ''), - defaultChecked: value === 'on', - required, - form, - }); - }, - }); - } - return fields; - }, -}); -``` - -
- -#### `closeModalOnClickOutside` - -Set to true to automatically close the modal when the user clicks outside of it -(`boolean`, default: `false`). - -#### `closeAfterFinish` - -Set to true to automatically close the modal when all current uploads are -complete (`boolean`, default: `false`). - -With this option, the modal is only automatically closed when uploads are -complete _and successful_. If some uploads failed, the modal stays open so the -user can retry failed uploads or cancel the current batch and upload an entirely -different set of files instead. - -:::info - -You can use this together with the -[`allowMultipleUploads: false`](/docs/uppy/#allowmultipleuploads) option in Uppy -Core to create a smooth experience when uploading a single (batch of) file(s). - -This is recommended. With several upload batches, the auto-closing behavior can -be quite confusing for users. - -::: - -#### `disablePageScrollWhenModalOpen` - -Disable page scroll when the modal is open (`boolean`, default: `true`). - -Page scrolling is disabled by default when the Dashboard modal is open, so when -you scroll a list of files in Uppy, the website in the background stays still. -Set to false to override this behaviour and leave page scrolling intact. - -#### `animateOpenClose` - -Add animations when the modal dialog is opened or closed, for a more satisfying -user experience (`boolean`, default: `true`). - -#### `fileManagerSelectionType` - -Configure the type of selections allowed when browsing your file system via the -file manager selection window (`string`, default: `'files'`). - -May be either `'files'`, `'folders'`, or `'both'`. Selecting entire folders for -upload may not be supported on all -[browsers](https://caniuse.com/#feat=input-file-directory). - -#### `proudlyDisplayPoweredByUppy` - -Show the Uppy logo with a link (`boolean`, default: `true`). - -Uppy is provided to the world for free by the team behind -[Transloadit](https://transloadit.com). In return, we ask that you consider -keeping a tiny Uppy logo at the bottom of the Dashboard, so that more people can -discover and use Uppy. - -#### `disableStatusBar` - -Disable the status bar completely (`boolean`, default: `false`). - -Dashboard ships with the `StatusBar` plugin that shows upload progress and -pause/resume/cancel buttons. If you want, you can disable the StatusBar to -provide your own custom solution. - -#### `disableInformer` - -Disable informer (shows notifications in the form of toasts) completely -(`boolean`, default: `false`). - -Dashboard ships with the `Informer` plugin that notifies when the browser is -offline, or when it’s time to say cheese if `Webcam` is taking a picture. If you -want, you can disable the Informer and/or provide your own custom solution. - -#### `disableThumbnailGenerator` - -Disable the thumbnail generator completely (`boolean`, default: `false`). - -Dashboard ships with the `ThumbnailGenerator` plugin that adds small resized -image thumbnails to images, for preview purposes only. If you want, you can -disable the `ThumbnailGenerator` and/or provide your own custom solution. - -#### `locale` - -```js -module.exports = { - strings: { - // When `inline: false`, used as the screen reader label for the button that closes the modal. - closeModal: 'Close Modal', - // Used as the screen reader label for the plus (+) button that shows the “Add more files” screen - addMoreFiles: 'Add more files', - addingMoreFiles: 'Adding more files', - // Used as the header for import panels, e.g., “Import from Google Drive”. - importFrom: 'Import from %{name}', - // When `inline: false`, used as the screen reader label for the dashboard modal. - dashboardWindowTitle: 'Uppy Dashboard Window (Press escape to close)', - // When `inline: true`, used as the screen reader label for the dashboard area. - dashboardTitle: 'Uppy Dashboard', - // Shown in the Informer when a link to a file was copied to the clipboard. - copyLinkToClipboardSuccess: 'Link copied to clipboard.', - // Used when a link cannot be copied automatically — the user has to select the text from the - // input element below this string. - copyLinkToClipboardFallback: 'Copy the URL below', - // Used as the hover title and screen reader label for buttons that copy a file link. - copyLink: 'Copy link', - back: 'Back', - // Used as the screen reader label for buttons that remove a file. - removeFile: 'Remove file', - // Used as the screen reader label for buttons that open the metadata editor panel for a file. - editFile: 'Edit file', - // Shown in the panel header for the metadata editor. Rendered as “Editing image.png”. - editing: 'Editing %{file}', - // Used as the screen reader label for the button that saves metadata edits and returns to the - // file list view. - finishEditingFile: 'Finish editing file', - saveChanges: 'Save changes', - // Used as the label for the tab button that opens the system file selection dialog. - myDevice: 'My Device', - dropHint: 'Drop your files here', - // Used as the hover text and screen reader label for file progress indicators when - // they have been fully uploaded. - uploadComplete: 'Upload complete', - uploadPaused: 'Upload paused', - // Used as the hover text and screen reader label for the buttons to resume paused uploads. - resumeUpload: 'Resume upload', - // Used as the hover text and screen reader label for the buttons to pause uploads. - pauseUpload: 'Pause upload', - // Used as the hover text and screen reader label for the buttons to retry failed uploads. - retryUpload: 'Retry upload', - // Used as the hover text and screen reader label for the buttons to cancel uploads. - cancelUpload: 'Cancel upload', - // Used in a title, how many files are currently selected - xFilesSelected: { - 0: '%{smart_count} file selected', - 1: '%{smart_count} files selected', - }, - uploadingXFiles: { - 0: 'Uploading %{smart_count} file', - 1: 'Uploading %{smart_count} files', - }, - processingXFiles: { - 0: 'Processing %{smart_count} file', - 1: 'Processing %{smart_count} files', - }, - // The "powered by Uppy" link at the bottom of the Dashboard. - poweredBy: 'Powered by %{uppy}', - addMore: 'Add more', - editFileWithFilename: 'Edit file %{file}', - save: 'Save', - cancel: 'Cancel', - dropPasteFiles: 'Drop files here or %{browseFiles}', - dropPasteFolders: 'Drop files here or %{browseFolders}', - dropPasteBoth: 'Drop files here, %{browseFiles} or %{browseFolders}', - dropPasteImportFiles: 'Drop files here, %{browseFiles} or import from:', - dropPasteImportFolders: 'Drop files here, %{browseFolders} or import from:', - dropPasteImportBoth: - 'Drop files here, %{browseFiles}, %{browseFolders} or import from:', - importFiles: 'Import files from:', - browseFiles: 'browse files', - browseFolders: 'browse folders', - recoveredXFiles: { - 0: 'We could not fully recover 1 file. Please re-select it and resume the upload.', - 1: 'We could not fully recover %{smart_count} files. Please re-select them and resume the upload.', - }, - recoveredAllFiles: 'We restored all files. You can now resume the upload.', - sessionRestored: 'Session restored', - reSelect: 'Re-select', - missingRequiredMetaFields: { - 0: 'Missing required meta field: %{fields}.', - 1: 'Missing required meta fields: %{fields}.', - }, - }, -}; -``` - -#### `theme` - -Light or dark theme for the Dashboard (`string`, default: `'light'`). - -Uppy Dashboard supports “Dark Mode”. You can try it live on -[the Dashboard example page](https://uppy.io/examples/). - -It supports the following values: - -- `light` — the default -- `dark` -- `auto` — will respect the user’s system settings and switch automatically - -#### `autoOpen` - -Automatically open file editor for the file user just dropped/selected. -If one file is added, editor opens for that file; if 10 files are added, editor -opens only for the first file. - -This option supports the following values: - -- `null` - the default -- `"metaEditor"` - open the meta fields editor if - [meta fields](/docs/dashboard/#metafields) are enabled. -- `"imageEditor"` - open [`@uppy/image-editor`](/docs/image-editor) if the - plugin is enabled. - -#### `disabled` - -Enabling this option makes the Dashboard grayed-out and non-interactive -(`boolean`, default: `false`). - -Users won’t be able to click on buttons or drop files. Useful when you need to -conditionally enable/disable file uploading or manipulation, based on a -condition in your app. Can be set on init or via API: - -```js -const dashboard = uppy.getPlugin('Dashboard'); -dashboard.setOptions({ disabled: true }); - -userNameInput.addEventListener('change', () => { - dashboard.setOptions({ disabled: false }); -}); -``` - -#### `disableLocalFiles` - -Disable local files (`boolean`, default: `false`). - -Enabling this option will disable drag & drop, hide the “browse” and “My Device” -button, allowing only uploads from plugins, such as Webcam, Screen Capture, -Google Drive, Instagram. - -#### `onDragOver(event)` - -Callback for the [`ondragover`][ondragover] event handler. - -#### `onDrop(event)` - -Callback for the [`ondrop`][ondrop] event handler. - -#### `onDragLeave(event)` - -Callback for the [`ondragleave`][ondragleave] event handler. - -### Methods - -:::info - -Dashboard also has the methods described in -[`UIPlugin`](/docs/uppy#new-uipluginuppy-options) and -[`BasePlugin`](/docs/uppy#new-basepluginuppy-options). - -::: - -#### `openModal()` - -Shows the Dashboard modal. Use it like this: - -`uppy.getPlugin('Dashboard').openModal()` - -#### `closeModal()` - -Hides the Dashboard modal. Use it like this: - -`uppy.getPlugin('Dashboard').closeModal()` - -#### `isModalOpen()` - -Returns `true` if the Dashboard modal is open, `false` otherwise. - -```js -const dashboard = uppy.getPlugin('Dashboard'); -if (dashboard.isModalOpen()) { - dashboard.closeModal(); -} -``` - -### Events - -:::info - -You can use [`on`](/docs/uppy#onevent-action) and -[`once`](/docs/uppy#onceevent-action) to listen to these events. - -::: - -#### `dashboard:modal-open` - -Fired when the Dashboard modal is open. - -```js -uppy.on('dashboard:modal-open', () => { - console.log('Modal is open'); -}); -``` - -#### `dashboard:modal-closed` - -Fired when the Dashboard modal is closed. - -#### `dashboard:file-edit-start` - -**Parameters:** - -- `file` — The [File Object](https://uppy.io/docs/uppy/#File-Objects) - representing the file that was opened for editing. - -Fired when the user clicks “edit” icon next to a file in the Dashboard. The -FileCard panel is then open with file metadata available for editing. - -#### `dashboard:file-edit-complete` - -**Parameters:** - -- `file` — The [File Object](https://uppy.io/docs/uppy/#File-Objects) - representing the file that was edited. - -Fired when the user finished editing the file metadata. - -## Integrations - -These are the plugins specifically made for the Dashboard. This is not a list of -all Uppy plugins. - -### Sources - -- [`@uppy/audio`](/docs/audio) — record audio. -- [`@uppy/box`](/docs/box) — import files from - [Box](https://www.box.com/en-nl/home). -- [`@uppy/dropbox`](/docs/dropbox) — import from [Dropbox](https://dropbox.com). -- [`@uppy/facebook`](/docs/facebook) — import from - [Facebook](https://facebook.com). -- [`@uppy/google-drive`](/docs/google-drive) — import from - [Google Drive](https://drive.google.com). -- [`@uppy/google-photos`](/docs/google-photos) — import from - [Google Photos](https://photos.google.com). -- [`@uppy/instagram`](/docs/instagram) — import from - [Instagram](https://instagram.com). -- [`@uppy/onedrive`](/docs/onedrive) — import from - [OneDrive](https://www.microsoft.com/en-us/microsoft-365/onedrive/online-cloud-storage). -- [`@uppy/screen-capture`](/docs/screen-capture) — Record your screen, including - (optionally) your microphone. -- [`@uppy/unsplash`](/docs/unsplash) — import files from - [Unsplash](https://unsplash.com/) -- [`@uppy/url`](/docs/url) — import files from any URL. -- [`@uppy/webcam`](/docs/webcam) — Record or make a picture with your webcam. -- [`@uppy/zoom`](/docs/zoom) — import files from [Zoom](https://zoom.us). - -### UI - -- [`@uppy/image-editor`](/docs/image-editor) — allows users to crop, rotate, - zoom and flip images that are added to Uppy. -- [`@uppy/informer`](/docs/informer) — show notifications. -- [`@uppy/status-bar`](/docs/status-bar) — advanced upload progress status bar. -- [`@uppy/thumbnail-generator`](/docs/thumbnail-generator) — generate preview - thumbnails for images to be uploaded. - -### Frameworks - -- [`@uppy/angular`](/docs/angular) — Dashboard component for - [Angular](https://angular.io/). -- [`@uppy/react`](/docs/react) — Dashboard component for - [React](https://reactjs.org/). -- [`@uppy/svelte`](/docs/svelte) — Dashboard component for - [Svelte](https://svelte.dev/). -- [`@uppy/vue`](/docs/vue) — Dashboard component for [Vue](https://vuejs.org/). - -[ondragover]: - https://developer.mozilla.org/en-US/docs/Web/API/GlobalEventHandlers/ondragover -[ondragleave]: - https://developer.mozilla.org/en-US/docs/Web/API/GlobalEventHandlers/ondragleave -[ondrop]: - https://developer.mozilla.org/en-US/docs/Web/API/GlobalEventHandlers/ondrop diff --git a/docs/user-interfaces/drag-drop.mdx b/docs/user-interfaces/drag-drop.mdx deleted file mode 100644 index dbaba6923..000000000 --- a/docs/user-interfaces/drag-drop.mdx +++ /dev/null @@ -1,149 +0,0 @@ ---- -sidebar_position: 2 -slug: /drag-drop ---- - -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; - -import UppyCdnExample from '/src/components/UppyCdnExample'; - -# Drag & Drop - -The `@uppy/drag-drop` plugin renders a drag and drop area for file selection. - -:::tip - -[Try out the live example](/examples) or take it for a spin in -[StackBlitz](https://stackblitz.com/edit/vitejs-vite-yzbujq?file=main.js/g). - -::: - -## When should I use this? - -It can be useful when you only want the local device as a file source, don’t -need file previews and a UI for metadata editing, or the -[Dashboard](/docs/dashboard/) is too much. But it can be too minimal too. By -default it doesn’t show that a file has been added nor is there a progress bar. - -## Install - - - - -```shell -npm install @uppy/core @uppy/drag-drop -``` - - - - - -```shell -yarn add @uppy/core @uppy/drag-drop -``` - - - - - - {` - import { Uppy, DragDrop } from "{{UPPY_JS_URL}}" - const uppy = new Uppy() - uppy.use(DragDrop, { target: '#uppy' }) - `} - - - - -## Use - -```js showLineNumbers -import Uppy from '@uppy/core'; -import DragDrop from '@uppy/drag-drop'; - -import '@uppy/core/dist/style.min.css'; -import '@uppy/drag-drop/dist/style.min.css'; - -new Uppy().use(DragDrop, { target: '#drag-drop' }); -``` - -:::info - -Certain [restrictions](/docs/uppy#restrictions) set in Uppy’s options, namely -`maxNumberOfFiles` and `allowedFileTypes`, affect the system file picker dialog. -If `maxNumberOfFiles: 1`, users will only be able to select one file, and -`allowedFileTypes: ['video/*', '.gif']` means only videos or gifs (files with -`.gif` extension) will be selectable. - -::: - -## API - -### Options - -#### `id` - -A unique identifier for this plugin (`string`, Default: `'DragDrop'`). - -Use this if you need to add several DragDrop instances. - -#### `target` - -DOM element, CSS selector, or plugin to place the drag and drop area into -(`string` or `Element`, default: `null`). - -#### `width` - -Drag and drop area width (`string`, default: `'100%'`). - -Set in inline CSS, so feel free to use percentage, pixels or other values that -you like. - -#### `height` - -Drag and drop area height (`string`, default: `'100%'`). - -Set in inline CSS, so feel free to use percentage, pixels or other values that -you like. - -#### `note` - -Optionally, specify a string of text that explains something about the upload -for the user (`string`, default: `null`). - -This is a place to explain any `restrictions` that are put in place. For -example: `'Images and video only, 2–3 files, up to 1 MB'`. - -#### `locale` - -```js -export default { - strings: { - // Text to show on the droppable area. - // `%{browse}` is replaced with a link that opens the system file selection dialog. - dropHereOr: 'Drop here or %{browse}', - // Used as the label for the link that opens the system file selection dialog. - browse: 'browse', - }, -}; -``` - -#### `onDragOver(event)` - -Callback for the [`ondragover`][ondragover] event handler. - -#### `onDragLeave(event)` - -Callback for the [`ondragleave`][ondragleave] event handler. - -#### `onDrop(event)` - -Callback for the [`ondrop`][ondrop] event handler. - -[ondragover]: - https://developer.mozilla.org/en-US/docs/Web/API/GlobalEventHandlers/ondragover -[ondragleave]: - https://developer.mozilla.org/en-US/docs/Web/API/GlobalEventHandlers/ondragleave -[ondrop]: - https://developer.mozilla.org/en-US/docs/Web/API/GlobalEventHandlers/ondrop diff --git a/docs/user-interfaces/elements/_category_.json b/docs/user-interfaces/elements/_category_.json deleted file mode 100644 index 4250be147..000000000 --- a/docs/user-interfaces/elements/_category_.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "label": "Elements", - "position": 3, - "collapsed": false -} diff --git a/docs/user-interfaces/elements/drop-target.mdx b/docs/user-interfaces/elements/drop-target.mdx deleted file mode 100644 index 9a891eec4..000000000 --- a/docs/user-interfaces/elements/drop-target.mdx +++ /dev/null @@ -1,113 +0,0 @@ ---- -sidebar_position: 2 -slug: /drop-target ---- - -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; - -import UppyCdnExample from '/src/components/UppyCdnExample'; - -# Drop target - -The `@uppy/drop-target` plugin lets your users drag-and-drop files on any -element on the page, for example the whole page, `document.body`. - -Can be used together with Uppy Dashboard or Drag & Drop plugins, or your custom -solution targeting any DOM element. - -:::tip - -[Try out the live example](/examples) or take it for a spin in -[StackBlitz](https://stackblitz.com/edit/vitejs-vite-yzbujq?file=main.js/g). - -::: - -## When should I use this? - -When you want to allow users to drag and drop files in your own UI, rather than -in the [`Dashboard`](/docs/dashboard) UI, or catch dropped files from anywhere -on the page. - -## Install - - - - -```shell -npm install @uppy/drop-target -``` - - - - - -```shell -yarn add @uppy/drop-target -``` - - - - - - {` - import { DropTarget } from "{{UPPY_JS_URL}}" - const DropTarget = new Uppy().use(DropTarget) - `} - - - - -## Use - -This module has one default export: the `DropTarget` plugin class. - -```js {8-10} showLineNumbers -import Uppy from '@uppy/core'; -import DropTarget from '@uppy/drop-target'; - -import '@uppy/core/dist/style.css'; -import '@uppy/drop-target/dist/style.css'; - -const uppy = new Uppy(); -uppy.use(DropTarget, { - target: document.body, -}); -``` - -## API - -### Options - -#### `onDragLeave` - -Event listener for the [`dragleave` event][]. - -```js {3-5} showLineNumbers -uppy.use(DropTarget, { - target: document.body, - onDragLeave: (event) => { - event.stopPropagation(); - }, -}); -``` - -#### `onDragOver` - -Event listener for the [`dragover` event][]. - -#### `onDrop` - -Event listener for the [`drop` event][]. - -#### `target` - -DOM element, CSS selector, or plugin to place the drag and drop area into -(`string`, `Element`, `Function`, or `UIPlugin`, default: `null`). - -[`dragover` event]: - https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/dragover_event -[`dragleave` event]: - https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/dragleave_event -[`drop` event]: - https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/drop_event diff --git a/docs/user-interfaces/elements/image-editor.mdx b/docs/user-interfaces/elements/image-editor.mdx deleted file mode 100644 index 033dd635e..000000000 --- a/docs/user-interfaces/elements/image-editor.mdx +++ /dev/null @@ -1,179 +0,0 @@ ---- -sidebar_position: 1 -slug: /image-editor ---- - -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; - -import UppyCdnExample from '/src/components/UppyCdnExample'; - -# Image editor - -Image editor. Designed to be used with the Dashboard UI. - -
- -![Screenshot of the Image Editor plugin UI in Dashboard](https://user-images.githubusercontent.com/1199054/87208710-654db400-c307-11ea-9471-6e3c6582d2a5.png) - -
- -## When should I use this? - -When you want to allow users to crop, rotate, zoom and flip images that are -added to Uppy. - -## Install - - - - -```shell -npm install @uppy/core @uppy/dashboard @uppy/image-editor -``` - - - - - -```shell -yarn add @uppy/core @uppy/dashboard @uppy/image-editor -``` - - - - - - {` - import { Uppy, Dashboard, ImageEditor } from "{{UPPY_JS_URL}}" - const uppy = new Uppy() - uppy.use(Dashboard, { target: '#uppy', inline: true }) - uppy.use(ImageEditor) - `} - - - - -## Use - -```js {3,7,11} showLineNumbers -import Uppy from '@uppy/core'; -import Dashboard from '@uppy/dashboard'; -import ImageEditor from '@uppy/image-editor'; - -import '@uppy/core/dist/style.min.css'; -import '@uppy/dashboard/dist/style.min.css'; -import '@uppy/image-editor/dist/style.min.css'; - -new Uppy() - .use(Dashboard, { inline: true, target: '#dashboard' }) - .use(ImageEditor); -``` - -## API - -### Options - -:::info - -If you automatically want to open the image editor when an image is added, see -the [`autoOpen`](/docs/dashboard#autoopen) Dashboard option. - -::: - -#### `id` - -A unique identifier for this plugin (`string`, default: `'ImageEditor'`). - -#### `quality` - -Quality Of the resulting blob that will be saved in Uppy after editing/cropping -(`number`, default: `0.8`). - -#### `cropperOptions` - -Image Editor is using the excellent -[Cropper.js](https://fengyuanchen.github.io/cropperjs/). `cropperOptions` will -be directly passed to `Cropper` and thus can expect the same values as -documented in their -[README](https://github.com/fengyuanchen/cropperjs/blob/HEAD/README.md#options), -with the addition of `croppedCanvasOptions`, which will be passed to -[`getCroppedCanvas`](https://github.com/fengyuanchen/cropperjs/blob/HEAD/README.md#getcroppedcanvasoptions). - -#### `actions` - -Show action buttons (`Object` or `boolean`). - -If you you’d like to hide all actions, pass `false` to it. By default all the -actions are visible. Or enable/disable them individually: - -```js -{ - revert: true, - rotate: true, - granularRotate: true, - flip: true, - zoomIn: true, - zoomOut: true, - cropSquare: true, - cropWidescreen: true, - cropWidescreenVertical: true, -} -``` - -#### `locale: {}` - -```js -export default { - strings: { - revert: 'Revert', - rotate: 'Rotate', - zoomIn: 'Zoom in', - zoomOut: 'Zoom out', - flipHorizontal: 'Flip horizontal', - aspectRatioSquare: 'Crop square', - aspectRatioLandscape: 'Crop landscape (16:9)', - aspectRatioPortrait: 'Crop portrait (9:16)', - }, -}; -``` - -### Events - -:::info - -You can use [`on`](/docs/uppy#onevent-action) and -[`once`](/docs/uppy#onceevent-action) to listen to these events. - -::: - -#### `file-editor:start` - -Emitted when `selectFile(file)` is called. - -```js -uppy.on('file-editor:start', (file) => { - console.log(file); -}); -``` - -#### `file-editor:complete` - -Emitted after `save(blob)` is called. - -```js -uppy.on('file-editor:complete', (updatedFile) => { - console.log(updatedFile); -}); -``` - -#### `file-editor:cancel` - -Emitted when `uninstall` is called or when the current image editing changes are -discarded. - -```js -uppy.on('file-editor:cancel', (file) => { - console.log(file); -}); -``` diff --git a/docs/user-interfaces/elements/informer.mdx b/docs/user-interfaces/elements/informer.mdx deleted file mode 100644 index 99304ea91..000000000 --- a/docs/user-interfaces/elements/informer.mdx +++ /dev/null @@ -1,94 +0,0 @@ ---- -sidebar_position: 3 -slug: /informer ---- - -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; - -import UppyCdnExample from '/src/components/UppyCdnExample'; - -# Informer - -The `@uppy/informer` plugin is a pop-up bar for showing notifications for the -[Dashboard](/docs/dashboard). When plugins have some exciting news (or errors) -to share, they can with Informer - -## When should I use it? - -When you use the [Dashboard](/docs/dashboard) it’s already included by default. -This plugin is published separately but made specifically for the Dashboard. You -can technically use it without it, but it’s not officially supported. - -## Install - - - - -```shell -npm install @uppy/informer -``` - - - - - -```shell -yarn add @uppy/informer -``` - - - - - - {` - import { Uppy, Informer } from "{{UPPY_JS_URL}}" - const uppy = new Uppy() - uppy.use(Informer, { target: '#informer' }) - `} - - - - -## Use - -```js -import Uppy from '@uppy/core'; -import Informer from '@uppy/informer'; - -// The `@uppy/informer` plugin includes some basic styles. -// You can also choose not to use it and provide your own styles instead. -import '@uppy/core/dist/style.min.css'; -import '@uppy/informer/dist/style.min.css'; - -new Uppy().use(Informer, { target: '#informer' }); -``` - -Informer gets its data from `uppy.state.info`, which is updated by various -plugins via [`uppy.info`](/docs/uppy#infomessage-type-duration) method. - -In the [compressor](/docs/compressor) plugin we use it like this for instance: - -```js -const size = prettierBytes(totalCompressedSize); -this.uppy.info(this.i18n('compressedX', { size }), 'info'); -``` - -When calling `uppy.info`, [Uppy](/docs/uppy) emits -[`info-visible`](/docs/uppy#info-visible) and will emit -[`info-hidden`](/docs/uppy#info-hidden) after the timeout. - -## API - -### Options - -### `id` - -A unique identifier for this plugin (`string`, default: `'Informer'`). - -Use this if you need several `Informer` instances. - -### `target` - -DOM element, CSS selector, or plugin to mount the Informer into (`string` or -`Element`, default: `null`). diff --git a/docs/user-interfaces/elements/progress-bar.mdx b/docs/user-interfaces/elements/progress-bar.mdx deleted file mode 100644 index b8cb23dcc..000000000 --- a/docs/user-interfaces/elements/progress-bar.mdx +++ /dev/null @@ -1,91 +0,0 @@ ---- -sidebar_position: 5 -slug: /progress-bar ---- - -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; - -import UppyCdnExample from '/src/components/UppyCdnExample'; - -# Progress bar - -`@uppy/progress-bar` is a minimalist plugin that shows the current upload -progress in a thin bar element, like the ones used by YouTube and GitHub when -navigating between pages. - -## When should I use it? - -When you need a minimalistec progress indicator and you’re -[building your own UI](/docs/guides/building-your-own-ui-with-uppy). For most -cases, [Dashboard](/docs/dashboard) or [Drag & Drop](/docs/drag-drop) (with -[Status Bar](/docs/status-bar)) is more likely what you need. - -## Install - - - - -```shell -npm install @uppy/progress-bar -``` - - - - - -```shell -yarn add @uppy/progress-bar -``` - - - - - - {` - import { Uppy, ProgressBar } from "{{UPPY_JS_URL}}" - const uppy = new Uppy() - uppy.use(ProgressBar, { target: '#progress-bar' }) - `} - - - - -## Use - -```js -import Uppy from '@uppy/core'; -import ProgressBar from '@uppy/progress-bar'; - -import '@uppy/core/dist/style.min.css'; -import '@uppy/progress-bar/dist/style.min.css'; - -new Uppy().use(ProgressBar, { target: '#progress-bar' }); -``` - -## API - -### Options - -#### `id` - -A unique identifier for this Progress Bar (`string`, default: `'ProgressBar'`). - -Use this if you need to add several ProgressBar instances. - -#### `target` - -DOM element, CSS selector, or plugin to mount the Progress Bar into (`Element`, -`string`, default: `null`). - -#### `fixed` - -Show the progress bar at the top of the page with `position: fixed` (`boolean`, -default: `false`). - -When set to false, show the progress bar inline wherever it’s mounted. - -#### `hideAfterFinish` - -Hides the progress bar after the upload has finished (`boolean`, default: -`true`). diff --git a/docs/user-interfaces/elements/status-bar.mdx b/docs/user-interfaces/elements/status-bar.mdx deleted file mode 100644 index b740672fe..000000000 --- a/docs/user-interfaces/elements/status-bar.mdx +++ /dev/null @@ -1,202 +0,0 @@ ---- -sidebar_position: 4 -slug: /status-bar ---- - -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; - -import UppyCdnExample from '/src/components/UppyCdnExample'; - -# Status bar - -The `@uppy/status-bar` plugin shows upload progress and speed, estimated time, -pre- and post-processing information, and allows users to control -(pause/resume/cancel) the upload. - -:::tip - -Try it out together with [`@uppy/drag-drop`](/docs/drag-drop) in -[StackBlitz](https://stackblitz.com/edit/vitejs-vite-yzbujq?file=main.js/g) - -::: - -## When should I use it? - -When you use the [Dashboard](/docs/dashboard) it’s already included by default. -This plugin is published separately but made specifically for the Dashboard. You -can still use it without it but it may need some (CSS) tweaking for your use -case. - -## Install - - - - -```shell -npm install @uppy/status-bar -``` - - - - - -```shell -yarn add @uppy/status-bar -``` - - - - - - {` - import { Uppy, StatusBar } from "{{UPPY_JS_URL}}" - const uppy = new Uppy() - uppy.use(StatusBar, { target: '#status-bar' }) - `} - - - - -## Use - -```js showLineNumbers -import Uppy from '@uppy/core'; -import StatusBar from '@uppy/status-bar'; - -import '@uppy/core/dist/style.min.css'; -import '@uppy/status-bar/dist/style.min.css'; - -new Uppy().use(StatusBar, { target: '#status-bar' }); -``` - -## API - -### Options - -#### `id` - -A unique identifier for this Status Bar (`string`, default: `'StatusBar'`). - -Use this if you need to add several StatusBar instances. - -#### `target` - -DOM element, CSS selector, or plugin to place the drag and drop area into -(`string`, `Element`, `Function`, or `UIPlugin`, default: -[`Dashboard`](/docs/dashboard)). - -#### `hideAfterFinish` - -Hide the Status Bar after the upload is complete (`boolean`, default: `true`). - -#### `showProgressDetails` - -Display remaining upload size and estimated time (`boolean`, default: `false`) - -:::note - -`false`: Uploading: 45% - -`true`: Uploading: 45%・43 MB of 101 MB・8s left - -::: - -#### `hideUploadButton` - -Hide the upload button (`boolean`, default: `false`). - -Use this if you are providing a custom upload button somewhere, and using the -`uppy.upload()` API. - -#### `hideRetryButton` - -Hide the retry button (`boolean`, default: `false`). - -Use this if you are providing a custom retry button somewhere, and using the -`uppy.retryAll()` or `uppy.retryUpload(fileID)` API. - -#### `hidePauseResumeButton` - -Hide pause/resume buttons (for resumable uploads, via [tus](http://tus.io), for -example) (`boolean`, default: `false`). - -Use this if you are providing custom cancel or pause/resume buttons somewhere, -and using the `uppy.pauseResume(fileID)` or `uppy.removeFile(fileID)` API. - -#### `hideCancelButton` - -Hide the cancel button (`boolean`, default: `false`). - -Use this if you are providing a custom retry button somewhere, and using the -`uppy.cancelAll()` API. - -#### `doneButtonHandler` - -Status Bar will render a “Done” button in place of pause/resume/cancel buttons, -once the upload/encoding is done (`Function`, default: `null`). - -The behaviour of this “Done” button is defined by the handler function — can be -used to close file picker modals or clear the upload state. This is what the -Dashboard plugin, which uses Status Bar internally, sets: - -```js -const doneButtonHandler = () => { - this.uppy.reset(); - this.requestCloseModal(); -}; -``` - -#### `locale` - -```js -export default { - strings: { - // Shown in the status bar while files are being uploaded. - uploading: 'Uploading', - // Shown in the status bar once all files have been uploaded. - complete: 'Complete', - // Shown in the status bar if an upload failed. - uploadFailed: 'Upload failed', - // Shown in the status bar while the upload is paused. - paused: 'Paused', - // Used as the label for the button that retries an upload. - retry: 'Retry', - // Used as the label for the button that cancels an upload. - cancel: 'Cancel', - // Used as the label for the button that pauses an upload. - pause: 'Pause', - // Used as the label for the button that resumes an upload. - resume: 'Resume', - // Used as the label for the button that resets the upload state after an upload - done: 'Done', - // When `showProgressDetails` is set, shows the number of files that have been fully uploaded so far. - filesUploadedOfTotal: { - 0: '%{complete} of %{smart_count} file uploaded', - 1: '%{complete} of %{smart_count} files uploaded', - }, - // When `showProgressDetails` is set, shows the amount of bytes that have been uploaded so far. - dataUploadedOfTotal: '%{complete} of %{total}', - // When `showProgressDetails` is set, shows an estimation of how long the upload will take to complete. - xTimeLeft: '%{time} left', - // Used as the label for the button that starts an upload. - uploadXFiles: { - 0: 'Upload %{smart_count} file', - 1: 'Upload %{smart_count} files', - }, - // Used as the label for the button that starts an upload, if another upload has been started in the past - // and new files were added later. - uploadXNewFiles: { - 0: 'Upload +%{smart_count} file', - 1: 'Upload +%{smart_count} files', - }, - upload: 'Upload', - retryUpload: 'Retry upload', - xMoreFilesAdded: { - 0: '%{smart_count} more file added', - 1: '%{smart_count} more files added', - }, - showErrorDetails: 'Show error details', - }, -}; -``` diff --git a/docs/user-interfaces/elements/thumbnail-generator.mdx b/docs/user-interfaces/elements/thumbnail-generator.mdx deleted file mode 100644 index a1aeb7ddb..000000000 --- a/docs/user-interfaces/elements/thumbnail-generator.mdx +++ /dev/null @@ -1,168 +0,0 @@ ---- -sidebar_position: 2 -slug: /thumbnail-generator ---- - -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; - -import UppyCdnExample from '/src/components/UppyCdnExample'; - -# Thumbnail generator - -`@uppy/thumbnail-generator` generates proportional thumbnails (file previews) -for images that are added to Uppy. - -## When should I use this? - -This plugin is included by default with the [Dashboard](/docs/dashboard) plugin, -and can also be useful to display image previews in a custom UI. - -:::note - -Thumbnails are only generated for _local_ files. Remote files through -[Companion](/docs/companion) generally won’t have thumbnails because they are -downloaded on the server. Some providers, such as Google Drive, have baked in -thumbnails into their API responses. - -::: - -## Install - - - - -```shell -npm install @uppy/thumbnail-generator -``` - - - - - -```shell -yarn add @uppy/thumbnail-generator -``` - - - - - - {` - import { Uppy, ThumbnailGenerator } from "{{UPPY_JS_URL}}" - const uppy = new Uppy() - uppy.use(ThumbnailGenerator) - `} - - - - -## Use - -If you use the [Dashboard](/docs/dashboard) then it’s already installed. If you -want to use it programmatically for your own UI: - -```js showLineNumbers -import Uppy from '@uppy/core'; -import ThumbnailGenerator from '@uppy/thumbnail-generator'; - -const uppy = new Uppy(); - -uppy.use(ThumbnailGenerator); -uppy.on('thumbnail:generated', (file, preview) => doSomething(file, preview)); -``` - -## API - -### Options - -#### `id` - -A unique identifier for this plugin (`string`, default: `'ThumbnailGenerator'`). - -#### `locale` - -```js -export default { - strings: { - generatingThumbnails: 'Generating thumbnails...', - }, -}; -``` - -#### `thumbnailWidth` - -Width of the resulting thumbnail (`number`, default: `200`). - -Thumbnails are always proportional and not cropped. If width is provided, height -is calculated automatically to match ratio. If both width and height are given, -only width is taken into account. - -#### `thumbnailHeight` - -Height of the resulting thumbnail (`number`, default: `200`). - -Thumbnails are always proportional and not cropped. If height is provided, width -is calculated automatically to match ratio. If both width and height are given, -only width is taken into account. - -:::note - -Produce a 300px height thumbnail with calculated width to match ratio: - -```js -uppy.use(ThumbnailGenerator, { thumbnailHeight: 300 }); -``` - -Produce a 300px width thumbnail with calculated height to match ratio (and -ignore the given height): - -```js -uppy.use(ThumbnailGenerator, { thumbnailWidth: 300, thumbnailHeight: 300 }); -``` - -See issue [#979](https://github.com/transloadit/uppy/issues/979) and -[#1096](https://github.com/transloadit/uppy/pull/1096) for details on this -feature. - -::: - -#### `thumbnailType` - -MIME type of the resulting thumbnail (`string`, default: `'image/jpeg'`). - -This is useful if you want to support transparency in your thumbnails by -switching to `image/png`. - -#### `waitForThumbnailsBeforeUpload` - -Whether to wait for all thumbnails to be ready before starting the upload -(`boolean`, default: `false`). - -If set to `true`, Thumbnail Generator will invoke Uppy’s internal processing -stage and wait for `thumbnail:all-generated` event, before proceeding to the -uploading stage. This is useful because Thumbnail Generator also adds EXIF data -to images, and if we wait until it’s done processing, this data will be -available on the server after the upload. - -### Events - -:::info - -You can use [`on`](/docs/uppy#onevent-action) and -[`once`](/docs/uppy#onceevent-action) to listen to these events. - -::: - -#### `thumbnail:generated` - -Emitted with `file` and `preview` local url as arguments: - -```js -uppy.on('thumbnail:generated', (file, preview) => { - const img = document.createElement('img'); - img.src = preview; - img.width = 100; - document.body.appendChild(img); -}); -``` diff --git a/e2e/.parcelrc b/e2e/.parcelrc deleted file mode 100644 index 236c7c742..000000000 --- a/e2e/.parcelrc +++ /dev/null @@ -1,9 +0,0 @@ -{ - "extends": "@parcel/config-default", - "transformers": { - "*.{js,mjs,jsx,cjs,ts,tsx}": [ - "@parcel/transformer-js", - "@parcel/transformer-react-refresh-wrap" - ] - } -} diff --git a/e2e/clients/dashboard-aws-multipart/app.js b/e2e/clients/dashboard-aws-multipart/app.js deleted file mode 100644 index 8abb12ff9..000000000 --- a/e2e/clients/dashboard-aws-multipart/app.js +++ /dev/null @@ -1,17 +0,0 @@ -import { Uppy } from '@uppy/core' -import Dashboard from '@uppy/dashboard' -import AwsS3Multipart from '@uppy/aws-s3-multipart' - -import '@uppy/core/dist/style.css' -import '@uppy/dashboard/dist/style.css' - -const uppy = new Uppy() - .use(Dashboard, { target: '#app', inline: true }) - .use(AwsS3Multipart, { - limit: 2, - endpoint: process.env.VITE_COMPANION_URL, - shouldUseMultipart: true, - }) - -// Keep this here to access uppy in tests -window.uppy = uppy diff --git a/e2e/clients/dashboard-aws-multipart/index.html b/e2e/clients/dashboard-aws-multipart/index.html deleted file mode 100644 index 9ccaf9e3b..000000000 --- a/e2e/clients/dashboard-aws-multipart/index.html +++ /dev/null @@ -1,11 +0,0 @@ - - - - - dashboard-aws-multipart - - - -
- - diff --git a/e2e/clients/dashboard-aws/app.js b/e2e/clients/dashboard-aws/app.js deleted file mode 100644 index 2c23b9205..000000000 --- a/e2e/clients/dashboard-aws/app.js +++ /dev/null @@ -1,17 +0,0 @@ -import { Uppy } from '@uppy/core' -import Dashboard from '@uppy/dashboard' -import AwsS3 from '@uppy/aws-s3' - -import '@uppy/core/dist/style.css' -import '@uppy/dashboard/dist/style.css' - -const uppy = new Uppy() - .use(Dashboard, { target: '#app', inline: true }) - .use(AwsS3, { - limit: 2, - endpoint: process.env.VITE_COMPANION_URL, - shouldUseMultipart: false, - }) - -// Keep this here to access uppy in tests -window.uppy = uppy diff --git a/e2e/clients/dashboard-aws/index.html b/e2e/clients/dashboard-aws/index.html deleted file mode 100644 index 27ecd5736..000000000 --- a/e2e/clients/dashboard-aws/index.html +++ /dev/null @@ -1,11 +0,0 @@ - - - - - dashboard-aws - - - -
- - diff --git a/e2e/clients/dashboard-compressor/app.js b/e2e/clients/dashboard-compressor/app.js deleted file mode 100644 index a0ca67ddd..000000000 --- a/e2e/clients/dashboard-compressor/app.js +++ /dev/null @@ -1,18 +0,0 @@ -import Uppy from '@uppy/core' -import Dashboard from '@uppy/dashboard' -import Compressor from '@uppy/compressor' - -import '@uppy/core/dist/style.css' -import '@uppy/dashboard/dist/style.css' - -const uppy = new Uppy() - .use(Dashboard, { - target: document.body, - inline: true, - }) - .use(Compressor, { - mimeType: 'image/webp', - }) - -// Keep this here to access uppy in tests -window.uppy = uppy diff --git a/e2e/clients/dashboard-compressor/index.html b/e2e/clients/dashboard-compressor/index.html deleted file mode 100644 index 849e8502c..000000000 --- a/e2e/clients/dashboard-compressor/index.html +++ /dev/null @@ -1,9 +0,0 @@ - - - - - dashboard-compressor - - - - diff --git a/e2e/clients/dashboard-transloadit/app.js b/e2e/clients/dashboard-transloadit/app.js deleted file mode 100644 index 67cf18bdf..000000000 --- a/e2e/clients/dashboard-transloadit/app.js +++ /dev/null @@ -1,25 +0,0 @@ -import { Uppy } from '@uppy/core' -import Dashboard from '@uppy/dashboard' -import Transloadit from '@uppy/transloadit' - -import generateSignatureIfSecret from './generateSignatureIfSecret.js' - -import '@uppy/core/dist/style.css' -import '@uppy/dashboard/dist/style.css' - -// Environment variables: -// https://en.parceljs.org/env.html -const uppy = new Uppy() - .use(Dashboard, { target: '#app', inline: true }) - .use(Transloadit, { - service: process.env.VITE_TRANSLOADIT_SERVICE_URL, - waitForEncoding: true, - assemblyOptions: () => - generateSignatureIfSecret(process.env.VITE_TRANSLOADIT_SECRET, { - auth: { key: process.env.VITE_TRANSLOADIT_KEY }, - template_id: process.env.VITE_TRANSLOADIT_TEMPLATE, - }), - }) - -// Keep this here to access uppy in tests -window.uppy = uppy diff --git a/e2e/clients/dashboard-transloadit/generateSignatureIfSecret.js b/e2e/clients/dashboard-transloadit/generateSignatureIfSecret.js deleted file mode 100644 index 2f40ce20e..000000000 --- a/e2e/clients/dashboard-transloadit/generateSignatureIfSecret.js +++ /dev/null @@ -1,34 +0,0 @@ -const enc = new TextEncoder('utf-8') -async function sign (secret, body) { - const algorithm = { name: 'HMAC', hash: 'SHA-384' } - - const key = await crypto.subtle.importKey('raw', enc.encode(secret), algorithm, false, ['sign', 'verify']) - const signature = await crypto.subtle.sign(algorithm.name, key, enc.encode(body)) - return `sha384:${Array.from(new Uint8Array(signature), x => x.toString(16).padStart(2, '0')).join('')}` -} -function getExpiration (future) { - return new Date(Date.now() + future) - .toISOString() - .replace('T', ' ') - .replace(/\.\d+Z$/, '+00:00') -} -/** - * Adds an expiration date and signs the params object if a secret is passed to - * it. If no secret is given, it returns the same object. - * - * @param {string | undefined} secret - * @param {object} params - * @returns {{ params: string, signature?: string }} - */ -export default async function generateSignatureIfSecret (secret, params) { - let signature - if (secret) { - // eslint-disable-next-line no-param-reassign - params.auth.expires = getExpiration(5 * 60 * 1000) - // eslint-disable-next-line no-param-reassign - params = JSON.stringify(params) - signature = await sign(secret, params) - } - - return { params, signature } -} diff --git a/e2e/clients/dashboard-transloadit/index.html b/e2e/clients/dashboard-transloadit/index.html deleted file mode 100644 index e05e15c94..000000000 --- a/e2e/clients/dashboard-transloadit/index.html +++ /dev/null @@ -1,11 +0,0 @@ - - - - - dashboard-transloadit - - - -
- - diff --git a/e2e/clients/dashboard-tus/app.js b/e2e/clients/dashboard-tus/app.js deleted file mode 100644 index 4ce9ec033..000000000 --- a/e2e/clients/dashboard-tus/app.js +++ /dev/null @@ -1,25 +0,0 @@ -import { Uppy } from '@uppy/core' -import Dashboard from '@uppy/dashboard' -import Tus from '@uppy/tus' -import Unsplash from '@uppy/unsplash' -import Url from '@uppy/url' - -import '@uppy/core/dist/style.css' -import '@uppy/dashboard/dist/style.css' - -function onShouldRetry (err, retryAttempt, options, next) { - if (err?.originalResponse?.getStatus() === 418) { - return true - } - return next(err) -} - -const companionUrl = 'http://localhost:3020' -const uppy = new Uppy() - .use(Dashboard, { target: '#app', inline: true }) - .use(Tus, { endpoint: 'https://tusd.tusdemo.net/files', onShouldRetry }) - .use(Url, { target: Dashboard, companionUrl }) - .use(Unsplash, { target: Dashboard, companionUrl }) - -// Keep this here to access uppy in tests -window.uppy = uppy diff --git a/e2e/clients/dashboard-tus/index.html b/e2e/clients/dashboard-tus/index.html deleted file mode 100644 index 235833541..000000000 --- a/e2e/clients/dashboard-tus/index.html +++ /dev/null @@ -1,11 +0,0 @@ - - - - - dashboard-tus - - - -
- - diff --git a/e2e/clients/dashboard-ui/app.js b/e2e/clients/dashboard-ui/app.js deleted file mode 100644 index 6e63ab9a1..000000000 --- a/e2e/clients/dashboard-ui/app.js +++ /dev/null @@ -1,36 +0,0 @@ -import Uppy from '@uppy/core' -import Dashboard from '@uppy/dashboard' -import RemoteSources from '@uppy/remote-sources' -import Webcam from '@uppy/webcam' -import ScreenCapture from '@uppy/screen-capture' -import GoldenRetriever from '@uppy/golden-retriever' -import ImageEditor from '@uppy/image-editor' -import DropTarget from '@uppy/drop-target' -import Audio from '@uppy/audio' -import Compressor from '@uppy/compressor' - -import '@uppy/core/dist/style.css' -import '@uppy/dashboard/dist/style.css' - -const COMPANION_URL = 'http://companion.uppy.io' - -const uppy = new Uppy() - .use(Dashboard, { target: '#app', inline: true }) - .use(RemoteSources, { companionUrl: COMPANION_URL }) - .use(Webcam, { - target: Dashboard, - showVideoSourceDropdown: true, - showRecordingLength: true, - }) - .use(Audio, { - target: Dashboard, - showRecordingLength: true, - }) - .use(ScreenCapture, { target: Dashboard }) - .use(ImageEditor, { target: Dashboard }) - .use(DropTarget, { target: document.body }) - .use(Compressor) - .use(GoldenRetriever, { serviceWorker: true }) - -// Keep this here to access uppy in tests -window.uppy = uppy diff --git a/e2e/clients/dashboard-ui/index.html b/e2e/clients/dashboard-ui/index.html deleted file mode 100644 index 9beff94ca..000000000 --- a/e2e/clients/dashboard-ui/index.html +++ /dev/null @@ -1,11 +0,0 @@ - - - - - dashboard-ui - - - -
- - diff --git a/e2e/clients/dashboard-vue/App.vue b/e2e/clients/dashboard-vue/App.vue deleted file mode 100644 index 96fad8b78..000000000 --- a/e2e/clients/dashboard-vue/App.vue +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - diff --git a/e2e/clients/dashboard-vue/index.html b/e2e/clients/dashboard-vue/index.html deleted file mode 100644 index d4254a3b9..000000000 --- a/e2e/clients/dashboard-vue/index.html +++ /dev/null @@ -1,11 +0,0 @@ - - - - - dashboard-vue - - - -
- - diff --git a/e2e/clients/dashboard-vue/index.js b/e2e/clients/dashboard-vue/index.js deleted file mode 100644 index 01433bca2..000000000 --- a/e2e/clients/dashboard-vue/index.js +++ /dev/null @@ -1,4 +0,0 @@ -import { createApp } from 'vue' -import App from './App.vue' - -createApp(App).mount('#app') diff --git a/e2e/clients/dashboard-xhr/app.js b/e2e/clients/dashboard-xhr/app.js deleted file mode 100644 index 88300630a..000000000 --- a/e2e/clients/dashboard-xhr/app.js +++ /dev/null @@ -1,18 +0,0 @@ -import { Uppy } from '@uppy/core' -import Dashboard from '@uppy/dashboard' -import XHRUpload from '@uppy/xhr-upload' -import Unsplash from '@uppy/unsplash' -import Url from '@uppy/url' - -import '@uppy/core/dist/style.css' -import '@uppy/dashboard/dist/style.css' - -const companionUrl = 'http://localhost:3020' -const uppy = new Uppy() - .use(Dashboard, { target: '#app', inline: true }) - .use(XHRUpload, { endpoint: 'https://xhr-server.herokuapp.com/upload', limit: 6 }) - .use(Url, { target: Dashboard, companionUrl }) - .use(Unsplash, { target: Dashboard, companionUrl }) - -// Keep this here to access uppy in tests -window.uppy = uppy diff --git a/e2e/clients/dashboard-xhr/index.html b/e2e/clients/dashboard-xhr/index.html deleted file mode 100644 index 83a81c643..000000000 --- a/e2e/clients/dashboard-xhr/index.html +++ /dev/null @@ -1,11 +0,0 @@ - - - - - dashboard-xhr - - - -
- - diff --git a/e2e/clients/index.html b/e2e/clients/index.html deleted file mode 100644 index c4582008b..000000000 --- a/e2e/clients/index.html +++ /dev/null @@ -1,31 +0,0 @@ - - - - - End-to-End test suite - - -

Test apps

- - - diff --git a/e2e/clients/react/App.jsx b/e2e/clients/react/App.jsx deleted file mode 100644 index 4818918fe..000000000 --- a/e2e/clients/react/App.jsx +++ /dev/null @@ -1,48 +0,0 @@ -/* eslint-disable react/react-in-jsx-scope */ -import Uppy from '@uppy/core' -/* eslint-disable-next-line no-unused-vars */ -import React, { useState } from 'react' -import { Dashboard, DashboardModal, DragDrop } from '@uppy/react' -import ThumbnailGenerator from '@uppy/thumbnail-generator' -import RemoteSources from '@uppy/remote-sources' - -import '@uppy/core/dist/style.css' -import '@uppy/dashboard/dist/style.css' -import '@uppy/drag-drop/dist/style.css' - -const uppyDashboard = new Uppy({ id: 'dashboard' }).use(RemoteSources, { - companionUrl: 'http://companion.uppy.io', - sources: ['GoogleDrive', 'OneDrive', 'Unsplash', 'Zoom', 'Url'], -}) -const uppyModal = new Uppy({ id: 'modal' }) -const uppyDragDrop = new Uppy({ id: 'drag-drop' }).use(ThumbnailGenerator) - -export default function App() { - const [open, setOpen] = useState(false) - // TODO: Parcel is having a bad time resolving React inside @uppy/react for some reason. - // We are using Parcel in an odd way and I don't think there is an easy fix. - // const files = useUppyState(uppyDashboard, (state) => state.files) - - // drag-drop has no visual output so we test it via the uppy instance - window.uppy = uppyDragDrop - - return ( -
- - {/*

Dashboard file count: {Object.keys(files).length}

*/} - - - - -
- ) -} diff --git a/e2e/clients/react/index.html b/e2e/clients/react/index.html deleted file mode 100644 index 315cd8ce9..000000000 --- a/e2e/clients/react/index.html +++ /dev/null @@ -1,11 +0,0 @@ - - - - - dashboard-react - - - -
- - diff --git a/e2e/clients/react/index.jsx b/e2e/clients/react/index.jsx deleted file mode 100644 index c9b4e66e4..000000000 --- a/e2e/clients/react/index.jsx +++ /dev/null @@ -1,8 +0,0 @@ -/* eslint-disable react/react-in-jsx-scope */ -import { createRoot } from 'react-dom/client' -import App from './App.jsx' - -const container = document.getElementById('app') -const root = createRoot(container) - -root.render() diff --git a/e2e/cypress.config.mjs b/e2e/cypress.config.mjs deleted file mode 100644 index c793357e7..000000000 --- a/e2e/cypress.config.mjs +++ /dev/null @@ -1,20 +0,0 @@ -import { defineConfig } from 'cypress' -import installLogsPrinter from 'cypress-terminal-report/src/installLogsPrinter.js' -import startMockServer from './mock-server.mjs' - -export default defineConfig({ - defaultCommandTimeout: 16_000, - requestTimeout: 16_000, - - e2e: { - baseUrl: 'http://localhost:1234', - specPattern: 'cypress/integration/*.spec.ts', - - setupNodeEvents (on) { - // implement node event listeners here - installLogsPrinter(on) - - startMockServer('localhost', 4678) - }, - }, -}) diff --git a/e2e/cypress/fixtures/1020-percent-state.json b/e2e/cypress/fixtures/1020-percent-state.json deleted file mode 100644 index 8c0b63b51..000000000 --- a/e2e/cypress/fixtures/1020-percent-state.json +++ /dev/null @@ -1,983 +0,0 @@ -{ - "plugins": { - "Dashboard": { - "isHidden": false, - "fileCardFor": null, - "activeOverlayType": null, - "showAddFilesPanel": false, - "activePickerPanel": false, - "metaFields": [ - { - "id": "license", - "name": "License", - "placeholder": "specify license" - }, - { - "id": "caption", - "name": "Caption", - "placeholder": "add caption" - } - ], - "targets": [ - { - "id": "Dashboard:StatusBar", - "name": "StatusBar", - "type": "progressindicator" - }, - { - "id": "Dashboard:Informer", - "name": "Informer", - "type": "progressindicator" - }, - { - "id": "GoogleDrive", - "name": "Google Drive", - "type": "acquirer" - }, - { - "id": "Instagram", - "name": "Instagram", - "type": "acquirer" - }, - { - "id": "Dropbox", - "name": "Dropbox", - "type": "acquirer" - }, - { - "id": "Url", - "name": "Link", - "type": "acquirer" - }, - { - "id": "Webcam", - "name": "Camera", - "type": "acquirer" - } - ], - "areInsidesReadyToBeVisible": true, - "containerWidth": 750, - "containerHeight": 490 - }, - "GoogleDrive": { - "currentSelection": [], - "authenticated": false, - "files": [], - "folders": [], - "directories": [], - "activeRow": -1, - "filterInput": "", - "isSearchVisible": false, - "hasTeamDrives": false, - "teamDrives": [], - "teamDriveId": "" - }, - "Instagram": { - "currentSelection": [], - "authenticated": true, - "files": [ - { - "isFolder": false, - "icon": "https://scontent.cdninstagram.com/vp/420d2bdc2cb30251d7ecf8e516f7fa7d/5D55A291/t51.2885-15/e35/s320x320/50692753_2474466385958388_3994336603663016042_n.jpg?_nc_ht=scontent.cdninstagram.com", - "name": "Instagram Feb 11, 2019, 3:34 PM.jpeg", - "mimeType": "image/jpeg", - "id": "1976930126949922734_104680", - "thumbnail": "https://scontent.cdninstagram.com/vp/ff2400b726bbd3415c4a07b723615578/5D3C08E9/t51.2885-15/e35/s150x150/50692753_2474466385958388_3994336603663016042_n.jpg?_nc_ht=scontent.cdninstagram.com", - "requestPath": "1976930126949922734_104680", - "modifiedDate": "1549888457" - }, - { - "isFolder": false, - "icon": "https://scontent.cdninstagram.com/vp/78aa49ad8ccbe12b07fb20f07c1b9a1d/5D520E84/t51.2885-15/e35/s320x320/49858772_2238267119827712_38852393303322952_n.jpg?_nc_ht=scontent.cdninstagram.com", - "name": "Instagram Jan 18, 2019, 11:39 PM.jpeg", - "mimeType": "image/jpeg", - "id": "1959779810654008285_104680", - "thumbnail": "https://scontent.cdninstagram.com/vp/b3eda9daaa214951c1fb1a0f7000d8de/5D3F3F89/t51.2885-15/e35/s150x150/49858772_2238267119827712_38852393303322952_n.jpg?_nc_ht=scontent.cdninstagram.com", - "requestPath": "1959779810654008285_104680?carousel_id=0", - "modifiedDate": "1547843980" - }, - { - "isFolder": false, - "icon": "https://scontent.cdninstagram.com/vp/ec36a8b8b7db546abeb3cdcf12b52ba5/5D77DC5F/t51.2885-15/e35/s320x320/49858316_1974473399524283_2231924729468134373_n.jpg?_nc_ht=scontent.cdninstagram.com", - "name": "Instagram Jan 18, 2019, 11:39 PM 1.jpeg", - "mimeType": "image/jpeg", - "id": "1959779810654008285_1046801", - "thumbnail": "https://scontent.cdninstagram.com/vp/d8ef3dd687b6aab396cae9284226c7df/5D711727/t51.2885-15/e35/s150x150/49858316_1974473399524283_2231924729468134373_n.jpg?_nc_ht=scontent.cdninstagram.com", - "requestPath": "1959779810654008285_104680?carousel_id=1", - "modifiedDate": "1547843980" - }, - { - "isFolder": false, - "icon": "https://scontent.cdninstagram.com/vp/3055f2ae78d775fc031654d20e791ebc/5D51235E/t51.2885-15/e35/s320x320/49933915_184580175831450_4288362971970794931_n.jpg?_nc_ht=scontent.cdninstagram.com", - "name": "Instagram Jan 18, 2019, 11:39 PM 2.jpeg", - "mimeType": "image/jpeg", - "id": "1959779810654008285_1046802", - "thumbnail": "https://scontent.cdninstagram.com/vp/3d1b5aa16b8d64bc7c14ae1e3b13dfbe/5D5DD553/t51.2885-15/e35/c0.0.1079.1079a/s150x150/49933915_184580175831450_4288362971970794931_n.jpg?_nc_ht=scontent.cdninstagram.com", - "requestPath": "1959779810654008285_104680?carousel_id=2", - "modifiedDate": "1547843980" - }, - { - "isFolder": false, - "icon": "https://scontent.cdninstagram.com/vp/3da7cd1f79dcecde1e871ea09cf65a87/5D737FDF/t51.2885-15/e35/s320x320/50515172_1138232866338235_1751853475314282763_n.jpg?_nc_ht=scontent.cdninstagram.com", - "name": "Instagram Jan 18, 2019, 11:39 PM 3.jpeg", - "mimeType": "image/jpeg", - "id": "1959779810654008285_1046803", - "thumbnail": "https://scontent.cdninstagram.com/vp/5c51a2dac516e575a66cf10c39615faf/5D6922A7/t51.2885-15/e35/s150x150/50515172_1138232866338235_1751853475314282763_n.jpg?_nc_ht=scontent.cdninstagram.com", - "requestPath": "1959779810654008285_104680?carousel_id=3", - "modifiedDate": "1547843980" - }, - { - "isFolder": false, - "icon": "https://scontent.cdninstagram.com/vp/9b869a8590154eb01bdb8c4d834dbc96/5D68F919/t51.2885-15/e35/s320x320/49536508_2715697688455532_3941725382632324585_n.jpg?_nc_ht=scontent.cdninstagram.com", - "name": "Instagram Jan 18, 2019, 11:39 PM 4.jpeg", - "mimeType": "image/jpeg", - "id": "1959779810654008285_1046804", - "thumbnail": "https://scontent.cdninstagram.com/vp/9d5ef7d80443c6e606dd2032bd9bbef3/5D736861/t51.2885-15/e35/s150x150/49536508_2715697688455532_3941725382632324585_n.jpg?_nc_ht=scontent.cdninstagram.com", - "requestPath": "1959779810654008285_104680?carousel_id=4", - "modifiedDate": "1547843980" - }, - { - "isFolder": false, - "icon": "https://scontent.cdninstagram.com/vp/e93879efc4dad02ed95f28c845b30c1b/5D6EE4D2/t51.2885-15/e35/s320x320/50024282_2330888297144674_8558997522361102927_n.jpg?_nc_ht=scontent.cdninstagram.com", - "name": "Instagram Jan 18, 2019, 11:39 PM 5.jpeg", - "mimeType": "image/jpeg", - "id": "1959779810654008285_1046805", - "thumbnail": "https://scontent.cdninstagram.com/vp/c173758a1dbcdc033973a4d9b77061fa/5D3FD13A/t51.2885-15/e35/c0.0.1079.1079a/s150x150/50024282_2330888297144674_8558997522361102927_n.jpg?_nc_ht=scontent.cdninstagram.com", - "requestPath": "1959779810654008285_104680?carousel_id=5", - "modifiedDate": "1547843980" - }, - { - "isFolder": false, - "icon": "https://scontent.cdninstagram.com/vp/385551f9d659254e79e52e2069962abe/5D3DB99B/t51.2885-15/e35/s320x320/49541690_1326265230849388_780299101204315442_n.jpg?_nc_ht=scontent.cdninstagram.com", - "name": "Instagram Jan 18, 2019, 11:39 PM 6.jpeg", - "mimeType": "image/jpeg", - "id": "1959779810654008285_1046806", - "thumbnail": "https://scontent.cdninstagram.com/vp/39449810cc5a750d7da2ff0311986e5f/5D6F4796/t51.2885-15/e35/c0.0.1079.1079a/s150x150/49541690_1326265230849388_780299101204315442_n.jpg?_nc_ht=scontent.cdninstagram.com", - "requestPath": "1959779810654008285_104680?carousel_id=6", - "modifiedDate": "1547843980" - }, - { - "isFolder": false, - "icon": "https://scontent.cdninstagram.com/vp/6dbe8e2193c6f35cc9a4b3681331281d/5D6B8838/t51.2885-15/e35/s320x320/47693310_2066019920179632_9021194778493613415_n.jpg?_nc_ht=scontent.cdninstagram.com", - "name": "Instagram Jan 18, 2019, 11:39 PM 7.jpeg", - "mimeType": "image/jpeg", - "id": "1959779810654008285_1046807", - "thumbnail": "https://scontent.cdninstagram.com/vp/d82f97e1cd037d3b5d8a9d3be1620725/5D54D1D0/t51.2885-15/e35/c0.0.1079.1079a/s150x150/47693310_2066019920179632_9021194778493613415_n.jpg?_nc_ht=scontent.cdninstagram.com", - "requestPath": "1959779810654008285_104680?carousel_id=7", - "modifiedDate": "1547843980" - }, - { - "isFolder": false, - "icon": "https://scontent.cdninstagram.com/vp/ab34b879e64b6b74d8cedfc7193dbc7f/5D5846E9/t51.2885-15/e35/s320x320/49306549_310052152966468_747321250340934181_n.jpg?_nc_ht=scontent.cdninstagram.com", - "name": "Instagram Jan 18, 2019, 11:39 PM 8.jpeg", - "mimeType": "image/jpeg", - "id": "1959779810654008285_1046808", - "thumbnail": "https://scontent.cdninstagram.com/vp/4899a15cbcf805e4fbb18197322b4187/5D5118EF/t51.2885-15/e35/c0.0.1079.1079a/s150x150/49306549_310052152966468_747321250340934181_n.jpg?_nc_ht=scontent.cdninstagram.com", - "requestPath": "1959779810654008285_104680?carousel_id=8", - "modifiedDate": "1547843980" - }, - { - "isFolder": false, - "icon": "https://scontent.cdninstagram.com/vp/9898c850ebf16fb48c7c67f40fa768aa/5D5D74F8/t51.2885-15/e35/s320x320/49766698_1079661332208739_5393337093402545472_n.jpg?_nc_ht=scontent.cdninstagram.com", - "name": "Instagram Jan 18, 2019, 11:39 PM 9.jpeg", - "mimeType": "image/jpeg", - "id": "1959779810654008285_1046809", - "thumbnail": "https://scontent.cdninstagram.com/vp/24ff0de8fd62247a5b4d46657e078504/5D56FE10/t51.2885-15/e35/c0.0.1079.1079a/s150x150/49766698_1079661332208739_5393337093402545472_n.jpg?_nc_ht=scontent.cdninstagram.com", - "requestPath": "1959779810654008285_104680?carousel_id=9", - "modifiedDate": "1547843980" - }, - { - "isFolder": false, - "icon": "https://scontent.cdninstagram.com/vp/945b83923551c2419d02fb394ca93ecf/5D6FFDF2/t51.2885-15/e35/s320x320/39220053_1153571588114505_375944023631724544_n.jpg?_nc_ht=scontent.cdninstagram.com", - "name": "Instagram Aug 23, 2018, 2:58 PM.jpeg", - "mimeType": "image/jpeg", - "id": "1852250486931812607_104680", - "thumbnail": "https://scontent.cdninstagram.com/vp/c0b40cc16747180b59ae966afb3c020d/5D3CFA02/t51.2885-15/e35/s150x150/39220053_1153571588114505_375944023631724544_n.jpg?_nc_ht=scontent.cdninstagram.com", - "requestPath": "1852250486931812607_104680?carousel_id=0", - "modifiedDate": "1535025486" - }, - { - "isFolder": false, - "icon": "https://scontent.cdninstagram.com/vp/61b460c2217002b2ca277d8e2844717c/5D5EFFFD/t51.2885-15/e35/s320x320/39346998_282273142585083_1015643341825507328_n.jpg?_nc_ht=scontent.cdninstagram.com", - "name": "Instagram Aug 23, 2018, 2:58 PM 1.jpeg", - "mimeType": "image/jpeg", - "id": "1852250486931812607_1046801", - "thumbnail": "https://scontent.cdninstagram.com/vp/4b39d19ad2583c95acbb11e762579e35/5D58090D/t51.2885-15/e35/s150x150/39346998_282273142585083_1015643341825507328_n.jpg?_nc_ht=scontent.cdninstagram.com", - "requestPath": "1852250486931812607_104680?carousel_id=1", - "modifiedDate": "1535025486" - }, - { - "isFolder": false, - "icon": "https://scontent.cdninstagram.com/vp/e7a7a3f0701ce18a1b036c24cb7cea9b/5D5AFDE4/t51.2885-15/e35/s320x320/38699807_314497562641072_2259807118783676416_n.jpg?_nc_ht=scontent.cdninstagram.com", - "name": "Instagram Aug 23, 2018, 2:58 PM 2.jpeg", - "mimeType": "image/jpeg", - "id": "1852250486931812607_1046802", - "thumbnail": "https://scontent.cdninstagram.com/vp/853fe9cc8931bd25d910f1cb27e34c91/5D3C8E14/t51.2885-15/e35/s150x150/38699807_314497562641072_2259807118783676416_n.jpg?_nc_ht=scontent.cdninstagram.com", - "requestPath": "1852250486931812607_104680?carousel_id=2", - "modifiedDate": "1535025486" - }, - { - "isFolder": false, - "icon": "https://scontent.cdninstagram.com/vp/3fb0681ab45a93f538a6388d77d1e0a6/5D6C7686/t51.2885-15/e35/s320x320/39628514_334313577140078_7876516111540289536_n.jpg?_nc_ht=scontent.cdninstagram.com", - "name": "Instagram Aug 23, 2018, 2:58 PM 3.jpeg", - "mimeType": "image/jpeg", - "id": "1852250486931812607_1046803", - "thumbnail": "https://scontent.cdninstagram.com/vp/d047037085ec8f9bcf5737275a54741b/5D70D676/t51.2885-15/e35/s150x150/39628514_334313577140078_7876516111540289536_n.jpg?_nc_ht=scontent.cdninstagram.com", - "requestPath": "1852250486931812607_104680?carousel_id=3", - "modifiedDate": "1535025486" - }, - { - "isFolder": false, - "icon": "https://scontent.cdninstagram.com/vp/be4a02058c30e658896f3a1cc10938fd/5D59783D/t51.2885-15/e35/s320x320/39507580_504831159978201_5212373753534611456_n.jpg?_nc_ht=scontent.cdninstagram.com", - "name": "Instagram Aug 23, 2018, 1:20 PM.jpeg", - "mimeType": "image/jpeg", - "id": "1852201222927172582_104680", - "thumbnail": "https://scontent.cdninstagram.com/vp/4a05e2f9b3464fa502d8102e98e81c13/5D5A45CD/t51.2885-15/e35/s150x150/39507580_504831159978201_5212373753534611456_n.jpg?_nc_ht=scontent.cdninstagram.com", - "requestPath": "1852201222927172582_104680", - "modifiedDate": "1535019613" - }, - { - "isFolder": false, - "icon": "https://scontent.cdninstagram.com/vp/1ed3c5ce49461c85d30e74eb371c5142/5D3D332D/t51.2885-15/e35/s320x320/38235951_280335879462106_4098398707425214464_n.jpg?_nc_ht=scontent.cdninstagram.com", - "name": "Instagram Aug 14, 2018, 1:40 AM.jpeg", - "mimeType": "image/jpeg", - "id": "1845325816068388330_104680", - "thumbnail": "https://scontent.cdninstagram.com/vp/fb2dacd2d08807cacc48a76acbb8cb32/5D7001DD/t51.2885-15/e35/s150x150/38235951_280335879462106_4098398707425214464_n.jpg?_nc_ht=scontent.cdninstagram.com", - "requestPath": "1845325816068388330_104680", - "modifiedDate": "1534200001" - }, - { - "isFolder": false, - "icon": "https://scontent.cdninstagram.com/vp/2aaab426ea979d4fec469c53aa748a1a/5D5B581D/t51.2885-15/e35/s320x320/38097421_294352187994556_231473126364413952_n.jpg?_nc_ht=scontent.cdninstagram.com", - "name": "Instagram Aug 14, 2018, 1:37 AM.jpeg", - "mimeType": "image/jpeg", - "id": "1845324587128869025_104680", - "thumbnail": "https://scontent.cdninstagram.com/vp/568448b82d5883bf6c5b2b554dcb3c71/5D5BFCA9/t51.2885-15/e35/c135.0.809.809a/s150x150/38097421_294352187994556_231473126364413952_n.jpg?_nc_ht=scontent.cdninstagram.com", - "requestPath": "1845324587128869025_104680", - "modifiedDate": "1534199854" - }, - { - "isFolder": false, - "icon": "https://scontent.cdninstagram.com/vp/73a478dae4d7ed45ba86bb51c6f1279c/5D3BC45B/t51.2885-15/e35/s320x320/38004914_1332543240181648_8551268285629333504_n.jpg?_nc_ht=scontent.cdninstagram.com", - "name": "Instagram Aug 2, 2018, 8:27 PM.jpeg", - "mimeType": "image/jpeg", - "id": "1837195735932389212_104680", - "thumbnail": "https://scontent.cdninstagram.com/vp/55047e7ff9ab12727fc38865852cfea4/5D3A6523/t51.2885-15/e35/s150x150/38004914_1332543240181648_8551268285629333504_n.jpg?_nc_ht=scontent.cdninstagram.com", - "requestPath": "1837195735932389212_104680", - "modifiedDate": "1533230820" - }, - { - "isFolder": false, - "icon": "https://scontent.cdninstagram.com/vp/4436a67f44ab681184d648b8cef2bd0d/5D5C35EB/t51.2885-15/e35/s320x320/36979476_227621117961595_1230588248623939584_n.jpg?_nc_ht=scontent.cdninstagram.com", - "name": "Instagram Jul 12, 2018, 6:58 PM.jpeg", - "mimeType": "image/jpeg", - "id": "1821930696162038399_104680", - "thumbnail": "https://scontent.cdninstagram.com/vp/b5626d3918d93266ee92b414853b7eaa/5D515F04/t51.2885-15/e35/c123.0.735.735a/s150x150/36979476_227621117961595_1230588248623939584_n.jpg?_nc_ht=scontent.cdninstagram.com", - "requestPath": "1821930696162038399_104680", - "modifiedDate": "1531411085" - }, - { - "isFolder": false, - "icon": "https://scontent.cdninstagram.com/vp/f26b47afa38a487002fb9cd823c911a9/5D53FC32/t51.2885-15/e35/s320x320/36037683_1012778022214785_7430470254473510912_n.jpg?_nc_ht=scontent.cdninstagram.com", - "name": "Instagram Jun 24, 2018, 7:33 PM.jpeg", - "mimeType": "image/jpeg", - "id": "1808902468027558819_104680", - "thumbnail": "https://scontent.cdninstagram.com/vp/b93e11cda790a99ffd3bcdbfd50199e4/5D4044D4/t51.2885-15/e35/c101.0.606.606a/s150x150/36037683_1012778022214785_7430470254473510912_n.jpg?_nc_ht=scontent.cdninstagram.com", - "requestPath": "1808902468027558819_104680?carousel_id=0", - "modifiedDate": "1529857999" - }, - { - "isFolder": false, - "icon": "https://scontent.cdninstagram.com/vp/1be4dae4e20a9b63e887ea5879590a9b/5D526FF1/t51.2885-15/e35/s320x320/35575252_166097084248741_3980291461882052608_n.jpg?_nc_ht=scontent.cdninstagram.com", - "name": "Instagram Jun 24, 2018, 7:33 PM 1.jpeg", - "mimeType": "image/jpeg", - "id": "1808902468027558819_1046801", - "thumbnail": "https://scontent.cdninstagram.com/vp/6c1e545968b58fa4bd9279284f5e4eb2/5D3A341F/t51.2885-15/e35/c135.0.809.809a/s150x150/35575252_166097084248741_3980291461882052608_n.jpg?_nc_ht=scontent.cdninstagram.com", - "requestPath": "1808902468027558819_104680?carousel_id=1", - "modifiedDate": "1529857999" - }, - { - "isFolder": false, - "icon": "video", - "name": "Instagram Jun 24, 2018, 7:33 PM 2.mp4", - "mimeType": "video/mp4", - "id": "1808902468027558819_1046802", - "thumbnail": null, - "requestPath": "1808902468027558819_104680?carousel_id=2", - "modifiedDate": "1529857999" - }, - { - "isFolder": false, - "icon": "https://scontent.cdninstagram.com/vp/862ced4e0f047ba14ff29c6cc0318c44/5D57DF38/t51.2885-15/e35/s320x320/35278170_837444053128139_5243357991205339136_n.jpg?_nc_ht=scontent.cdninstagram.com", - "name": "Instagram Jun 20, 2018, 2:42 AM.jpeg", - "mimeType": "image/jpeg", - "id": "1805494813368147007_104680", - "thumbnail": "https://scontent.cdninstagram.com/vp/4829dc1acad2af234ff4628d7d4750c2/5D3D81C8/t51.2885-15/e35/s150x150/35278170_837444053128139_5243357991205339136_n.jpg?_nc_ht=scontent.cdninstagram.com", - "requestPath": "1805494813368147007_104680", - "modifiedDate": "1529451775" - }, - { - "isFolder": false, - "icon": "https://scontent.cdninstagram.com/vp/7c4ceb66525b802444dccd5a86924fc2/5D74C314/t51.2885-15/e35/s320x320/28764233_1638269642893040_4480301736187133952_n.jpg?_nc_ht=scontent.cdninstagram.com", - "name": "Instagram Mar 18, 2018, 9:32 PM.jpeg", - "mimeType": "image/jpeg", - "id": "1737934383083311905_104680", - "thumbnail": "https://scontent.cdninstagram.com/vp/5f57985b3b0a727ba6cb8b5107f8a992/5D50EF6C/t51.2885-15/e35/s150x150/28764233_1638269642893040_4480301736187133952_n.jpg?_nc_ht=scontent.cdninstagram.com", - "requestPath": "1737934383083311905_104680", - "modifiedDate": "1521397944" - }, - { - "isFolder": false, - "icon": "https://scontent.cdninstagram.com/vp/899393f947e53ac5b36a995d67d111df/5D5BD35A/t51.2885-15/e35/s320x320/26067758_541222112905573_5423593755456307200_n.jpg?_nc_ht=scontent.cdninstagram.com", - "name": "Instagram Jan 11, 2018, 5:18 AM.jpeg", - "mimeType": "image/jpeg", - "id": "1689609196894186490_104680", - "thumbnail": "https://scontent.cdninstagram.com/vp/fd06fd5bb588684209877d1414315f16/5D5973AA/t51.2885-15/e35/s150x150/26067758_541222112905573_5423593755456307200_n.jpg?_nc_ht=scontent.cdninstagram.com", - "requestPath": "1689609196894186490_104680", - "modifiedDate": "1515637133" - }, - { - "isFolder": false, - "icon": "https://scontent.cdninstagram.com/vp/4cde437dd22560fab9d5eb3de0b8b2e0/5D7201E3/t51.2885-15/e35/s320x320/25036240_2079885088923563_8690166922391584768_n.jpg?_nc_ht=scontent.cdninstagram.com", - "name": "Instagram Dec 22, 2017, 8:05 PM.jpeg", - "mimeType": "image/jpeg", - "id": "1675559745477333499_104680", - "thumbnail": "https://scontent.cdninstagram.com/vp/db7f2fbf1db02963e8bea721101cc287/5D6E489B/t51.2885-15/e35/s150x150/25036240_2079885088923563_8690166922391584768_n.jpg?_nc_ht=scontent.cdninstagram.com", - "requestPath": "1675559745477333499_104680", - "modifiedDate": "1513962308" - }, - { - "isFolder": false, - "icon": "https://scontent.cdninstagram.com/vp/af35659718f36655e8bf703f25bb233c/5D5F644F/t51.2885-15/e35/s320x320/24331847_1536227273080845_4856051751851130880_n.jpg?_nc_ht=scontent.cdninstagram.com", - "name": "Instagram Dec 4, 2017, 7:44 AM.jpeg", - "mimeType": "image/jpeg", - "id": "1662141091721036563_104680", - "thumbnail": "https://scontent.cdninstagram.com/vp/190fd6922e69ad087597ba0697938cf5/5D5B97BC/t51.2885-15/e35/c102.0.875.875/s150x150/24331847_1536227273080845_4856051751851130880_n.jpg?_nc_ht=scontent.cdninstagram.com", - "requestPath": "1662141091721036563_104680", - "modifiedDate": "1512362680" - }, - { - "isFolder": false, - "icon": "https://scontent.cdninstagram.com/vp/b83e927ac2d13ad1ac9d2cafdc01f959/5D405387/t51.2885-15/e35/s320x320/23417132_213197029221132_8238897611998756864_n.jpg?_nc_ht=scontent.cdninstagram.com", - "name": "Instagram Nov 12, 2017, 7:02 PM.jpeg", - "mimeType": "image/jpeg", - "id": "1646537186320644618_104680", - "thumbnail": "https://scontent.cdninstagram.com/vp/5b346e973c29acbbcaea0f61f1809f4e/5D560A77/t51.2885-15/e35/s150x150/23417132_213197029221132_8238897611998756864_n.jpg?_nc_ht=scontent.cdninstagram.com", - "requestPath": "1646537186320644618_104680", - "modifiedDate": "1510502549" - }, - { - "isFolder": false, - "icon": "https://scontent.cdninstagram.com/vp/e83ae26b09c5187a1a53083f8bdeb364/5D76BF66/t51.2885-15/e35/s320x320/23347341_1932821443410985_573390529291616256_n.jpg?_nc_ht=scontent.cdninstagram.com", - "name": "Instagram Nov 7, 2017, 4:55 AM.jpeg", - "mimeType": "image/jpeg", - "id": "1642487154005217857_104680", - "thumbnail": "https://scontent.cdninstagram.com/vp/992905011fba71ca828cb6f5b83df600/5D6F5B96/t51.2885-15/e35/s150x150/23347341_1932821443410985_573390529291616256_n.jpg?_nc_ht=scontent.cdninstagram.com", - "requestPath": "1642487154005217857_104680?carousel_id=0", - "modifiedDate": "1510019748" - }, - { - "isFolder": false, - "icon": "https://scontent.cdninstagram.com/vp/50ae2bf3f9340ff4f1d78653087d7450/5D71B641/t51.2885-15/e35/s320x320/23280046_500879000292815_4369528731417444352_n.jpg?_nc_ht=scontent.cdninstagram.com", - "name": "Instagram Nov 7, 2017, 4:55 AM 1.jpeg", - "mimeType": "image/jpeg", - "id": "1642487154005217857_1046801", - "thumbnail": "https://scontent.cdninstagram.com/vp/01cf69185d6c33521df01696920039f4/5D56B014/t51.2885-15/e35/c2.0.598.598/s150x150/23280046_500879000292815_4369528731417444352_n.jpg?_nc_ht=scontent.cdninstagram.com", - "requestPath": "1642487154005217857_104680?carousel_id=1", - "modifiedDate": "1510019748" - }, - { - "isFolder": false, - "icon": "https://scontent.cdninstagram.com/vp/1b1512e3914c2fda53ad3a840c2681be/5D6D9C4A/t51.2885-15/e35/s320x320/22858331_1958241871109983_4354847188874952704_n.jpg?_nc_ht=scontent.cdninstagram.com", - "name": "Instagram Oct 29, 2017, 7:22 PM.jpeg", - "mimeType": "image/jpeg", - "id": "1636400252659303992_104680", - "thumbnail": "https://scontent.cdninstagram.com/vp/5a5c1885bbd867feda749149e1dd56d6/5D5B9732/t51.2885-15/e35/s150x150/22858331_1958241871109983_4354847188874952704_n.jpg?_nc_ht=scontent.cdninstagram.com", - "requestPath": "1636400252659303992_104680?carousel_id=0", - "modifiedDate": "1509294133" - }, - { - "isFolder": false, - "icon": "https://scontent.cdninstagram.com/vp/8615347d7e76a9300f3593abf4e9016c/5D6C344D/t51.2885-15/e35/s320x320/23098451_123562928335776_7617655378888622080_n.jpg?_nc_ht=scontent.cdninstagram.com", - "name": "Instagram Oct 29, 2017, 7:22 PM 1.jpeg", - "mimeType": "image/jpeg", - "id": "1636400252659303992_1046801", - "thumbnail": "https://scontent.cdninstagram.com/vp/9811c2c8bfd4f7d97045d8bbf3ecd2d1/5D708FBD/t51.2885-15/e35/s150x150/23098451_123562928335776_7617655378888622080_n.jpg?_nc_ht=scontent.cdninstagram.com", - "requestPath": "1636400252659303992_104680?carousel_id=1", - "modifiedDate": "1509294133" - }, - { - "isFolder": false, - "icon": "https://scontent.cdninstagram.com/vp/01a9ab9149a22a597a80fc4cbf13738f/5D50E262/t51.2885-15/e35/s320x320/23098990_848783778612174_4475941923474898944_n.jpg?_nc_ht=scontent.cdninstagram.com", - "name": "Instagram Oct 29, 2017, 7:22 PM 2.jpeg", - "mimeType": "image/jpeg", - "id": "1636400252659303992_1046802", - "thumbnail": "https://scontent.cdninstagram.com/vp/ea649769b559a7f076bd727bee49a2d6/5D3C7892/t51.2885-15/e35/s150x150/23098990_848783778612174_4475941923474898944_n.jpg?_nc_ht=scontent.cdninstagram.com", - "requestPath": "1636400252659303992_104680?carousel_id=2", - "modifiedDate": "1509294133" - }, - { - "isFolder": false, - "icon": "https://scontent.cdninstagram.com/vp/fc516c0e146dbf9b0fca960a9411fdf7/5D3ED20B/t51.2885-15/e35/s320x320/22802126_1517752501642900_5606082552176574464_n.jpg?_nc_ht=scontent.cdninstagram.com", - "name": "Instagram Oct 29, 2017, 7:22 PM 3.jpeg", - "mimeType": "image/jpeg", - "id": "1636400252659303992_1046803", - "thumbnail": "https://scontent.cdninstagram.com/vp/001f32c24f6ccd508cc4905336073053/5D71F973/t51.2885-15/e35/s150x150/22802126_1517752501642900_5606082552176574464_n.jpg?_nc_ht=scontent.cdninstagram.com", - "requestPath": "1636400252659303992_104680?carousel_id=3", - "modifiedDate": "1509294133" - }, - { - "isFolder": false, - "icon": "https://scontent.cdninstagram.com/vp/9ccca5c9eadd69ccc13e53cf4f43b314/5D390493/t51.2885-15/e35/s320x320/22802520_139793160103830_5225518814476632064_n.jpg?_nc_ht=scontent.cdninstagram.com", - "name": "Instagram Oct 29, 2017, 7:22 PM 4.jpeg", - "mimeType": "image/jpeg", - "id": "1636400252659303992_1046804", - "thumbnail": "https://scontent.cdninstagram.com/vp/3d85f3116a34cbd465421dca31972e16/5D752E63/t51.2885-15/e35/s150x150/22802520_139793160103830_5225518814476632064_n.jpg?_nc_ht=scontent.cdninstagram.com", - "requestPath": "1636400252659303992_104680?carousel_id=4", - "modifiedDate": "1509294133" - }, - { - "isFolder": false, - "icon": "https://scontent.cdninstagram.com/vp/031438a7a7f73fadff9af4f9f27e897a/5D5AB962/t51.2885-15/e35/s320x320/22639252_183626528874474_2694579090425380864_n.jpg?_nc_ht=scontent.cdninstagram.com", - "name": "Instagram Oct 22, 2017, 2:52 PM.jpeg", - "mimeType": "image/jpeg", - "id": "1631190853481371017_104680", - "thumbnail": "https://scontent.cdninstagram.com/vp/793785790f639b4d1c281f8f85b617b7/5D5CC492/t51.2885-15/e35/s150x150/22639252_183626528874474_2694579090425380864_n.jpg?_nc_ht=scontent.cdninstagram.com", - "requestPath": "1631190853481371017_104680", - "modifiedDate": "1508673124" - }, - { - "isFolder": false, - "icon": "https://scontent.cdninstagram.com/vp/1c45c002dca07b795008b734a2dadf1a/5D567634/t51.2885-15/e35/s320x320/22580990_1537478786332265_2919339273100460032_n.jpg?_nc_ht=scontent.cdninstagram.com", - "name": "Instagram Oct 17, 2017, 12:11 AM.jpeg", - "mimeType": "image/jpeg", - "id": "1627123826827378513_104680", - "thumbnail": "https://scontent.cdninstagram.com/vp/9cdda68ef82a7d5415184d24b7938aae/5D5B8E4C/t51.2885-15/e35/s150x150/22580990_1537478786332265_2919339273100460032_n.jpg?_nc_ht=scontent.cdninstagram.com", - "requestPath": "1627123826827378513_104680?carousel_id=0", - "modifiedDate": "1508188297" - }, - { - "isFolder": false, - "icon": "https://scontent.cdninstagram.com/vp/83c22b41fcf93fbf4c0c2a2164c74eba/5D6009D3/t51.2885-15/e35/s320x320/22430535_1082180631919539_4957960914784485376_n.jpg?_nc_ht=scontent.cdninstagram.com", - "name": "Instagram Oct 17, 2017, 12:11 AM 1.jpeg", - "mimeType": "image/jpeg", - "id": "1627123826827378513_1046801", - "thumbnail": "https://scontent.cdninstagram.com/vp/b1ec088d4bad03e47344f9e8742e26af/5D5CF2AB/t51.2885-15/e35/s150x150/22430535_1082180631919539_4957960914784485376_n.jpg?_nc_ht=scontent.cdninstagram.com", - "requestPath": "1627123826827378513_104680?carousel_id=1", - "modifiedDate": "1508188297" - }, - { - "isFolder": false, - "icon": "https://scontent.cdninstagram.com/vp/b4fa4fc2530639cc555702153d5142da/5D5C414B/t51.2885-15/e35/s320x320/22430474_501048343603948_3378745776992681984_n.jpg?_nc_ht=scontent.cdninstagram.com", - "name": "Instagram Oct 17, 2017, 12:11 AM 2.jpeg", - "mimeType": "image/jpeg", - "id": "1627123826827378513_1046802", - "thumbnail": "https://scontent.cdninstagram.com/vp/0d97e18dbf6176009d599a76b76b83db/5D57C4BB/t51.2885-15/e35/s150x150/22430474_501048343603948_3378745776992681984_n.jpg?_nc_ht=scontent.cdninstagram.com", - "requestPath": "1627123826827378513_104680?carousel_id=2", - "modifiedDate": "1508188297" - }, - { - "isFolder": false, - "icon": "https://scontent.cdninstagram.com/vp/c7bc86b8e610c7d9dc6642f53697ded2/5D53711A/t51.2885-15/e35/s320x320/22430500_182297355651006_8614244764025356288_n.jpg?_nc_ht=scontent.cdninstagram.com", - "name": "Instagram Oct 17, 2017, 12:11 AM 3.jpeg", - "mimeType": "image/jpeg", - "id": "1627123826827378513_1046803", - "thumbnail": "https://scontent.cdninstagram.com/vp/d87f16c611a0f28c4b49a5c14136d13c/5D39C3EA/t51.2885-15/e35/s150x150/22430500_182297355651006_8614244764025356288_n.jpg?_nc_ht=scontent.cdninstagram.com", - "requestPath": "1627123826827378513_104680?carousel_id=3", - "modifiedDate": "1508188297" - }, - { - "isFolder": false, - "icon": "https://scontent.cdninstagram.com/vp/89a31fd438a9953a17ac5687da438581/5D54979A/t51.2885-15/e35/s320x320/22637761_173098443269988_8262666012554428416_n.jpg?_nc_ht=scontent.cdninstagram.com", - "name": "Instagram Oct 17, 2017, 12:11 AM 4.jpeg", - "mimeType": "image/jpeg", - "id": "1627123826827378513_1046804", - "thumbnail": "https://scontent.cdninstagram.com/vp/2b6932601eec3e25c447510f777b227d/5D40AF6A/t51.2885-15/e35/s150x150/22637761_173098443269988_8262666012554428416_n.jpg?_nc_ht=scontent.cdninstagram.com", - "requestPath": "1627123826827378513_104680?carousel_id=4", - "modifiedDate": "1508188297" - }, - { - "isFolder": false, - "icon": "https://scontent.cdninstagram.com/vp/d6d074a714c2e6758cd1fc3732211dc0/5D5C94AD/t51.2885-15/e35/s320x320/22344026_1843951665618388_1364607905617149952_n.jpg?_nc_ht=scontent.cdninstagram.com", - "name": "Instagram Oct 10, 2017, 7:53 PM.jpeg", - "mimeType": "image/jpeg", - "id": "1622645126576609002_104680", - "thumbnail": "https://scontent.cdninstagram.com/vp/1b8dd33db144490ca9087796b16792ca/5D3DCDD5/t51.2885-15/e35/s150x150/22344026_1843951665618388_1364607905617149952_n.jpg?_nc_ht=scontent.cdninstagram.com", - "requestPath": "1622645126576609002_104680", - "modifiedDate": "1507654394" - } - ], - "folders": [], - "directories": [ - { - "id": "recent" - } - ], - "activeRow": -1, - "filterInput": "", - "isSearchVisible": false, - "didFirstRender": true, - "loading": false - }, - "Dropbox": { - "currentSelection": [], - "authenticated": false, - "files": [], - "folders": [], - "directories": [], - "activeRow": -1, - "filterInput": "", - "isSearchVisible": false - }, - "Webcam": { - "cameraReady": false - } - }, - "files": { - "uppy-instagramjan1820191139pm6jpeg-image/jpeg": { - "source": "Instagram", - "id": "uppy-instagramjan1820191139pm6jpeg-image/jpeg", - "name": "Instagram Jan 18, 2019, 11:39 PM 6.jpeg", - "extension": "jpeg", - "meta": { - "username": "John", - "license": "Creative Commons", - "name": "Instagram Jan 18, 2019, 11:39 PM 6.jpeg", - "type": "image/jpeg" - }, - "type": "image/jpeg", - "data": { - "isFolder": false, - "icon": "https://scontent.cdninstagram.com/vp/385551f9d659254e79e52e2069962abe/5D3DB99B/t51.2885-15/e35/s320x320/49541690_1326265230849388_780299101204315442_n.jpg?_nc_ht=scontent.cdninstagram.com", - "name": "Instagram Jan 18, 2019, 11:39 PM 6.jpeg", - "mimeType": "image/jpeg", - "id": "1959779810654008285_1046806", - "thumbnail": "https://scontent.cdninstagram.com/vp/39449810cc5a750d7da2ff0311986e5f/5D6F4796/t51.2885-15/e35/c0.0.1079.1079a/s150x150/49541690_1326265230849388_780299101204315442_n.jpg?_nc_ht=scontent.cdninstagram.com", - "requestPath": "1959779810654008285_104680?carousel_id=6", - "modifiedDate": "1547843980" - }, - "progress": { - "uploadStarted": 1556219160869, - "uploadComplete": true, - "percentage": 100, - "bytesUploaded": 69427, - "bytesTotal": 69427 - }, - "size": null, - "isRemote": true, - "remote": { - "companionUrl": "http://localhost:3020", - "url": "http://localhost:3020/instagram/get/1959779810654008285_104680?carousel_id=6", - "body": { - "fileId": "1959779810654008285_1046806" - }, - "providerOptions": { - "companionUrl": "http://localhost:3020", - "provider": "instagram", - "authProvider": "instagram", - "pluginId": "Instagram" - } - }, - "preview": "https://scontent.cdninstagram.com/vp/39449810cc5a750d7da2ff0311986e5f/5D6F4796/t51.2885-15/e35/c0.0.1079.1079a/s150x150/49541690_1326265230849388_780299101204315442_n.jpg?_nc_ht=scontent.cdninstagram.com", - "serverToken": "4a8616b1-58c9-464c-a5f0-450f4fac7959", - "response": { - "uploadURL": "https://master.tus.io/files/0db2bbb15785b5fbe1e04450a2ad7e27+NMjcmLga7xPuGJT6Ri65E4qN34Hck4CpgPG4r8uQdR2iyOTNDS2_hSHzROTuV.ApP.F9tLbZR04303y.X0apIHXPstqQAgOPyO1tbUKfqJ3SQ6XkX_gRfmc8hadlJK_H" - }, - "uploadURL": "https://master.tus.io/files/0db2bbb15785b5fbe1e04450a2ad7e27+NMjcmLga7xPuGJT6Ri65E4qN34Hck4CpgPG4r8uQdR2iyOTNDS2_hSHzROTuV.ApP.F9tLbZR04303y.X0apIHXPstqQAgOPyO1tbUKfqJ3SQ6XkX_gRfmc8hadlJK_H", - "isPaused": false - }, - "uppy-instagramjan1820191139pm5jpeg-image/jpeg": { - "source": "Instagram", - "id": "uppy-instagramjan1820191139pm5jpeg-image/jpeg", - "name": "Instagram Jan 18, 2019, 11:39 PM 5.jpeg", - "extension": "jpeg", - "meta": { - "username": "John", - "license": "Creative Commons", - "name": "Instagram Jan 18, 2019, 11:39 PM 5.jpeg", - "type": "image/jpeg" - }, - "type": "image/jpeg", - "data": { - "isFolder": false, - "icon": "https://scontent.cdninstagram.com/vp/e93879efc4dad02ed95f28c845b30c1b/5D6EE4D2/t51.2885-15/e35/s320x320/50024282_2330888297144674_8558997522361102927_n.jpg?_nc_ht=scontent.cdninstagram.com", - "name": "Instagram Jan 18, 2019, 11:39 PM 5.jpeg", - "mimeType": "image/jpeg", - "id": "1959779810654008285_1046805", - "thumbnail": "https://scontent.cdninstagram.com/vp/c173758a1dbcdc033973a4d9b77061fa/5D3FD13A/t51.2885-15/e35/c0.0.1079.1079a/s150x150/50024282_2330888297144674_8558997522361102927_n.jpg?_nc_ht=scontent.cdninstagram.com", - "requestPath": "1959779810654008285_104680?carousel_id=5", - "modifiedDate": "1547843980" - }, - "progress": { - "uploadStarted": 1556219160871, - "uploadComplete": true, - "percentage": 100, - "bytesUploaded": null, - "bytesTotal": null - }, - "size": null, - "isRemote": true, - "remote": { - "companionUrl": "http://localhost:3020", - "url": "http://localhost:3020/instagram/get/1959779810654008285_104680?carousel_id=5", - "body": { - "fileId": "1959779810654008285_1046805" - }, - "providerOptions": { - "companionUrl": "http://localhost:3020", - "provider": "instagram", - "authProvider": "instagram", - "pluginId": "Instagram" - } - }, - "preview": "https://scontent.cdninstagram.com/vp/c173758a1dbcdc033973a4d9b77061fa/5D3FD13A/t51.2885-15/e35/c0.0.1079.1079a/s150x150/50024282_2330888297144674_8558997522361102927_n.jpg?_nc_ht=scontent.cdninstagram.com", - "serverToken": "78105341-af5e-4576-9fc7-d0a11becdc51", - "response": { - "uploadURL": "https://master.tus.io/files/691bd69026e91a7568a240cb909139e5+gDSpFJXfEGIaPQyzAZk0OZ_y5g2fsNI2FPdNuoCQl0ka7Ch.uAXWtsUhjyB3ieH84JkHl03_h5jAXJn4X6TZRsF8yfZqVVBaYRdZbOqpZcYoBwdQ.SNFmASrukqcmYVa" - }, - "uploadURL": "https://master.tus.io/files/691bd69026e91a7568a240cb909139e5+gDSpFJXfEGIaPQyzAZk0OZ_y5g2fsNI2FPdNuoCQl0ka7Ch.uAXWtsUhjyB3ieH84JkHl03_h5jAXJn4X6TZRsF8yfZqVVBaYRdZbOqpZcYoBwdQ.SNFmASrukqcmYVa", - "isPaused": false - }, - "uppy-instagramjan1820191139pm4jpeg-image/jpeg": { - "source": "Instagram", - "id": "uppy-instagramjan1820191139pm4jpeg-image/jpeg", - "name": "Instagram Jan 18, 2019, 11:39 PM 4.jpeg", - "extension": "jpeg", - "meta": { - "username": "John", - "license": "Creative Commons", - "name": "Instagram Jan 18, 2019, 11:39 PM 4.jpeg", - "type": "image/jpeg" - }, - "type": "image/jpeg", - "data": { - "isFolder": false, - "icon": "https://scontent.cdninstagram.com/vp/9b869a8590154eb01bdb8c4d834dbc96/5D68F919/t51.2885-15/e35/s320x320/49536508_2715697688455532_3941725382632324585_n.jpg?_nc_ht=scontent.cdninstagram.com", - "name": "Instagram Jan 18, 2019, 11:39 PM 4.jpeg", - "mimeType": "image/jpeg", - "id": "1959779810654008285_1046804", - "thumbnail": "https://scontent.cdninstagram.com/vp/9d5ef7d80443c6e606dd2032bd9bbef3/5D736861/t51.2885-15/e35/s150x150/49536508_2715697688455532_3941725382632324585_n.jpg?_nc_ht=scontent.cdninstagram.com", - "requestPath": "1959779810654008285_104680?carousel_id=4", - "modifiedDate": "1547843980" - }, - "progress": { - "uploadStarted": 1556219160872, - "uploadComplete": true, - "percentage": 100, - "bytesUploaded": 79296, - "bytesTotal": 79296 - }, - "size": null, - "isRemote": true, - "remote": { - "companionUrl": "http://localhost:3020", - "url": "http://localhost:3020/instagram/get/1959779810654008285_104680?carousel_id=4", - "body": { - "fileId": "1959779810654008285_1046804" - }, - "providerOptions": { - "companionUrl": "http://localhost:3020", - "provider": "instagram", - "authProvider": "instagram", - "pluginId": "Instagram" - } - }, - "preview": "https://scontent.cdninstagram.com/vp/9d5ef7d80443c6e606dd2032bd9bbef3/5D736861/t51.2885-15/e35/s150x150/49536508_2715697688455532_3941725382632324585_n.jpg?_nc_ht=scontent.cdninstagram.com", - "serverToken": "11061e76-6fa9-4c0d-b877-c0759f077d94", - "response": { - "uploadURL": "https://master.tus.io/files/f49cc001f5447ea90c898b69b1789d96+GD3uaee_lz.07izRtBlC1E99eum2CAWHHGOgdiTDyP8DpGM31fiBIQrCzCnbjGZ2xVqad3W1S1SL5R96dRlNdNKie4uDpRrhKcW17RQBkWyKyiE5fDKe8FhEo.9JstBx" - }, - "uploadURL": "https://master.tus.io/files/f49cc001f5447ea90c898b69b1789d96+GD3uaee_lz.07izRtBlC1E99eum2CAWHHGOgdiTDyP8DpGM31fiBIQrCzCnbjGZ2xVqad3W1S1SL5R96dRlNdNKie4uDpRrhKcW17RQBkWyKyiE5fDKe8FhEo.9JstBx", - "isPaused": false - }, - "uppy-instagramjan1820191139pm3jpeg-image/jpeg": { - "source": "Instagram", - "id": "uppy-instagramjan1820191139pm3jpeg-image/jpeg", - "name": "Instagram Jan 18, 2019, 11:39 PM 3.jpeg", - "extension": "jpeg", - "meta": { - "username": "John", - "license": "Creative Commons", - "name": "Instagram Jan 18, 2019, 11:39 PM 3.jpeg", - "type": "image/jpeg" - }, - "type": "image/jpeg", - "data": { - "isFolder": false, - "icon": "https://scontent.cdninstagram.com/vp/3da7cd1f79dcecde1e871ea09cf65a87/5D737FDF/t51.2885-15/e35/s320x320/50515172_1138232866338235_1751853475314282763_n.jpg?_nc_ht=scontent.cdninstagram.com", - "name": "Instagram Jan 18, 2019, 11:39 PM 3.jpeg", - "mimeType": "image/jpeg", - "id": "1959779810654008285_1046803", - "thumbnail": "https://scontent.cdninstagram.com/vp/5c51a2dac516e575a66cf10c39615faf/5D6922A7/t51.2885-15/e35/s150x150/50515172_1138232866338235_1751853475314282763_n.jpg?_nc_ht=scontent.cdninstagram.com", - "requestPath": "1959779810654008285_104680?carousel_id=3", - "modifiedDate": "1547843980" - }, - "progress": { - "uploadStarted": 1556219160872, - "uploadComplete": true, - "percentage": 100, - "bytesUploaded": 129055, - "bytesTotal": 129055 - }, - "size": null, - "isRemote": true, - "remote": { - "companionUrl": "http://localhost:3020", - "url": "http://localhost:3020/instagram/get/1959779810654008285_104680?carousel_id=3", - "body": { - "fileId": "1959779810654008285_1046803" - }, - "providerOptions": { - "companionUrl": "http://localhost:3020", - "provider": "instagram", - "authProvider": "instagram", - "pluginId": "Instagram" - } - }, - "preview": "https://scontent.cdninstagram.com/vp/5c51a2dac516e575a66cf10c39615faf/5D6922A7/t51.2885-15/e35/s150x150/50515172_1138232866338235_1751853475314282763_n.jpg?_nc_ht=scontent.cdninstagram.com", - "serverToken": "b12c3999-1be2-4a15-86ea-530e73e6da76", - "response": { - "uploadURL": "https://master.tus.io/files/9a89b79d6ed7b6ced857a036aad43fb6+WmV6kpylmuFV1HHA3JKIgmU5wJRwyRmweOpEUjQT86Zc5YLVa_nwsmV.P4Pl4oF15rmhb5PvkukMVTKN98WQff_QGD0TjBSHDYqVNOVGuJ8_w4.A.IWOl3yRSk58rkHf" - }, - "uploadURL": "https://master.tus.io/files/9a89b79d6ed7b6ced857a036aad43fb6+WmV6kpylmuFV1HHA3JKIgmU5wJRwyRmweOpEUjQT86Zc5YLVa_nwsmV.P4Pl4oF15rmhb5PvkukMVTKN98WQff_QGD0TjBSHDYqVNOVGuJ8_w4.A.IWOl3yRSk58rkHf", - "isPaused": false - }, - "uppy-instagramfeb112019334pmjpeg-image/jpeg": { - "source": "Instagram", - "id": "uppy-instagramfeb112019334pmjpeg-image/jpeg", - "name": "Instagram Feb 11, 2019, 3:34 PM.jpeg", - "extension": "jpeg", - "meta": { - "username": "John", - "license": "Creative Commons", - "name": "Instagram Feb 11, 2019, 3:34 PM.jpeg", - "type": "image/jpeg" - }, - "type": "image/jpeg", - "data": { - "isFolder": false, - "icon": "https://scontent.cdninstagram.com/vp/420d2bdc2cb30251d7ecf8e516f7fa7d/5D55A291/t51.2885-15/e35/s320x320/50692753_2474466385958388_3994336603663016042_n.jpg?_nc_ht=scontent.cdninstagram.com", - "name": "Instagram Feb 11, 2019, 3:34 PM.jpeg", - "mimeType": "image/jpeg", - "id": "1976930126949922734_104680", - "thumbnail": "https://scontent.cdninstagram.com/vp/ff2400b726bbd3415c4a07b723615578/5D3C08E9/t51.2885-15/e35/s150x150/50692753_2474466385958388_3994336603663016042_n.jpg?_nc_ht=scontent.cdninstagram.com", - "requestPath": "1976930126949922734_104680", - "modifiedDate": "1549888457" - }, - "progress": { - "uploadStarted": 1556219160874, - "uploadComplete": true, - "percentage": 100, - "bytesUploaded": 86926, - "bytesTotal": 86926 - }, - "size": null, - "isRemote": true, - "remote": { - "companionUrl": "http://localhost:3020", - "url": "http://localhost:3020/instagram/get/1976930126949922734_104680", - "body": { - "fileId": "1976930126949922734_104680" - }, - "providerOptions": { - "companionUrl": "http://localhost:3020", - "provider": "instagram", - "authProvider": "instagram", - "pluginId": "Instagram" - } - }, - "preview": "https://scontent.cdninstagram.com/vp/ff2400b726bbd3415c4a07b723615578/5D3C08E9/t51.2885-15/e35/s150x150/50692753_2474466385958388_3994336603663016042_n.jpg?_nc_ht=scontent.cdninstagram.com", - "serverToken": "dde6723d-29f7-4983-8b42-0a9e18683872", - "response": { - "uploadURL": "https://master.tus.io/files/533f69131228bc58faef2b247af1f1bd+hHkHXy5FmiPzutH2jEfQMMHiUtae68lrmZKRenqvSSU94FcsKxurqzg10jHSvV3lFps9zdzXfbmb8VBEX6znqffM603qapJuDhfeNZkgrBxOkgKglyvQLV_9ydgaJNLE" - }, - "uploadURL": "https://master.tus.io/files/533f69131228bc58faef2b247af1f1bd+hHkHXy5FmiPzutH2jEfQMMHiUtae68lrmZKRenqvSSU94FcsKxurqzg10jHSvV3lFps9zdzXfbmb8VBEX6znqffM603qapJuDhfeNZkgrBxOkgKglyvQLV_9ydgaJNLE", - "isPaused": false - }, - "uppy-instagramjan1820191139pmjpeg-image/jpeg": { - "source": "Instagram", - "id": "uppy-instagramjan1820191139pmjpeg-image/jpeg", - "name": "Instagram Jan 18, 2019, 11:39 PM.jpeg", - "extension": "jpeg", - "meta": { - "username": "John", - "license": "Creative Commons", - "name": "Instagram Jan 18, 2019, 11:39 PM.jpeg", - "type": "image/jpeg" - }, - "type": "image/jpeg", - "data": { - "isFolder": false, - "icon": "https://scontent.cdninstagram.com/vp/78aa49ad8ccbe12b07fb20f07c1b9a1d/5D520E84/t51.2885-15/e35/s320x320/49858772_2238267119827712_38852393303322952_n.jpg?_nc_ht=scontent.cdninstagram.com", - "name": "Instagram Jan 18, 2019, 11:39 PM.jpeg", - "mimeType": "image/jpeg", - "id": "1959779810654008285_104680", - "thumbnail": "https://scontent.cdninstagram.com/vp/b3eda9daaa214951c1fb1a0f7000d8de/5D3F3F89/t51.2885-15/e35/s150x150/49858772_2238267119827712_38852393303322952_n.jpg?_nc_ht=scontent.cdninstagram.com", - "requestPath": "1959779810654008285_104680?carousel_id=0", - "modifiedDate": "1547843980" - }, - "progress": { - "uploadStarted": 1556219160874, - "uploadComplete": true, - "percentage": 100, - "bytesUploaded": 80471, - "bytesTotal": 80471 - }, - "size": null, - "isRemote": true, - "remote": { - "companionUrl": "http://localhost:3020", - "url": "http://localhost:3020/instagram/get/1959779810654008285_104680?carousel_id=0", - "body": { - "fileId": "1959779810654008285_104680" - }, - "providerOptions": { - "companionUrl": "http://localhost:3020", - "provider": "instagram", - "authProvider": "instagram", - "pluginId": "Instagram" - } - }, - "preview": "https://scontent.cdninstagram.com/vp/b3eda9daaa214951c1fb1a0f7000d8de/5D3F3F89/t51.2885-15/e35/s150x150/49858772_2238267119827712_38852393303322952_n.jpg?_nc_ht=scontent.cdninstagram.com", - "serverToken": "60675af3-fec5-45d8-87d6-30363a0534fa", - "response": { - "uploadURL": "https://master.tus.io/files/1f4f0dabad36e2995d4fde177ce64d0c+C.z6wtvRCGwt5hr4hUpbLE3NrduyiOiDn55wE8e81j88CRvceybDQ04iOQhlfl817wlPa0f2JU5aPjhZlp.pA4sBKPqLOlDMAkakwsVIGR4IW5nLYRoRGuauiJjYDMW." - }, - "uploadURL": "https://master.tus.io/files/1f4f0dabad36e2995d4fde177ce64d0c+C.z6wtvRCGwt5hr4hUpbLE3NrduyiOiDn55wE8e81j88CRvceybDQ04iOQhlfl817wlPa0f2JU5aPjhZlp.pA4sBKPqLOlDMAkakwsVIGR4IW5nLYRoRGuauiJjYDMW.", - "isPaused": false - }, - "uppy-instagramjan1820191139pm1jpeg-image/jpeg": { - "source": "Instagram", - "id": "uppy-instagramjan1820191139pm1jpeg-image/jpeg", - "name": "Instagram Jan 18, 2019, 11:39 PM 1.jpeg", - "extension": "jpeg", - "meta": { - "username": "John", - "license": "Creative Commons", - "name": "Instagram Jan 18, 2019, 11:39 PM 1.jpeg", - "type": "image/jpeg" - }, - "type": "image/jpeg", - "data": { - "isFolder": false, - "icon": "https://scontent.cdninstagram.com/vp/ec36a8b8b7db546abeb3cdcf12b52ba5/5D77DC5F/t51.2885-15/e35/s320x320/49858316_1974473399524283_2231924729468134373_n.jpg?_nc_ht=scontent.cdninstagram.com", - "name": "Instagram Jan 18, 2019, 11:39 PM 1.jpeg", - "mimeType": "image/jpeg", - "id": "1959779810654008285_1046801", - "thumbnail": "https://scontent.cdninstagram.com/vp/d8ef3dd687b6aab396cae9284226c7df/5D711727/t51.2885-15/e35/s150x150/49858316_1974473399524283_2231924729468134373_n.jpg?_nc_ht=scontent.cdninstagram.com", - "requestPath": "1959779810654008285_104680?carousel_id=1", - "modifiedDate": "1547843980" - }, - "progress": { - "uploadStarted": 1556219160876, - "uploadComplete": false, - "percentage": 15, - "bytesUploaded": 12561.5, - "bytesTotal": 80471 - }, - "size": null, - "isRemote": true, - "remote": { - "companionUrl": "http://localhost:3020", - "url": "http://localhost:3020/instagram/get/1959779810654008285_104680?carousel_id=1", - "body": { - "fileId": "1959779810654008285_1046801" - }, - "providerOptions": { - "companionUrl": "http://localhost:3020", - "provider": "instagram", - "authProvider": "instagram", - "pluginId": "Instagram" - } - }, - "preview": "https://scontent.cdninstagram.com/vp/d8ef3dd687b6aab396cae9284226c7df/5D711727/t51.2885-15/e35/s150x150/49858316_1974473399524283_2231924729468134373_n.jpg?_nc_ht=scontent.cdninstagram.com", - "serverToken": "d6f75847-6685-456e-9db6-c345f32749af", - "isPaused": true - }, - "uppy-instagramjan1820191139pm2jpeg-image/jpeg": { - "source": "Instagram", - "id": "uppy-instagramjan1820191139pm2jpeg-image/jpeg", - "name": "Instagram Jan 18, 2019, 11:39 PM 2.jpeg", - "extension": "jpeg", - "meta": { - "username": "John", - "license": "Creative Commons", - "name": "Instagram Jan 18, 2019, 11:39 PM 2.jpeg", - "type": "image/jpeg" - }, - "type": "image/jpeg", - "data": { - "isFolder": false, - "icon": "https://scontent.cdninstagram.com/vp/3055f2ae78d775fc031654d20e791ebc/5D51235E/t51.2885-15/e35/s320x320/49933915_184580175831450_4288362971970794931_n.jpg?_nc_ht=scontent.cdninstagram.com", - "name": "Instagram Jan 18, 2019, 11:39 PM 2.jpeg", - "mimeType": "image/jpeg", - "id": "1959779810654008285_1046802", - "thumbnail": "https://scontent.cdninstagram.com/vp/3d1b5aa16b8d64bc7c14ae1e3b13dfbe/5D5DD553/t51.2885-15/e35/c0.0.1079.1079a/s150x150/49933915_184580175831450_4288362971970794931_n.jpg?_nc_ht=scontent.cdninstagram.com", - "requestPath": "1959779810654008285_104680?carousel_id=2", - "modifiedDate": "1547843980" - }, - "progress": { - "uploadStarted": 1556219160878, - "uploadComplete": true, - "percentage": 100, - "bytesUploaded": 111226, - "bytesTotal": 111226 - }, - "size": null, - "isRemote": true, - "remote": { - "companionUrl": "http://localhost:3020", - "url": "http://localhost:3020/instagram/get/1959779810654008285_104680?carousel_id=2", - "body": { - "fileId": "1959779810654008285_1046802" - }, - "providerOptions": { - "companionUrl": "http://localhost:3020", - "provider": "instagram", - "authProvider": "instagram", - "pluginId": "Instagram" - } - }, - "preview": "https://scontent.cdninstagram.com/vp/3d1b5aa16b8d64bc7c14ae1e3b13dfbe/5D5DD553/t51.2885-15/e35/c0.0.1079.1079a/s150x150/49933915_184580175831450_4288362971970794931_n.jpg?_nc_ht=scontent.cdninstagram.com", - "serverToken": "92ae2486-90f0-4e2f-82f9-285ad87e2c5a", - "response": { - "uploadURL": "https://master.tus.io/files/c83164658faa4fdd7a12dc9e6044510f+YJKdoXIXl7PjA_UXhQ.PDqPhnk4A_O_LWm.3qau9X8kzHXg7dgI.igu5n_yPVBG0PezDPi2b8fSjBfc2KZjX3qUZxST9pb0iQP20_wKPax9Y4slQRQr7qpiUugFTljbA" - }, - "uploadURL": "https://master.tus.io/files/c83164658faa4fdd7a12dc9e6044510f+YJKdoXIXl7PjA_UXhQ.PDqPhnk4A_O_LWm.3qau9X8kzHXg7dgI.igu5n_yPVBG0PezDPi2b8fSjBfc2KZjX3qUZxST9pb0iQP20_wKPax9Y4slQRQr7qpiUugFTljbA", - "isPaused": false - } - }, - "currentUploads": { - "cjux0phjn00012a5vzjw0buly": { - "fileIDs": [ - "uppy-instagramjan1820191139pm6jpeg-image/jpeg", - "uppy-instagramjan1820191139pm5jpeg-image/jpeg", - "uppy-instagramjan1820191139pm4jpeg-image/jpeg", - "uppy-instagramjan1820191139pm3jpeg-image/jpeg", - "uppy-instagramfeb112019334pmjpeg-image/jpeg", - "uppy-instagramjan1820191139pmjpeg-image/jpeg", - "uppy-instagramjan1820191139pm1jpeg-image/jpeg", - "uppy-instagramjan1820191139pm2jpeg-image/jpeg" - ], - "step": 0, - "result": {} - } - }, - "allowNewUpload": true, - "capabilities": { - "uploadProgress": true, - "individualCancellation": true, - "resumableUploads": true - }, - "totalProgress": 1328, - "meta": { - "username": "John", - "license": "Creative Commons" - }, - "info": { - "isHidden": true, - "type": "info", - "message": "" - }, - "error": null -} diff --git a/e2e/cypress/fixtures/images/baboon.png b/e2e/cypress/fixtures/images/baboon.png deleted file mode 100644 index a8556f212..000000000 Binary files a/e2e/cypress/fixtures/images/baboon.png and /dev/null differ diff --git a/e2e/cypress/fixtures/images/car.jpg b/e2e/cypress/fixtures/images/car.jpg deleted file mode 100644 index 205d2e565..000000000 Binary files a/e2e/cypress/fixtures/images/car.jpg and /dev/null differ diff --git a/e2e/cypress/fixtures/images/cat-symbolic-link b/e2e/cypress/fixtures/images/cat-symbolic-link deleted file mode 120000 index d9dd9f211..000000000 --- a/e2e/cypress/fixtures/images/cat-symbolic-link +++ /dev/null @@ -1 +0,0 @@ -./cat.jpg \ No newline at end of file diff --git a/e2e/cypress/fixtures/images/cat-symbolic-link.jpg b/e2e/cypress/fixtures/images/cat-symbolic-link.jpg deleted file mode 120000 index d9dd9f211..000000000 --- a/e2e/cypress/fixtures/images/cat-symbolic-link.jpg +++ /dev/null @@ -1 +0,0 @@ -./cat.jpg \ No newline at end of file diff --git a/e2e/cypress/fixtures/images/cat.jpg b/e2e/cypress/fixtures/images/cat.jpg deleted file mode 100644 index 671a1fd5a..000000000 Binary files a/e2e/cypress/fixtures/images/cat.jpg and /dev/null differ diff --git a/e2e/cypress/fixtures/images/invalid.png b/e2e/cypress/fixtures/images/invalid.png deleted file mode 100644 index 6c288100a..000000000 Binary files a/e2e/cypress/fixtures/images/invalid.png and /dev/null differ diff --git a/e2e/cypress/fixtures/images/kodim23.png b/e2e/cypress/fixtures/images/kodim23.png deleted file mode 100644 index ff22e8373..000000000 Binary files a/e2e/cypress/fixtures/images/kodim23.png and /dev/null differ diff --git a/e2e/cypress/fixtures/images/traffic.jpg b/e2e/cypress/fixtures/images/traffic.jpg deleted file mode 100644 index 39f7cb792..000000000 Binary files a/e2e/cypress/fixtures/images/traffic.jpg and /dev/null differ diff --git a/e2e/cypress/fixtures/images/١٠ كم мест для Нью-Йорке.pdf b/e2e/cypress/fixtures/images/١٠ كم мест для Нью-Йорке.pdf deleted file mode 100644 index 9ec444cfa..000000000 --- a/e2e/cypress/fixtures/images/١٠ كم мест для Нью-Йорке.pdf +++ /dev/null @@ -1,5 +0,0 @@ -%PDF-1. -1 0 obj<>endobj -2 0 obj<>endobj -3 0 obj<>endobj -trailer <> \ No newline at end of file diff --git a/e2e/cypress/integration/dashboard-aws-multipart.spec.ts b/e2e/cypress/integration/dashboard-aws-multipart.spec.ts deleted file mode 100644 index 70f384a99..000000000 --- a/e2e/cypress/integration/dashboard-aws-multipart.spec.ts +++ /dev/null @@ -1,219 +0,0 @@ -describe('Dashboard with @uppy/aws-s3-multipart', () => { - beforeEach(() => { - cy.visit('/dashboard-aws-multipart') - cy.get('.uppy-Dashboard-input:first').as('file-input') - cy.intercept({ method: 'POST', pathname: '/s3/multipart' }).as('post') - cy.intercept({ method: 'GET', pathname: '/s3/multipart/*/1' }).as('get') - cy.intercept({ method: 'PUT' }).as('put') - }) - - it('should upload cat image successfully', () => { - cy.get('@file-input').selectFile('cypress/fixtures/images/cat.jpg', { - force: true, - }) - - cy.get('.uppy-StatusBar-actionBtn--upload').click() - cy.wait(['@post', '@get', '@put']) - cy.get('.uppy-StatusBar-statusPrimary').should('contain', 'Complete') - }) - it('should upload Russian poem image successfully', () => { - const fileName = '١٠ كم мест для Нью-Йорке.pdf' - cy.get('@file-input').selectFile(`cypress/fixtures/images/${fileName}`, { - force: true, - }) - - cy.get('.uppy-StatusBar-actionBtn--upload').click() - cy.wait(['@post', '@get', '@put']) - cy.get('.uppy-Dashboard-Item-name').should('contain', fileName) - cy.get('.uppy-StatusBar-statusPrimary').should('contain', 'Complete') - }) - - it('should handle retry request gracefully', () => { - cy.get('@file-input').selectFile('cypress/fixtures/images/cat.jpg', { - force: true, - }) - - cy.intercept('POST', '/s3/multipart', { - forceNetworkError: true, - times: 1, - }).as('createMultipartUpload-fails') - cy.get('.uppy-StatusBar-actionBtn--upload').click() - cy.wait(['@createMultipartUpload-fails']) - cy.get('.uppy-StatusBar-statusPrimary').should('contain', 'Upload failed') - - cy.intercept('POST', '/s3/multipart', { - statusCode: 200, - times: 1, - body: JSON.stringify({ - key: 'mocked-key-attempt1', - uploadId: 'mocked-uploadId-attempt1', - }), - }).as('createMultipartUpload-attempt1') - cy.intercept( - 'GET', - '/s3/multipart/mocked-uploadId-attempt1/1?key=mocked-key-attempt1', - { forceNetworkError: true }, - ).as('signPart-fails') - cy.intercept( - 'DELETE', - '/s3/multipart/mocked-uploadId-attempt1?key=mocked-key-attempt1', - { statusCode: 200, body: '{}' }, - ).as('abortAttempt-1') - cy.get('.uppy-StatusBar-actions > .uppy-c-btn').click() - cy.wait([ - '@createMultipartUpload-attempt1', - '@signPart-fails', - '@abortAttempt-1', - ]) - cy.get('.uppy-StatusBar-statusPrimary').should('contain', 'Upload failed') - - cy.intercept('POST', '/s3/multipart', { - statusCode: 200, - times: 1, - body: JSON.stringify({ - key: 'mocked-key-attempt2', - uploadId: 'mocked-uploadId-attempt2', - }), - }).as('createMultipartUpload-attempt2') - cy.intercept( - 'GET', - '/s3/multipart/mocked-uploadId-attempt2/1?key=mocked-key-attempt2', - { - statusCode: 200, - headers: { - ETag: 'W/"222-GXE2wLoMKDihw3wxZFH1APdUjHM"', - }, - body: JSON.stringify({ url: '/put-fail', expires: 8 }), - }, - ).as('signPart-toFail') - cy.intercept( - 'DELETE', - '/s3/multipart/mocked-uploadId-attempt2?key=mocked-key-attempt2', - { statusCode: 200, body: '{}' }, - ).as('abortAttempt-2') - cy.intercept('PUT', '/put-fail', { forceNetworkError: true }).as( - 'put-fails', - ) - cy.get('.uppy-StatusBar-actions > .uppy-c-btn').click() - cy.wait([ - '@createMultipartUpload-attempt2', - '@signPart-toFail', - ...Array(5).fill('@put-fails'), - '@abortAttempt-2', - ]) - cy.get('.uppy-StatusBar-statusPrimary').should('contain', 'Upload failed') - - cy.intercept( - 'GET', - '/s3/multipart/mocked-uploadId-attempt2/1?key=mocked-key-attempt2', - { - statusCode: 200, - headers: { - ETag: 'ETag-attempt2', - }, - body: JSON.stringify({ url: '/put-success-attempt2', expires: 8 }), - }, - ).as('signPart-attempt2') - cy.intercept('PUT', '/put-success-attempt2', { - statusCode: 200, - headers: { - ETag: 'ETag-attempt2', - }, - }).as('put-attempt2') - cy.intercept( - 'POST', - '/s3/multipart/mocked-uploadId-attempt2/complete?key=mocked-key-attempt2', - { forceNetworkError: true }, - ).as('completeMultipartUpload-fails') - cy.get('.uppy-StatusBar-actions > .uppy-c-btn').click() - cy.wait([ - '@createMultipartUpload-attempt2', - '@signPart-attempt2', - '@put-attempt2', - '@completeMultipartUpload-fails', - '@abortAttempt-2', - ]) - cy.get('.uppy-StatusBar-statusPrimary').should('contain', 'Upload failed') - - cy.intercept('POST', '/s3/multipart', { - statusCode: 200, - times: 1, - body: JSON.stringify({ - key: 'mocked-key-attempt3', - uploadId: 'mocked-uploadId-attempt3', - }), - }).as('createMultipartUpload-attempt3') - let intercepted = 0 - cy.intercept( - 'GET', - '/s3/multipart/mocked-uploadId-attempt3/1?key=mocked-key-attempt3', - (req) => { - if (intercepted++ < 2) { - // Ensure that Uppy can recover from at least 2 network errors at this stage. - req.destroy() - return - } - req.reply({ - statusCode: 200, - headers: { - ETag: 'ETag-attempt3', - }, - body: JSON.stringify({ url: '/put-success-attempt3', expires: 8 }), - }) - }, - ).as('signPart-attempt3') - cy.intercept('PUT', '/put-success-attempt3', { - statusCode: 200, - headers: { - ETag: 'ETag-attempt3', - }, - }).as('put-attempt3') - cy.intercept( - 'POST', - '/s3/multipart/mocked-uploadId-attempt3/complete?key=mocked-key-attempt3', - { - statusCode: 200, - body: JSON.stringify({ - location: 'someLocation', - }), - }, - ).as('completeMultipartUpload-attempt3') - cy.get('.uppy-StatusBar-actions > .uppy-c-btn').click() - cy.wait([ - '@createMultipartUpload-attempt3', - ...Array(3).fill('@signPart-attempt3'), - '@put-attempt3', - '@completeMultipartUpload-attempt3', - ]) - cy.get('.uppy-StatusBar-statusPrimary').should('contain', 'Complete') - }) - - it('should complete when resuming after pause', () => { - cy.get('@file-input').selectFile( - [ - 'cypress/fixtures/images/cat.jpg', - 'cypress/fixtures/images/traffic.jpg', - ], - { force: true }, - ) - cy.get('.uppy-StatusBar-actionBtn--upload').click() - - cy.wait('@post') - - cy.get('button[data-cy=togglePauseResume]').click() - // eslint-disable-next-line cypress/no-unnecessary-waiting - cy.wait(300) // Wait an arbitrary amount of time as a user would do. - cy.get('button[data-cy=togglePauseResume]').click() - - cy.wait('@get') - - cy.get('button[data-cy=togglePauseResume]').click() - // eslint-disable-next-line cypress/no-unnecessary-waiting - cy.wait(300) // Wait an arbitrary amount of time as a user would do. - cy.get('button[data-cy=togglePauseResume]').click() - - cy.wait(['@get', '@put']) - - cy.get('.uppy-StatusBar-statusPrimary').should('contain', 'Complete') - }) -}) diff --git a/e2e/cypress/integration/dashboard-aws.spec.ts b/e2e/cypress/integration/dashboard-aws.spec.ts deleted file mode 100644 index 0b39f3fba..000000000 --- a/e2e/cypress/integration/dashboard-aws.spec.ts +++ /dev/null @@ -1,18 +0,0 @@ -describe('Dashboard with @uppy/aws-s3', () => { - beforeEach(() => { - cy.visit('/dashboard-aws') - cy.get('.uppy-Dashboard-input:first').as('file-input') - cy.intercept({ method: 'GET', pathname: '/s3/params' }).as('get') - cy.intercept({ method: 'POST' }).as('post') - }) - - it('should upload cat image successfully', () => { - cy.get('@file-input').selectFile('cypress/fixtures/images/cat.jpg', { - force: true, - }) - - cy.get('.uppy-StatusBar-actionBtn--upload').click() - cy.wait(['@post', '@get']) - cy.get('.uppy-StatusBar-statusPrimary').should('contain', 'Complete') - }) -}) diff --git a/e2e/cypress/integration/dashboard-compressor.spec.ts b/e2e/cypress/integration/dashboard-compressor.spec.ts deleted file mode 100644 index 84e5ab8cf..000000000 --- a/e2e/cypress/integration/dashboard-compressor.spec.ts +++ /dev/null @@ -1,63 +0,0 @@ -function uglierBytes(text) { - const KB = 2 ** 10 - const MB = KB * KB - - if (text.endsWith(' KB')) { - return Number(text.slice(0, -3)) * KB - } - - if (text.endsWith(' MB')) { - return Number(text.slice(0, -3)) * MB - } - - if (text.endsWith(' B')) { - return Number(text.slice(0, -2)) - } - - throw new Error( - `Not what the computer thinks a human-readable size string look like: ${text}`, - ) -} - -describe('dashboard-compressor', () => { - beforeEach(() => { - cy.visit('/dashboard-compressor') - cy.get('.uppy-Dashboard-input:first').as('file-input') - }) - - it('should compress images', () => { - const sizeBeforeCompression = [] - - cy.get('@file-input').selectFile( - [ - 'cypress/fixtures/images/cat.jpg', - 'cypress/fixtures/images/traffic.jpg', - ], - { force: true }, - ) - - cy.get('.uppy-Dashboard-Item-statusSize').each((element) => { - const text = element.text() - sizeBeforeCompression.push(uglierBytes(text)) - }) - - cy.window().then(({ uppy }) => { - uppy.on('preprocess-complete', (file) => { - expect(file.extension).to.equal('webp') - expect(file.type).to.equal('image/webp') - - cy.get('.uppy-Dashboard-Item-statusSize').should((elements) => { - expect(elements).to.have.length(sizeBeforeCompression.length) - - for (let i = 0; i < elements.length; i++) { - expect(sizeBeforeCompression[i]).to.be.greaterThan( - uglierBytes(elements[i].textContent), - ) - } - }) - }) - - cy.get('.uppy-StatusBar-actionBtn--upload').click() - }) - }) -}) diff --git a/e2e/cypress/integration/dashboard-transloadit.spec.ts b/e2e/cypress/integration/dashboard-transloadit.spec.ts deleted file mode 100644 index faee0a33d..000000000 --- a/e2e/cypress/integration/dashboard-transloadit.spec.ts +++ /dev/null @@ -1,373 +0,0 @@ -import Uppy from '@uppy/core' -import Transloadit from '@uppy/transloadit' - -function getPlugin(uppy: Uppy) { - return uppy.getPlugin>('Transloadit')! -} - -describe('Dashboard with Transloadit', () => { - beforeEach(() => { - cy.visit('/dashboard-transloadit') - cy.get('.uppy-Dashboard-input:first').as('file-input') - }) - - it('should upload all files as a single assembly with UppyFile metadata in Upload-Metadata', () => { - cy.intercept({ path: '/assemblies', method: 'POST' }).as('createAssembly') - - cy.get('@file-input').selectFile( - [ - 'cypress/fixtures/images/cat.jpg', - 'cypress/fixtures/images/traffic.jpg', - ], - { force: true }, - ) - - cy.window().then(({ uppy }) => { - // Set metadata on all files - uppy.setMeta({ sharedMetaProperty: 'bar' }) - const [file1, file2] = uppy.getFiles() - // Set unique metdata per file as before that's how we determined to create multiple assemblies - uppy.setFileMeta(file1.id, { one: 'one' }) - uppy.setFileMeta(file2.id, { two: 'two' }) - - cy.get('.uppy-StatusBar-actionBtn--upload').click() - - cy.intercept('POST', '/resumable/*', (req) => { - expect(req.headers['upload-metadata']).to.include('sharedMetaProperty') - req.continue() - }) - - cy.wait(['@createAssembly']).then(() => { - cy.get('.uppy-StatusBar-statusPrimary').should('contain', 'Complete') - // should only create one assembly - cy.get('@createAssembly.all').should('have.length', 1) - }) - }) - }) - - it('should close assembly when cancelled', () => { - cy.intercept({ path: '/resumable/*', method: 'POST' }).as('tusCreate') - cy.intercept({ path: '/assemblies', method: 'POST' }).as('createAssemblies') - cy.intercept({ path: '/assemblies/*', method: 'DELETE' }).as('delete') - cy.intercept({ path: '/resumable/files/*', method: 'DELETE' }).as( - 'tusDelete', - ) - - cy.window().then(({ uppy }) => { - cy.get('@file-input').selectFile( - [ - 'cypress/fixtures/images/cat.jpg', - 'cypress/fixtures/images/traffic.jpg', - 'cypress/fixtures/images/car.jpg', - ], - { force: true }, - ) - cy.get('.uppy-StatusBar-actionBtn--upload').click() - - cy.wait(['@createAssemblies', '@tusCreate']).then(() => { - const { assembly } = getPlugin(uppy) - - expect(assembly.closed).to.be.false - - uppy.cancelAll() - - cy.wait(['@delete', '@tusDelete']).then(() => { - expect(assembly.closed).to.be.true - }) - }) - }) - }) - - it('should not emit error if upload is cancelled right away', () => { - cy.intercept({ path: '/assemblies', method: 'POST' }).as('createAssemblies') - - cy.get('@file-input').selectFile('cypress/fixtures/images/cat.jpg', { - force: true, - }) - cy.get('.uppy-StatusBar-actionBtn--upload').click() - - const handler = cy.spy() - - cy.window().then(({ uppy }) => { - const { files } = uppy.getState() - uppy.on('upload-error', handler) - - const [fileID] = Object.keys(files) - uppy.removeFile(fileID) - uppy.removeFile(fileID) - cy.wait('@createAssemblies').then(() => expect(handler).not.to.be.called) - }) - }) - - it('should not re-use erroneous tus keys', () => { - function createAssemblyStatus({ - message, - assembly_id, - bytes_expected, - ...other - }) { - return { - message, - assembly_id, - parent_id: null, - account_id: 'deadbeef', - account_name: 'foo', - account_slug: 'foo', - template_id: null, - template_name: null, - instance: 'test.transloadit.com', - assembly_url: `http://api2.test.transloadit.com/assemblies/${assembly_id}`, - assembly_ssl_url: `https://api2-test.transloadit.com/assemblies/${assembly_id}`, - uppyserver_url: 'https://api2-test.transloadit.com/companion/', - companion_url: 'https://api2-test.transloadit.com/companion/', - websocket_url: 'about:blank', - tus_url: 'https://api2-test.transloadit.com/resumable/files/', - bytes_received: 0, - bytes_expected, - upload_duration: 0.162, - client_agent: null, - client_ip: null, - client_referer: null, - transloadit_client: - 'uppy-core:3.2.0,uppy-transloadit:3.1.3,uppy-tus:3.1.0,uppy-dropbox:3.1.1,uppy-box:2.1.1,uppy-facebook:3.1.1,uppy-google-drive:3.1.1,uppy-google-photos:0.0.1,uppy-instagram:3.1.1,uppy-onedrive:3.1.1,uppy-zoom:2.1.1,uppy-url:3.3.1', - start_date: new Date().toISOString(), - upload_meta_data_extracted: false, - warnings: [], - is_infinite: false, - has_dupe_jobs: false, - execution_start: null, - execution_duration: null, - queue_duration: 0.009, - jobs_queue_duration: 0, - notify_start: null, - notify_url: null, - notify_response_code: null, - notify_response_data: null, - notify_duration: null, - last_job_completed: null, - fields: {}, - running_jobs: [], - bytes_usage: 0, - executing_jobs: [], - started_jobs: [], - parent_assembly_status: null, - params: '{}', - template: null, - merged_params: '{}', - expected_tus_uploads: 1, - started_tus_uploads: 0, - finished_tus_uploads: 0, - tus_uploads: [], - uploads: [], - results: {}, - build_id: '4765326956', - status_endpoint: `https://api2-test.transloadit.com/assemblies/${assembly_id}`, - ...other, - } - } - cy.get('@file-input').selectFile(['cypress/fixtures/images/cat.jpg'], { - force: true, - }) - - // SETUP for FIRST ATTEMPT (error response from Transloadit backend) - const assemblyIDAttempt1 = '500e56004f4347a288194edd7c7a0ae1' - const tusIDAttempt1 = 'a9daed4af4981880faf29b0e9596a14d' - cy.intercept('POST', 'https://api2.transloadit.com/assemblies', { - statusCode: 200, - body: JSON.stringify( - createAssemblyStatus({ - ok: 'ASSEMBLY_UPLOADING', - message: 'The Assembly is still in the process of being uploaded.', - assembly_id: assemblyIDAttempt1, - bytes_expected: 263871, - }), - ), - }).as('createAssembly') - - cy.intercept('POST', 'https://api2-test.transloadit.com/resumable/files/', { - statusCode: 201, - headers: { - Location: `https://api2-test.transloadit.com/resumable/files/${tusIDAttempt1}`, - }, - times: 1, - }).as('tusCall') - cy.intercept( - 'PATCH', - `https://api2-test.transloadit.com/resumable/files/${tusIDAttempt1}`, - { - statusCode: 204, - headers: { - 'Upload-Length': '263871', - 'Upload-Offset': '263871', - }, - times: 1, - }, - ) - cy.intercept( - 'HEAD', - `https://api2-test.transloadit.com/resumable/files/${tusIDAttempt1}`, - { statusCode: 204 }, - ) - - cy.intercept( - 'GET', - `https://api2-test.transloadit.com/assemblies/${assemblyIDAttempt1}`, - { - statusCode: 200, - body: JSON.stringify( - createAssemblyStatus({ - error: 'INVALID_FILE_META_DATA', - http_code: 400, - message: 'Whatever error message from Transloadit backend', - reason: 'Whatever reason', - msg: 'Whatever error from Transloadit backend', - assembly_id: '500e56004f4347a288194edd7c7a0ae1', - bytes_expected: 263871, - }), - ), - }, - ).as('failureReported') - - cy.intercept('POST', 'https://transloaditstatus.com/client_error', { - statusCode: 200, - body: '{}', - }) - - // FIRST ATTEMPT - cy.get('.uppy-StatusBar-actionBtn--upload').click() - cy.wait(['@createAssembly', '@tusCall', '@failureReported']) - cy.get('.uppy-StatusBar-statusPrimary').should('contain', 'Upload failed') - - // SETUP for SECOND ATTEMPT - const assemblyIDAttempt2 = '6a3fa40e527d4d73989fce678232a5e1' - const tusIDAttempt2 = 'b8ebed4af4981880faf29b0e9596b25e' - cy.intercept('POST', 'https://api2.transloadit.com/assemblies', { - statusCode: 200, - body: JSON.stringify( - createAssemblyStatus({ - ok: 'ASSEMBLY_UPLOADING', - message: 'The Assembly is still in the process of being uploaded.', - assembly_id: assemblyIDAttempt2, - tus_url: 'https://api2-test.transloadit.com/resumable/files/attempt2', - bytes_expected: 263871, - }), - ), - }).as('createAssembly-attempt2') - - cy.intercept( - 'POST', - 'https://api2-test.transloadit.com/resumable/files/attempt2', - { - statusCode: 201, - headers: { - 'Upload-Length': '263871', - 'Upload-Offset': '0', - Location: `https://api2-test.transloadit.com/resumable/files/${tusIDAttempt2}`, - }, - times: 1, - }, - ).as('tusCall-attempt2') - - cy.intercept( - 'PATCH', - `https://api2-test.transloadit.com/resumable/files/${tusIDAttempt2}`, - { - statusCode: 204, - headers: { - 'Upload-Length': '263871', - 'Upload-Offset': '263871', - 'Tus-Resumable': '1.0.0', - }, - times: 1, - }, - ) - cy.intercept( - 'HEAD', - `https://api2-test.transloadit.com/resumable/files/${tusIDAttempt2}`, - { statusCode: 204 }, - ) - - cy.intercept( - 'GET', - `https://api2-test.transloadit.com/assemblies/${assemblyIDAttempt2}`, - { - statusCode: 200, - body: JSON.stringify( - createAssemblyStatus({ - ok: 'ASSEMBLY_COMPLETED', - http_code: 200, - message: 'The Assembly was successfully completed.', - assembly_id: assemblyIDAttempt2, - bytes_received: 263871, - bytes_expected: 263871, - }), - ), - }, - ).as('assemblyCompleted-attempt2') - - // SECOND ATTEMPT - cy.get('.uppy-StatusBar-actions > .uppy-c-btn').click() - cy.wait([ - '@createAssembly-attempt2', - '@tusCall-attempt2', - '@assemblyCompleted-attempt2', - ]) - }) - - it('should complete on retry', () => { - cy.intercept('/assemblies/*').as('assemblies') - cy.intercept('/resumable/*').as('resumable') - - cy.get('@file-input').selectFile( - [ - 'cypress/fixtures/images/cat.jpg', - 'cypress/fixtures/images/traffic.jpg', - ], - { force: true }, - ) - - cy.intercept('POST', 'https://transloaditstatus.com/client_error', { - statusCode: 200, - body: '{}', - }) - - cy.intercept( - { method: 'POST', pathname: '/assemblies', times: 1 }, - { statusCode: 500, body: {} }, - ).as('failedAssemblyCreation') - - cy.get('.uppy-StatusBar-actionBtn--upload').click() - cy.wait('@failedAssemblyCreation') - - cy.get('button[data-cy=retry]').click() - - cy.wait(['@assemblies', '@resumable']) - - cy.get('.uppy-StatusBar-statusPrimary').should('contain', 'Complete') - }) - - it('should complete when resuming after pause', () => { - cy.intercept({ path: '/assemblies', method: 'POST' }).as('createAssemblies') - cy.intercept('/resumable/*').as('resumable') - - cy.get('@file-input').selectFile( - [ - 'cypress/fixtures/images/cat.jpg', - 'cypress/fixtures/images/traffic.jpg', - ], - { force: true }, - ) - cy.get('.uppy-StatusBar-actionBtn--upload').click() - - cy.wait('@createAssemblies') - - cy.get('button[data-cy=togglePauseResume]').click() - // eslint-disable-next-line cypress/no-unnecessary-waiting - cy.wait(300) // Wait an arbitrary amount of time as a user would do. - cy.get('button[data-cy=togglePauseResume]').click() - - cy.wait('@resumable') - - cy.get('.uppy-StatusBar-statusPrimary').should('contain', 'Complete') - }) -}) diff --git a/e2e/cypress/integration/dashboard-tus.spec.ts b/e2e/cypress/integration/dashboard-tus.spec.ts deleted file mode 100644 index 3e11cb58c..000000000 --- a/e2e/cypress/integration/dashboard-tus.spec.ts +++ /dev/null @@ -1,52 +0,0 @@ -import { - runRemoteUrlImageUploadTest, - runRemoteUnsplashUploadTest, -} from './reusable-tests.ts' - -// NOTE: we have to use different files to upload per test -// because we are uploading to https://tusd.tusdemo.net, -// constantly uploading the same images gives a different cached result (or something). -describe('Dashboard with Tus', () => { - beforeEach(() => { - cy.visit('/dashboard-tus') - cy.get('.uppy-Dashboard-input:first').as('file-input') - cy.intercept('/files/*').as('tus') - cy.intercept({ method: 'POST', pathname: '/files' }).as('post') - cy.intercept({ method: 'PATCH', pathname: '/files/*' }).as('patch') - }) - - it('should upload cat image successfully', () => { - cy.get('@file-input').selectFile('cypress/fixtures/images/cat.jpg', { - force: true, - }) - - cy.get('.uppy-StatusBar-actionBtn--upload').click() - cy.wait(['@post', '@patch']).then(() => { - cy.get('.uppy-StatusBar-statusPrimary').should('contain', 'Complete') - }) - }) - - it('should start exponential backoff when receiving HTTP 429', () => { - cy.get('@file-input').selectFile('cypress/fixtures/images/baboon.png', { - force: true, - }) - - cy.intercept( - { method: 'PATCH', pathname: '/files/*', times: 2 }, - { statusCode: 429, body: {} }, - ).as('patch') - - cy.get('.uppy-StatusBar-actionBtn--upload').click() - cy.wait('@tus').then(() => { - cy.get('.uppy-StatusBar-statusPrimary').should('contain', 'Complete') - }) - }) - - it('should upload remote image with URL plugin', () => { - runRemoteUrlImageUploadTest() - }) - - it('should upload remote image with Unsplash plugin', () => { - runRemoteUnsplashUploadTest() - }) -}) diff --git a/e2e/cypress/integration/dashboard-ui.spec.ts b/e2e/cypress/integration/dashboard-ui.spec.ts deleted file mode 100644 index 31439e685..000000000 --- a/e2e/cypress/integration/dashboard-ui.spec.ts +++ /dev/null @@ -1,56 +0,0 @@ -describe('dashboard-ui', () => { - beforeEach(() => { - cy.visit('/dashboard-ui') - cy.get('.uppy-Dashboard-input:first').as('file-input') - cy.get('.uppy-Dashboard-AddFiles').as('drop-target') - }) - - it('should not throw when calling uppy.destroy()', () => { - cy.get('@file-input').selectFile( - [ - 'cypress/fixtures/images/cat.jpg', - 'cypress/fixtures/images/traffic.jpg', - ], - { force: true }, - ) - - cy.window().then(({ uppy }) => { - expect(uppy.destroy()).to.not.throw - }) - }) - - it('should render thumbnails', () => { - cy.get('@file-input').selectFile( - [ - 'cypress/fixtures/images/cat.jpg', - 'cypress/fixtures/images/traffic.jpg', - ], - { force: true }, - ) - cy.get('.uppy-Dashboard-Item-previewImg') - .should('have.length', 2) - .each((element) => expect(element).attr('src').to.include('blob:')) - }) - - it('should support drag&drop', () => { - cy.get('@drop-target').selectFile( - [ - 'cypress/fixtures/images/cat.jpg', - 'cypress/fixtures/images/cat-symbolic-link', - 'cypress/fixtures/images/cat-symbolic-link.jpg', - 'cypress/fixtures/images/traffic.jpg', - ], - { action: 'drag-drop' }, - ) - - cy.get('.uppy-Dashboard-Item').should('have.length', 4) - cy.get('.uppy-Dashboard-Item-previewImg') - .should('have.length', 3) - .each((element) => expect(element).attr('src').to.include('blob:')) - cy.window().then(({ uppy }) => { - expect( - JSON.stringify(uppy.getFiles().map((file) => file.meta.relativePath)), - ).to.be.equal('[null,null,null,null]') - }) - }) -}) diff --git a/e2e/cypress/integration/dashboard-vue.spec.ts b/e2e/cypress/integration/dashboard-vue.spec.ts deleted file mode 100644 index 57d51b01f..000000000 --- a/e2e/cypress/integration/dashboard-vue.spec.ts +++ /dev/null @@ -1,20 +0,0 @@ -describe('dashboard-vue', () => { - beforeEach(() => { - cy.visit('/dashboard-vue') - }) - - // Only Vue 3 works in Parcel if you use SFC's but Vue 3 is broken in Uppy: - // https://github.com/transloadit/uppy/issues/2877 - xit('should render in Vue 3 and show thumbnails', () => { - cy.get('@file-input').selectFile( - [ - 'cypress/fixtures/images/cat.jpg', - 'cypress/fixtures/images/traffic.jpg', - ], - { force: true }, - ) - cy.get('.uppy-Dashboard-Item-previewImg') - .should('have.length', 2) - .each((element) => expect(element).attr('src').to.include('blob:')) - }) -}) diff --git a/e2e/cypress/integration/dashboard-xhr.spec.ts b/e2e/cypress/integration/dashboard-xhr.spec.ts deleted file mode 100644 index 4a093c517..000000000 --- a/e2e/cypress/integration/dashboard-xhr.spec.ts +++ /dev/null @@ -1,63 +0,0 @@ -import { - interceptCompanionUrlMetaRequest, - runRemoteUrlImageUploadTest, - runRemoteUnsplashUploadTest, -} from './reusable-tests.ts' - -describe('Dashboard with XHR', () => { - beforeEach(() => { - cy.visit('/dashboard-xhr') - }) - - it('should upload remote image with URL plugin', () => { - runRemoteUrlImageUploadTest() - }) - - it('should return correct file name with URL plugin from remote image with Content-Disposition', () => { - const fileName = `DALL·E IMG_9078 - 学中文 🤑` - cy.get('[data-cy="Url"]').click() - cy.get('.uppy-Url-input').type( - 'http://localhost:4678/file-with-content-disposition', - ) - interceptCompanionUrlMetaRequest() - cy.get('.uppy-Url-importButton').click() - cy.wait('@url-meta').then(() => { - cy.get('.uppy-Dashboard-Item-name').should('contain', fileName) - cy.get('.uppy-Dashboard-Item-status').should('contain', '84 KB') - }) - }) - - it('should return correct file name with URL plugin from remote image without Content-Disposition', () => { - cy.get('[data-cy="Url"]').click() - cy.get('.uppy-Url-input').type('http://localhost:4678/file-no-headers') - interceptCompanionUrlMetaRequest() - cy.get('.uppy-Url-importButton').click() - cy.wait('@url-meta').then(() => { - cy.get('.uppy-Dashboard-Item-name').should('contain', 'file-no') - cy.get('.uppy-Dashboard-Item-status').should('contain', '0') - }) - }) - - it('should return correct file name even when Companion doesnt supply it', () => { - cy.intercept('POST', 'http://localhost:3020/url/meta', { - statusCode: 200, - headers: {}, - body: JSON.stringify({ size: 123, type: 'image/jpeg' }), - }).as('url') - - cy.get('[data-cy="Url"]').click() - cy.get('.uppy-Url-input').type( - 'http://localhost:4678/file-with-content-disposition', - ) - interceptCompanionUrlMetaRequest() - cy.get('.uppy-Url-importButton').click() - cy.wait('@url-meta').then(() => { - cy.get('.uppy-Dashboard-Item-name').should('contain', 'file-with') - cy.get('.uppy-Dashboard-Item-status').should('contain', '123 B') - }) - }) - - it('should upload remote image with Unsplash plugin', () => { - runRemoteUnsplashUploadTest() - }) -}) diff --git a/e2e/cypress/integration/react.spec.ts b/e2e/cypress/integration/react.spec.ts deleted file mode 100644 index c206dbc28..000000000 --- a/e2e/cypress/integration/react.spec.ts +++ /dev/null @@ -1,69 +0,0 @@ -describe('@uppy/react', () => { - beforeEach(() => { - cy.visit('/react') - cy.get('#dashboard .uppy-Dashboard-input:first').as('dashboard-input') - cy.get('#modal .uppy-Dashboard-input:first').as('modal-input') - cy.get('#drag-drop .uppy-DragDrop-input').as('dragdrop-input') - }) - - it('should render Dashboard in React and show thumbnails', () => { - cy.get('@dashboard-input').selectFile( - [ - 'cypress/fixtures/images/cat.jpg', - 'cypress/fixtures/images/traffic.jpg', - ], - { force: true }, - ) - cy.get('#dashboard .uppy-Dashboard-Item-previewImg') - .should('have.length', 2) - .each((element) => expect(element).attr('src').to.include('blob:')) - }) - - it('should render Dashboard with Remote Sources plugin pack', () => { - const sources = [ - 'My Device', - 'Google Drive', - 'OneDrive', - 'Unsplash', - 'Zoom', - 'Link', - ] - cy.get('#dashboard .uppy-DashboardTab-name').each((item, index, list) => { - expect(list).to.have.length(6) - // Returns the current element from the loop - expect(Cypress.$(item).text()).to.eq(sources[index]) - }) - }) - - it('should render Modal in React and show thumbnails', () => { - cy.get('#open').click() - cy.get('@modal-input').selectFile( - [ - 'cypress/fixtures/images/cat.jpg', - 'cypress/fixtures/images/traffic.jpg', - ], - { force: true }, - ) - cy.get('#modal .uppy-Dashboard-Item-previewImg') - .should('have.length', 2) - .each((element) => expect(element).attr('src').to.include('blob:')) - }) - - it('should render Drag & Drop in React and create a thumbail with @uppy/thumbnail-generator', () => { - const spy = cy.spy() - - // eslint-disable-next-line - // @ts-ignore fix me - cy.window().then(({ uppy }) => uppy.on('thumbnail:generated', spy)) - cy.get('@dragdrop-input').selectFile( - [ - 'cypress/fixtures/images/cat.jpg', - 'cypress/fixtures/images/traffic.jpg', - ], - { force: true }, - ) - // not sure how I can accurately wait for the thumbnail - // eslint-disable-next-line cypress/no-unnecessary-waiting - cy.wait(1000).then(() => expect(spy).to.be.called) - }) -}) diff --git a/e2e/cypress/integration/reusable-tests.ts b/e2e/cypress/integration/reusable-tests.ts deleted file mode 100644 index d311b60e3..000000000 --- a/e2e/cypress/integration/reusable-tests.ts +++ /dev/null @@ -1,54 +0,0 @@ -/* global cy */ - -const interceptCompanionUrlRequest = () => - cy - .intercept({ method: 'POST', url: 'http://localhost:3020/url/get' }) - .as('url') -export const interceptCompanionUrlMetaRequest = () => - cy - .intercept({ method: 'POST', url: 'http://localhost:3020/url/meta' }) - .as('url-meta') - -export function runRemoteUrlImageUploadTest() { - cy.get('[data-cy="Url"]').click() - cy.get('.uppy-Url-input').type( - 'https://raw.githubusercontent.com/transloadit/uppy/main/e2e/cypress/fixtures/images/cat.jpg', - ) - cy.get('.uppy-Url-importButton').click() - interceptCompanionUrlRequest() - cy.get('.uppy-StatusBar-actionBtn--upload').click() - cy.wait('@url').then(() => { - cy.get('.uppy-StatusBar-statusPrimary').should('contain', 'Complete') - }) -} - -export function runRemoteUnsplashUploadTest() { - cy.get('[data-cy="Unsplash"]').click() - cy.get('.uppy-SearchProvider-input').type('book') - cy.intercept({ - method: 'GET', - url: 'http://localhost:3020/search/unsplash/list?q=book', - }).as('unsplash-list') - cy.get('.uppy-SearchProvider-searchButton').click() - cy.wait('@unsplash-list') - // Test that the author link is visible - cy.get('.uppy-ProviderBrowserItem') - .first() - .within(() => { - cy.root().click() - // We have hover states that show the author - // but we don't have hover in e2e, so we focus after the click - // to get the same effect. Also tests keyboard users this way. - cy.get('input[type="checkbox"]').focus() - cy.get('a').should('have.css', 'display', 'block') - }) - cy.get('.uppy-c-btn-primary').click() - cy.intercept({ - method: 'POST', - url: 'http://localhost:3020/search/unsplash/get/*', - }).as('unsplash-get') - cy.get('.uppy-StatusBar-actionBtn--upload').click() - cy.wait('@unsplash-get').then(() => { - cy.get('.uppy-StatusBar-statusPrimary').should('contain', 'Complete') - }) -} diff --git a/e2e/cypress/support/commands.ts b/e2e/cypress/support/commands.ts deleted file mode 100644 index 986d5df16..000000000 --- a/e2e/cypress/support/commands.ts +++ /dev/null @@ -1,30 +0,0 @@ -// *********************************************** -// This example commands.js shows you how to -// create various custom commands and overwrite -// existing commands. -// -// For more comprehensive examples of custom -// commands please read more here: -// https://on.cypress.io/custom-commands -// *********************************************** -// -// -// -- This is a parent command -- -// Cypress.Commands.add('login', (email, password) => { ... }) -// -// -// -- This is a child command -- -// Cypress.Commands.add('drag', { prevSubject: 'element'}, (subject, options) => { ... }) -// -// -// -- This is a dual command -- -// Cypress.Commands.add('dismiss', { prevSubject: 'optional'}, (subject, options) => { ... }) -// -// -// -- This will overwrite an existing command -- -// Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... }) -// - -import { createFakeFile } from './createFakeFile.ts' - -Cypress.Commands.add('createFakeFile', createFakeFile) diff --git a/e2e/cypress/support/createFakeFile.ts b/e2e/cypress/support/createFakeFile.ts deleted file mode 100644 index 825cae6ff..000000000 --- a/e2e/cypress/support/createFakeFile.ts +++ /dev/null @@ -1,59 +0,0 @@ -declare global { - namespace Cypress { - interface Chainable { - // eslint-disable-next-line no-use-before-define - createFakeFile: typeof createFakeFile - } - } -} - -interface File { - source: string - name: string - type: string - data: Blob -} - -export function createFakeFile( - name?: string, - type?: string, - b64?: string, -): File { - if (!b64) { - // eslint-disable-next-line no-param-reassign - b64 = - 'PHN2ZyB2aWV3Qm94PSIwIDAgMTIwIDEyMCI+CiAgPGNpcmNsZSBjeD0iNjAiIGN5PSI2MCIgcj0iNTAiLz4KPC9zdmc+Cg==' - } - // eslint-disable-next-line no-param-reassign - if (!type) type = 'image/svg+xml' - - // https://stackoverflow.com/questions/16245767/creating-a-blob-from-a-base64-string-in-javascript - function base64toBlob(base64Data: string, contentType = '') { - const sliceSize = 1024 - const byteCharacters = atob(base64Data) - const bytesLength = byteCharacters.length - const slicesCount = Math.ceil(bytesLength / sliceSize) - const byteArrays = new Array(slicesCount) - - for (let sliceIndex = 0; sliceIndex < slicesCount; ++sliceIndex) { - const begin = sliceIndex * sliceSize - const end = Math.min(begin + sliceSize, bytesLength) - - const bytes = new Array(end - begin) - for (let offset = begin, i = 0; offset < end; ++i, ++offset) { - bytes[i] = byteCharacters[offset].charCodeAt(0) - } - byteArrays[sliceIndex] = new Uint8Array(bytes) - } - return new Blob(byteArrays, { type: contentType }) - } - - const blob = base64toBlob(b64, type) - - return { - source: 'test', - name: name || 'test-file', - type: blob.type, - data: blob, - } -} diff --git a/e2e/cypress/support/e2e.ts b/e2e/cypress/support/e2e.ts deleted file mode 100644 index 97dc4e009..000000000 --- a/e2e/cypress/support/e2e.ts +++ /dev/null @@ -1,26 +0,0 @@ -// *********************************************************** -// This example support/e2e.ts is processed and -// loaded automatically before your test files. -// -// This is a great place to put global configuration and -// behavior that modifies Cypress. -// -// You can change the location of this file or turn off -// automatically serving support files with the -// 'supportFile' configuration option. -// -// You can read more here: -// https://on.cypress.io/configuration -// *********************************************************** - -// Import commands.js using ES2015 syntax: -import './commands.ts' - -// Alternatively you can use CommonJS syntax: -// require('./commands') - -// eslint-disable-next-line -// @ts-ignore -import installLogsCollector from 'cypress-terminal-report/src/installLogsCollector.js' - -installLogsCollector() diff --git a/e2e/cypress/support/index.ts b/e2e/cypress/support/index.ts deleted file mode 100644 index 71e0d4194..000000000 --- a/e2e/cypress/support/index.ts +++ /dev/null @@ -1,24 +0,0 @@ -// *********************************************************** -// This example support/index.js is processed and -// loaded automatically before your test files. -// -// This is a great place to put global configuration and -// behavior that modifies Cypress. -// -// You can change the location of this file or turn off -// automatically serving support files with the -// 'supportFile' configuration option. -// -// You can read more here: -// https://on.cypress.io/configuration -// *********************************************************** - -import './commands.ts' - -import type { Uppy } from '@uppy/core' - -declare global { - interface Window { - uppy: Uppy - } -} diff --git a/e2e/generate-test.mjs b/e2e/generate-test.mjs deleted file mode 100755 index c4e9cc8eb..000000000 --- a/e2e/generate-test.mjs +++ /dev/null @@ -1,100 +0,0 @@ -#!/usr/bin/env node -import prompts from 'prompts' -import fs from 'node:fs/promises' - -/** - * Utility function that strips indentation from multi-line strings. - * Inspired from https://github.com/dmnd/dedent. - */ -function dedent (strings, ...parts) { - const nonSpacingChar = /\S/m.exec(strings[0]) - if (nonSpacingChar == null) return '' - - const indent = nonSpacingChar.index - strings[0].lastIndexOf('\n', nonSpacingChar.index) - 1 - const dedentEachLine = str => str.split('\n').map((line, i) => line.slice(i && indent)).join('\n') - let returnLines = dedentEachLine(strings[0].slice(nonSpacingChar.index), indent) - for (let i = 1; i < strings.length; i++) { - returnLines += String(parts[i - 1]) + dedentEachLine(strings[i], indent) - } - return returnLines -} - -const packageNames = await fs.readdir(new URL('../packages/@uppy', import.meta.url)) -const unwantedPackages = ['core', 'companion', 'redux-dev-tools', 'utils'] - -const { name } = await prompts({ - type: 'text', - name: 'name', - message: 'What should the name of the test be (e.g `dashboard-tus`)?', - validate: (value) => /^[a-z|-]+$/i.test(value), -}) - -const { packages } = await prompts({ - type: 'multiselect', - name: 'packages', - message: 'What packages do you want to test?', - hint: '@uppy/core is automatically included', - choices: packageNames - .filter((pkg) => !unwantedPackages.includes(pkg)) - .map((pkg) => ({ title: pkg, value: pkg })), -}) - -const camelcase = (str) => str - .toLowerCase() - .replace(/([-][a-z])/g, (group) => group.toUpperCase().replace('-', '')) - -const testUrl = new URL(`cypress/integration/${name}.spec.ts`, import.meta.url) -const test = dedent` - describe('${name}', () => { - beforeEach(() => { - cy.visit('/${name}') - }) - }) - ` -const htmlUrl = new URL(`clients/${name}/index.html`, import.meta.url) -const html = dedent` - - - - - ${name} - - - -
- - - ` - -const appUrl = new URL(`clients/${name}/app.js`, import.meta.url) -// dedent is acting weird for this one but this formatting fixes it. -const app = dedent` - import Uppy from '@uppy/core' - ${packages.map((pgk) => `import ${camelcase(pgk)} from '@uppy/${pgk}'`).join('\n')} - - const uppy = new Uppy() - ${packages.map((pkg) => `.use(${camelcase(pkg)})`).join('\n\t')} - - // Keep this here to access uppy in tests - window.uppy = uppy - ` - -await fs.writeFile(testUrl, test) -await fs.mkdir(new URL(`clients/${name}`, import.meta.url)) -await fs.writeFile(htmlUrl, html) -await fs.writeFile(appUrl, app) - -const homeUrl = new URL('clients/index.html', import.meta.url) -const home = await fs.readFile(homeUrl, 'utf8') -const newHome = home.replace( - '', - `
  • ${name}
  • \n `, -) -await fs.writeFile(homeUrl, newHome) - -const prettyPath = (url) => url.toString().split('uppy', 2)[1] - -console.log(`✅ Generated ${prettyPath(testUrl)}`) -console.log(`✅ Generated ${prettyPath(htmlUrl)}`) -console.log(`✅ Generated ${prettyPath(appUrl)}`) -console.log(`✅ Updated ${prettyPath(homeUrl)}`) diff --git a/e2e/mock-server.mjs b/e2e/mock-server.mjs deleted file mode 100644 index 02d84b77d..000000000 --- a/e2e/mock-server.mjs +++ /dev/null @@ -1,30 +0,0 @@ -import http from 'node:http' - -const requestListener = (req, res) => { - const endpoint = req.url - - switch (endpoint) { - case '/file-with-content-disposition': { - const fileName = `DALL·E IMG_9078 - 学中文 🤑` - res.setHeader('Content-Disposition', `attachment; filename="ASCII-name.zip"; filename*=UTF-8''${encodeURIComponent(fileName)}`) - res.setHeader('Content-Type', 'image/jpeg') - res.setHeader('Content-Length', '86500') - break - } - case '/file-no-headers': - break - default: - res.writeHead(404).end('Unhandled request') - } - - res.end() -} - -export default function startMockServer (host, port) { - const server = http.createServer(requestListener) - server.listen(port, host, () => { - console.log(`Mock server is running on http://${host}:${port}`) - }) -} - -// startMockServer('localhost', 4678) diff --git a/e2e/package.json b/e2e/package.json deleted file mode 100644 index e923ebf3b..000000000 --- a/e2e/package.json +++ /dev/null @@ -1,61 +0,0 @@ -{ - "name": "e2e", - "private": true, - "author": "Merlijn Vos ", - "description": "End-to-end test suite for Uppy", - "scripts": { - "client:start": "parcel --no-autoinstall clients/index.html", - "cypress:open": "cypress open", - "cypress:headless": "cypress run", - "generate-test": "yarn node generate-test.mjs" - }, - "dependencies": { - "@uppy/audio": "workspace:^", - "@uppy/aws-s3": "workspace:^", - "@uppy/aws-s3-multipart": "workspace:^", - "@uppy/box": "workspace:^", - "@uppy/companion-client": "workspace:^", - "@uppy/core": "workspace:^", - "@uppy/dashboard": "workspace:^", - "@uppy/drag-drop": "workspace:^", - "@uppy/drop-target": "workspace:^", - "@uppy/dropbox": "workspace:^", - "@uppy/facebook": "workspace:^", - "@uppy/file-input": "workspace:^", - "@uppy/form": "workspace:^", - "@uppy/golden-retriever": "workspace:^", - "@uppy/google-drive": "workspace:^", - "@uppy/google-photos": "workspace:^", - "@uppy/image-editor": "workspace:^", - "@uppy/informer": "workspace:^", - "@uppy/instagram": "workspace:^", - "@uppy/onedrive": "workspace:^", - "@uppy/progress-bar": "workspace:^", - "@uppy/provider-views": "workspace:^", - "@uppy/screen-capture": "workspace:^", - "@uppy/status-bar": "workspace:^", - "@uppy/store-default": "workspace:^", - "@uppy/store-redux": "workspace:^", - "@uppy/thumbnail-generator": "workspace:^", - "@uppy/transloadit": "workspace:^", - "@uppy/tus": "workspace:^", - "@uppy/unsplash": "workspace:^", - "@uppy/url": "workspace:^", - "@uppy/webcam": "workspace:^", - "@uppy/xhr-upload": "workspace:^", - "@uppy/zoom": "workspace:^" - }, - "devDependencies": { - "@parcel/transformer-vue": "^2.9.3", - "cypress": "^13.0.0", - "cypress-terminal-report": "^6.0.0", - "deep-freeze": "^0.0.1", - "parcel": "^2.9.3", - "process": "^0.11.10", - "prompts": "^2.4.2", - "react": "^18.1.0", - "react-dom": "^18.1.0", - "typescript": "~5.4", - "vue": "^3.2.33" - } -} diff --git a/e2e/tsconfig.json b/e2e/tsconfig.json deleted file mode 100644 index e2544d3c7..000000000 --- a/e2e/tsconfig.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "compilerOptions": { - "module": "NodeNext", - "moduleResolution": "NodeNext", - "noEmit": true, - "target": "es2020", - "lib": ["es2020", "dom"], - "types": ["cypress"], - }, - "include": ["cypress/**/*.ts"], -} diff --git a/examples/angular-example/src/styles.css b/examples/angular-example/src/styles.css deleted file mode 100644 index 1f612c2cf..000000000 --- a/examples/angular-example/src/styles.css +++ /dev/null @@ -1,5 +0,0 @@ -/* You can add global styles to this file, and also import other style files */ -@import '@uppy/core/dist/style.css'; -@import '@uppy/dashboard/dist/style.css'; -@import '@uppy/drag-drop/dist/style.css'; -@import '@uppy/progress-bar/dist/style.css'; diff --git a/examples/angular-example/tsconfig.spec.json b/examples/angular-example/tsconfig.spec.json deleted file mode 100644 index 47e3dd755..000000000 --- a/examples/angular-example/tsconfig.spec.json +++ /dev/null @@ -1,9 +0,0 @@ -/* To learn more about this file see: https://angular.io/config/tsconfig. */ -{ - "extends": "./tsconfig.json", - "compilerOptions": { - "outDir": "./out-tsc/spec", - "types": ["jasmine"] - }, - "include": ["src/**/*.spec.ts", "src/**/*.d.ts"] -} diff --git a/examples/angular-example/.editorconfig b/examples/angular/.editorconfig similarity index 100% rename from examples/angular-example/.editorconfig rename to examples/angular/.editorconfig diff --git a/examples/angular-example/.eslintrc.json b/examples/angular/.eslintrc.json similarity index 100% rename from examples/angular-example/.eslintrc.json rename to examples/angular/.eslintrc.json diff --git a/examples/angular-example/.gitignore b/examples/angular/.gitignore similarity index 100% rename from examples/angular-example/.gitignore rename to examples/angular/.gitignore diff --git a/examples/angular-example/.vscode/extensions.json b/examples/angular/.vscode/extensions.json similarity index 100% rename from examples/angular-example/.vscode/extensions.json rename to examples/angular/.vscode/extensions.json diff --git a/examples/angular-example/.vscode/launch.json b/examples/angular/.vscode/launch.json similarity index 100% rename from examples/angular-example/.vscode/launch.json rename to examples/angular/.vscode/launch.json diff --git a/examples/angular-example/.vscode/tasks.json b/examples/angular/.vscode/tasks.json similarity index 100% rename from examples/angular-example/.vscode/tasks.json rename to examples/angular/.vscode/tasks.json diff --git a/examples/angular-example/README.md b/examples/angular/README.md similarity index 100% rename from examples/angular-example/README.md rename to examples/angular/README.md diff --git a/examples/angular-example/angular.json b/examples/angular/angular.json similarity index 100% rename from examples/angular-example/angular.json rename to examples/angular/angular.json diff --git a/examples/angular-example/package.json b/examples/angular/package.json similarity index 55% rename from examples/angular-example/package.json rename to examples/angular/package.json index bf0668871..5d587868e 100644 --- a/examples/angular-example/package.json +++ b/examples/angular/package.json @@ -1,13 +1,11 @@ { - "name": "@uppy-example/angular", + "name": "example-angular", "version": "0.0.0", "scripts": { "ng": "ng", "start": "ng serve", "build": "ng build", - "watch": "ng build --watch --configuration development", - "test": "ng test", - "lint": "ng lint" + "watch": "ng build --watch --configuration development" }, "private": true, "dependencies": { @@ -21,11 +19,10 @@ "@angular/router": "^18.0.0", "@uppy/angular": "workspace:*", "@uppy/core": "workspace:*", - "@uppy/drag-drop": "workspace:*", + "@uppy/dashboard": "workspace:*", "@uppy/google-drive": "workspace:*", - "@uppy/google-photos": "workspace:*", - "@uppy/progress-bar": "workspace:*", "@uppy/tus": "workspace:*", + "@uppy/utils": "workspace:*", "@uppy/webcam": "workspace:*", "rxjs": "~7.8.0", "tslib": "^2.3.0", @@ -33,20 +30,8 @@ }, "devDependencies": { "@angular-devkit/build-angular": "^18.0.2", - "@angular-eslint/eslint-plugin": "18.0.1", - "@angular-eslint/eslint-plugin-template": "18.0.1", "@angular/cli": "^18.0.2", "@angular/compiler-cli": "^18.0.0", - "@types/jasmine": "~5.1.0", - "@typescript-eslint/eslint-plugin": "^7.2.0", - "@typescript-eslint/parser": "^7.2.0", - "eslint": "^8.57.0", - "jasmine-core": "~5.1.0", - "karma": "~6.4.0", - "karma-chrome-launcher": "~3.2.0", - "karma-coverage": "~2.2.0", - "karma-jasmine": "~5.1.0", - "karma-jasmine-html-reporter": "~2.1.0", "typescript": "~5.4" } } diff --git a/examples/angular-example/src/app/app.component.css b/examples/angular/src/app/app.component.css similarity index 100% rename from examples/angular-example/src/app/app.component.css rename to examples/angular/src/app/app.component.css diff --git a/examples/angular-example/src/app/app.component.html b/examples/angular/src/app/app.component.html similarity index 100% rename from examples/angular-example/src/app/app.component.html rename to examples/angular/src/app/app.component.html diff --git a/examples/angular-example/src/app/app.component.ts b/examples/angular/src/app/app.component.ts similarity index 91% rename from examples/angular-example/src/app/app.component.ts rename to examples/angular/src/app/app.component.ts index 5daa5ffb4..0c6ca0ae1 100644 --- a/examples/angular-example/src/app/app.component.ts +++ b/examples/angular/src/app/app.component.ts @@ -1,8 +1,8 @@ -import { Component, OnInit } from '@angular/core' +import { Component, type OnInit } from '@angular/core' import { Uppy } from '@uppy/core' -import Webcam from '@uppy/webcam' -import Tus from '@uppy/tus' import GoogleDrive from '@uppy/google-drive' +import Tus from '@uppy/tus' +import Webcam from '@uppy/webcam' @Component({ selector: 'app-root', @@ -36,9 +36,6 @@ import GoogleDrive from '@uppy/google-drive' -

    Drag Drop Area

    - -

    Progress Bar

    console.error(err)) // eslint-disable-line no-console + .catch((err) => console.error(err)) diff --git a/examples/angular/src/styles.css b/examples/angular/src/styles.css new file mode 100644 index 000000000..8e9d29575 --- /dev/null +++ b/examples/angular/src/styles.css @@ -0,0 +1,3 @@ +/* You can add global styles to this file, and also import other style files */ +@import "@uppy/core/dist/style.css"; +@import "@uppy/dashboard/dist/style.css"; diff --git a/examples/angular-example/tsconfig.app.json b/examples/angular/tsconfig.app.json similarity index 100% rename from examples/angular-example/tsconfig.app.json rename to examples/angular/tsconfig.app.json diff --git a/examples/angular-example/tsconfig.json b/examples/angular/tsconfig.json similarity index 92% rename from examples/angular-example/tsconfig.json rename to examples/angular/tsconfig.json index 8901ce8c6..1301bf238 100644 --- a/examples/angular-example/tsconfig.json +++ b/examples/angular/tsconfig.json @@ -19,12 +19,12 @@ "target": "ES2022", "module": "ES2022", "useDefineForClassFields": false, - "lib": ["ES2022", "dom"], + "lib": ["ES2022", "dom"] }, "angularCompilerOptions": { "enableI18nLegacyMessageIdFormat": false, "strictInjectionParameters": true, "strictInputAccessModifiers": true, - "strictTemplates": true, - }, + "strictTemplates": true + } } diff --git a/examples/aws-companion/package.json b/examples/aws-companion/package.json index 49f1819c9..6d0d1c40f 100644 --- a/examples/aws-companion/package.json +++ b/examples/aws-companion/package.json @@ -1,5 +1,5 @@ { - "name": "@uppy-example/aws-companion", + "name": "example-aws-companion", "version": "0.0.0", "type": "module", "dependencies": { @@ -11,14 +11,14 @@ }, "devDependencies": { "@uppy/companion": "workspace:*", - "body-parser": "^1.20.0", + "body-parser": "^1.20.3", "cookie-parser": "^1.4.6", "cors": "^2.8.5", "dotenv": "^16.0.1", "express": "^4.19.2", "express-session": "^1.17.3", "npm-run-all": "^4.1.5", - "vite": "^5.0.0" + "vite": "^7.0.6" }, "private": true, "engines": { diff --git a/examples/aws-companion/server.cjs b/examples/aws-companion/server.cjs index d1a92dfeb..010d7e644 100644 --- a/examples/aws-companion/server.cjs +++ b/examples/aws-companion/server.cjs @@ -8,18 +8,22 @@ const app = require('express')() const DATA_DIR = path.join(__dirname, 'tmp') -app.use(require('cors')({ - origin: 'http://localhost:3000', - methods: ['GET', 'POST', 'OPTIONS'], - credentials: true, -})) +app.use( + require('cors')({ + origin: 'http://localhost:3000', + methods: ['GET', 'POST', 'OPTIONS'], + credentials: true, + }), +) app.use(require('cookie-parser')()) app.use(require('body-parser').json()) -app.use(require('express-session')({ - secret: 'hello planet', - saveUninitialized: false, - resave: false, -})) +app.use( + require('express-session')({ + secret: 'hello planet', + saveUninitialized: false, + resave: false, + }), +) const options = { providerOptions: { @@ -46,7 +50,7 @@ const options = { // Create the data directory here for the sake of the example. try { fs.accessSync(DATA_DIR) -} catch (err) { +} catch (_err) { fs.mkdirSync(DATA_DIR) } process.on('exit', () => { diff --git a/examples/aws-nodejs/index.js b/examples/aws-nodejs/index.js index 97accb384..1010edf45 100644 --- a/examples/aws-nodejs/index.js +++ b/examples/aws-nodejs/index.js @@ -1,5 +1,3 @@ -'use strict' - const path = require('node:path') const crypto = require('node:crypto') const { existsSync } = require('node:fs') @@ -74,6 +72,29 @@ function getSTSClient() { return stsClient } +// Generate a unique S3 key for the file +const generateS3Key = (filename) => `${crypto.randomUUID()}-${filename}` + +// Extract the file parameters from the request +const extractFileParameters = (req) => { + const isPostRequest = req.method === 'POST' + const params = isPostRequest ? req.body : req.query + + return { + filename: params.filename, + contentType: params.type, + } +} + +// Validate the file parameters +const validateFileParameters = (filename, contentType) => { + if (!filename || !contentType) { + throw new Error( + 'Missing required parameters: filename and content type are required', + ) + } +} + app.use(bodyParser.urlencoded({ extended: true }), bodyParser.json()) app.get('/s3/sts', (req, res, next) => { @@ -109,8 +130,11 @@ const signOnServer = (req, res, next) => { // are authorized to perform that operation, and if the request is legit. // For the sake of simplification, we skip that check in this example. - const Key = `${crypto.randomUUID()}-${req.body.filename}` - const { contentType } = req.body + const { filename, contentType } = extractFileParameters(req) + validateFileParameters(filename, contentType) + + // Generate S3 key and prepare command + const Key = generateS3Key(filename) getSignedUrl( getS3Client(), @@ -171,7 +195,6 @@ app.post('/s3/multipart', (req, res, next) => { }) function validatePartNumber(partNumber) { - // eslint-disable-next-line no-param-reassign partNumber = Number(partNumber) return Number.isInteger(partNumber) && partNumber >= 1 && partNumber <= 10_000 } @@ -180,19 +203,15 @@ app.get('/s3/multipart/:uploadId/:partNumber', (req, res, next) => { const { key } = req.query if (!validatePartNumber(partNumber)) { - return res - .status(400) - .json({ - error: 's3: the part number must be an integer between 1 and 10000.', - }) + return res.status(400).json({ + error: 's3: the part number must be an integer between 1 and 10000.', + }) } if (typeof key !== 'string') { - return res - .status(400) - .json({ - error: - 's3: the object key must be passed as a query parameter. For example: "?key=abc.jpg"', - }) + return res.status(400).json({ + error: + 's3: the object key must be passed as a query parameter. For example: "?key=abc.jpg"', + }) } return getSignedUrl( @@ -217,38 +236,39 @@ app.get('/s3/multipart/:uploadId', (req, res, next) => { const { key } = req.query if (typeof key !== 'string') { - res - .status(400) - .json({ - error: - 's3: the object key must be passed as a query parameter. For example: "?key=abc.jpg"', - }) + res.status(400).json({ + error: + 's3: the object key must be passed as a query parameter. For example: "?key=abc.jpg"', + }) return } const parts = [] function listPartsPage(startsAt = undefined) { - client.send(new ListPartsCommand({ - Bucket: process.env.COMPANION_AWS_BUCKET, - Key: key, - UploadId: uploadId, - PartNumberMarker: startsAt, - }), (err, data) => { - if (err) { - next(err) - return - } + client.send( + new ListPartsCommand({ + Bucket: process.env.COMPANION_AWS_BUCKET, + Key: key, + UploadId: uploadId, + PartNumberMarker: startsAt, + }), + (err, data) => { + if (err) { + next(err) + return + } parts.push(...data.Parts) - // continue to get list of all uploaded parts until the IsTruncated flag is false - if (data.IsTruncated) { - listPartsPage(data.NextPartNumberMarker) - } else { - res.json(parts) - } - }) + // continue to get list of all uploaded parts until the IsTruncated flag is false + if (data.IsTruncated) { + listPartsPage(data.NextPartNumberMarker) + } else { + res.json(parts) + } + }, + ) } listPartsPage() }) @@ -268,19 +288,15 @@ app.post('/s3/multipart/:uploadId/complete', (req, res, next) => { const { parts } = req.body if (typeof key !== 'string') { - return res - .status(400) - .json({ - error: - 's3: the object key must be passed as a query parameter. For example: "?key=abc.jpg"', - }) + return res.status(400).json({ + error: + 's3: the object key must be passed as a query parameter. For example: "?key=abc.jpg"', + }) } if (!Array.isArray(parts) || !parts.every(isValidPart)) { - return res - .status(400) - .json({ - error: 's3: `parts` must be an array of {ETag, PartNumber} objects.', - }) + return res.status(400).json({ + error: 's3: `parts` must be an array of {ETag, PartNumber} objects.', + }) } return client.send( @@ -311,12 +327,10 @@ app.delete('/s3/multipart/:uploadId', (req, res, next) => { const { key } = req.query if (typeof key !== 'string') { - return res - .status(400) - .json({ - error: - 's3: the object key must be passed as a query parameter. For example: "?key=abc.jpg"', - }) + return res.status(400).json({ + error: + 's3: the object key must be passed as a query parameter. For example: "?key=abc.jpg"', + }) } return client.send( diff --git a/examples/aws-nodejs/package.json b/examples/aws-nodejs/package.json index ed07df7f8..27eda12df 100644 --- a/examples/aws-nodejs/package.json +++ b/examples/aws-nodejs/package.json @@ -1,5 +1,5 @@ { - "name": "@uppy-example/aws-nodejs", + "name": "example-aws-nodejs", "version": "1.0.0", "description": "Uppy for AWS S3 with a custom Node.js backend for signing URLs", "main": "index.js", @@ -13,7 +13,7 @@ "@aws-sdk/client-s3": "^3.338.0", "@aws-sdk/client-sts": "^3.338.0", "@aws-sdk/s3-request-presigner": "^3.338.0", - "body-parser": "^1.20.0", + "body-parser": "^1.20.3", "dotenv": "^16.0.0", "express": "^4.19.2" } diff --git a/examples/aws-php/main.js b/examples/aws-php/main.js index 40c54fd96..4704663bf 100644 --- a/examples/aws-php/main.js +++ b/examples/aws-php/main.js @@ -1,6 +1,6 @@ +import AwsS3 from '@uppy/aws-s3' import Uppy from '@uppy/core' import Dashboard from '@uppy/dashboard' -import AwsS3 from '@uppy/aws-s3' const uppy = new Uppy({ debug: true, @@ -13,7 +13,7 @@ uppy.use(Dashboard, { uppy.use(AwsS3, { shouldUseMultipart: false, // The PHP backend only supports non-multipart uploads - getUploadParameters (file) { + getUploadParameters(file) { // Send a request to our PHP signing endpoint. return fetch('/s3-sign.php', { method: 'post', @@ -26,17 +26,19 @@ uppy.use(AwsS3, { filename: file.name, contentType: file.type, }), - }).then((response) => { - // Parse the JSON response. - return response.json() - }).then((data) => { - // Return an object in the correct shape. - return { - method: data.method, - url: data.url, - fields: data.fields, - headers: data.headers, - } }) + .then((response) => { + // Parse the JSON response. + return response.json() + }) + .then((data) => { + // Return an object in the correct shape. + return { + method: data.method, + url: data.url, + fields: data.fields, + headers: data.headers, + } + }) }, }) diff --git a/examples/aws-php/package.json b/examples/aws-php/package.json index cd54377c6..5d8d275a3 100644 --- a/examples/aws-php/package.json +++ b/examples/aws-php/package.json @@ -1,5 +1,5 @@ { - "name": "@uppy-example/aws-php", + "name": "example-aws-php", "version": "0.0.0", "dependencies": { "@uppy/aws-s3": "workspace:*", @@ -8,7 +8,7 @@ "uppy": "workspace:*" }, "devDependencies": { - "esbuild": "^0.21.2" + "esbuild": "^0.25.0" }, "private": true, "type": "module", diff --git a/examples/bundled/index.html b/examples/bundled/index.html deleted file mode 100644 index 492cabe65..000000000 --- a/examples/bundled/index.html +++ /dev/null @@ -1,32 +0,0 @@ - - - - - - uppy - - - -
    -

    Uppy

    - - - - - -
    - - - - diff --git a/examples/bundled/index.js b/examples/bundled/index.js deleted file mode 100644 index d4878eb71..000000000 --- a/examples/bundled/index.js +++ /dev/null @@ -1,71 +0,0 @@ -import Uppy from '@uppy/core' -import Dashboard from '@uppy/dashboard' -import Instagram from '@uppy/instagram' -import GoogleDrive from '@uppy/google-drive' -import Url from '@uppy/url' -import Webcam from '@uppy/webcam' -import Tus from '@uppy/tus' - -import '@uppy/core/dist/style.css' -import '@uppy/dashboard/dist/style.css' -import '@uppy/url/dist/style.css' -import '@uppy/webcam/dist/style.css' - -const TUS_ENDPOINT = 'https://tusd.tusdemo.net/files/' - -const uppy = new Uppy({ - debug: true, - meta: { - username: 'John', - license: 'Creative Commons', - }, -}) - .use(Dashboard, { - trigger: '#pick-files', - target: '#upload-form', - inline: true, - metaFields: [ - { id: 'license', name: 'License', placeholder: 'specify license' }, - { id: 'caption', name: 'Caption', placeholder: 'add caption' }, - ], - showProgressDetails: true, - proudlyDisplayPoweredByUppy: true, - note: '2 files, images and video only', - restrictions: { requiredMetaFields: ['caption'] }, - }) - .use(GoogleDrive, { target: Dashboard, companionUrl: 'http://localhost:3020' }) - .use(Instagram, { target: Dashboard, companionUrl: 'http://localhost:3020' }) - .use(Url, { target: Dashboard, companionUrl: 'http://localhost:3020' }) - .use(Webcam, { target: Dashboard }) - .use(Tus, { endpoint: TUS_ENDPOINT }) - -// You can optinally enable the Golden Retriever plugin — it will -// restore files after a browser crash / accidental closed window -// see more at https://uppy.io/docs/golden-retriever/ -// -// .use(GoldenRetriever, { serviceWorker: true }) - -uppy.on('complete', (result) => { - if (result.failed.length === 0) { - console.log('Upload successful 😀') - } else { - console.warn('Upload failed 😞') - } - console.log('successful files:', result.successful) - console.log('failed files:', result.failed) -}) - -// uncomment if you enable Golden Retriever -// -/* eslint-disable compat/compat */ -// if ('serviceWorker' in navigator) { -// navigator.serviceWorker -// .register('/sw.js') -// .then((registration) => { -// console.log('ServiceWorker registration successful with scope: ', registration.scope) -// }) -// .catch((error) => { -// console.log('Registration failed with ' + error) -// }) -// } -/* eslint-enable */ diff --git a/examples/bundled/package.json b/examples/bundled/package.json deleted file mode 100644 index 5c7d1b3c4..000000000 --- a/examples/bundled/package.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "name": "@uppy-example/bundled", - "version": "0.0.0", - "dependencies": { - "@uppy/core": "workspace:*", - "@uppy/dashboard": "workspace:*", - "@uppy/google-drive": "workspace:*", - "@uppy/instagram": "workspace:*", - "@uppy/tus": "workspace:*", - "@uppy/url": "workspace:*", - "@uppy/webcam": "workspace:*" - }, - "devDependencies": { - "parcel": "^2.0.0" - }, - "private": true, - "type": "module", - "scripts": { - "build": "parcel build index.html", - "dev": "parcel index.html" - } -} diff --git a/examples/bundled/sw.js b/examples/bundled/sw.js deleted file mode 100644 index 6f10af858..000000000 --- a/examples/bundled/sw.js +++ /dev/null @@ -1,68 +0,0 @@ -// This service worker is needed for Golden Retriever plugin, -// only include if you’ve enabled it -// https://uppy.io/docs/golden-retriever/ - -/* globals clients */ -/* eslint-disable no-restricted-globals */ - -const fileCache = Object.create(null) - -function getCache (name) { - if (!fileCache[name]) { - fileCache[name] = Object.create(null) - } - return fileCache[name] -} - -self.addEventListener('install', (event) => { - console.log('Installing Uppy Service Worker...') - - event.waitUntil(Promise.resolve() - .then(() => self.skipWaiting())) -}) - -self.addEventListener('activate', (event) => { - event.waitUntil(self.clients.claim()) -}) - -function sendMessageToAllClients (msg) { - clients.matchAll().then((clients) => { - clients.forEach((client) => { - client.postMessage(msg) - }) - }) -} - -function addFile (store, file) { - getCache(store)[file.id] = file.data - console.log('Added file blob to service worker cache:', file.data) -} - -function removeFile (store, fileID) { - delete getCache(store)[fileID] - console.log('Removed file blob from service worker cache:', fileID) -} - -function getFiles (store) { - sendMessageToAllClients({ - type: 'uppy/ALL_FILES', - store, - files: getCache(store), - }) -} - -self.addEventListener('message', (event) => { - switch (event.data.type) { - case 'uppy/ADD_FILE': - addFile(event.data.store, event.data.file) - break - case 'uppy/REMOVE_FILE': - removeFile(event.data.store, event.data.fileID) - break - case 'uppy/GET_FILES': - getFiles(event.data.store) - break - - default: throw new Error('unreachable') - } -}) diff --git a/examples/cdn-example/README.md b/examples/cdn/README.md similarity index 100% rename from examples/cdn-example/README.md rename to examples/cdn/README.md diff --git a/examples/cdn-example/index.html b/examples/cdn/index.html similarity index 85% rename from examples/cdn-example/index.html rename to examples/cdn/index.html index fe5bb0c18..5f66f2087 100644 --- a/examples/cdn-example/index.html +++ b/examples/cdn/index.html @@ -5,7 +5,7 @@ @@ -19,7 +19,7 @@ Dashboard, Webcam, Tus, - } from 'https://releases.transloadit.com/uppy/v4.3.0/uppy.min.mjs' + } from 'https://releases.transloadit.com/uppy/v4.18.0/uppy.min.mjs' const uppy = new Uppy({ debug: true, autoProceed: false }) .use(Dashboard, { trigger: '#uppyModalOpener' }) diff --git a/examples/cdn-example/package.json b/examples/cdn/package.json similarity index 86% rename from examples/cdn-example/package.json rename to examples/cdn/package.json index c785e5676..84ca87ec1 100644 --- a/examples/cdn-example/package.json +++ b/examples/cdn/package.json @@ -1,5 +1,5 @@ { - "name": "@uppy-example/cdn", + "name": "example-cdn", "version": "0.0.0", "private": true, "scripts": { diff --git a/examples/custom-provider/README.md b/examples/companion-custom-provider/README.md similarity index 100% rename from examples/custom-provider/README.md rename to examples/companion-custom-provider/README.md diff --git a/examples/custom-provider/client/MyCustomProvider.jsx b/examples/companion-custom-provider/client/MyCustomProvider.jsx similarity index 95% rename from examples/custom-provider/client/MyCustomProvider.jsx rename to examples/companion-custom-provider/client/MyCustomProvider.jsx index bf08a53d0..52fab29ff 100644 --- a/examples/custom-provider/client/MyCustomProvider.jsx +++ b/examples/companion-custom-provider/client/MyCustomProvider.jsx @@ -1,9 +1,8 @@ /** @jsx h */ +import { getAllowedHosts, Provider, tokenStorage } from '@uppy/companion-client' import { UIPlugin } from '@uppy/core' -import { Provider, getAllowedHosts, tokenStorage } from '@uppy/companion-client' import { ProviderViews } from '@uppy/provider-views' -import { h } from 'preact' const defaultOptions = {} diff --git a/examples/custom-provider/client/main.js b/examples/companion-custom-provider/client/main.js similarity index 100% rename from examples/custom-provider/client/main.js rename to examples/companion-custom-provider/client/main.js index 6d4e27531..839ebf249 100644 --- a/examples/custom-provider/client/main.js +++ b/examples/companion-custom-provider/client/main.js @@ -1,7 +1,7 @@ import Uppy from '@uppy/core' +import Dashboard from '@uppy/dashboard' import GoogleDrive from '@uppy/google-drive' import Tus from '@uppy/tus' -import Dashboard from '@uppy/dashboard' import MyCustomProvider from './MyCustomProvider.jsx' import '@uppy/core/dist/style.css' diff --git a/examples/custom-provider/index.html b/examples/companion-custom-provider/index.html similarity index 100% rename from examples/custom-provider/index.html rename to examples/companion-custom-provider/index.html diff --git a/examples/custom-provider/package.json b/examples/companion-custom-provider/package.json similarity index 88% rename from examples/custom-provider/package.json rename to examples/companion-custom-provider/package.json index afcf1b175..f2b5d8584 100644 --- a/examples/custom-provider/package.json +++ b/examples/companion-custom-provider/package.json @@ -1,6 +1,7 @@ { - "name": "@uppy-example/custom-provider", + "name": "example-companion-custom-provider", "version": "0.0.0", + "private": true, "type": "module", "dependencies": { "@uppy/companion-client": "workspace:*", @@ -16,14 +17,13 @@ }, "devDependencies": { "@uppy/companion": "workspace:*", - "body-parser": "^1.18.2", + "body-parser": "^1.20.3", "dotenv": "^16.0.1", "express": "^4.19.2", "express-session": "^1.15.6", "npm-run-all": "^4.1.2", - "vite": "^5.0.0" + "vite": "^7.0.6" }, - "private": true, "scripts": { "start": "npm-run-all --parallel start:server start:client", "start:client": "vite", diff --git a/examples/custom-provider/server/CustomProvider.cjs b/examples/companion-custom-provider/server/CustomProvider.js similarity index 58% rename from examples/custom-provider/server/CustomProvider.cjs rename to examples/companion-custom-provider/server/CustomProvider.js index 6e4292b14..9a2cbb952 100644 --- a/examples/custom-provider/server/CustomProvider.cjs +++ b/examples/companion-custom-provider/server/CustomProvider.js @@ -2,7 +2,7 @@ const { Readable } = require('node:stream') const BASE_URL = 'https://api.unsplash.com' -function adaptData (res) { +function adaptData(res) { const data = { username: null, items: [], @@ -31,57 +31,48 @@ function adaptData (res) { /** * an example of a custom provider module. It implements @uppy/companion's Provider interface */ -class MyCustomProvider { +export default class MyCustomProvider { static version = 2 - static get oauthProvider () { + static get oauthProvider() { return 'myunsplash' } - // eslint-disable-next-line class-methods-use-this - async list ({ token, directory }) { + async list({ token, directory }) { const path = directory ? `/${directory}/photos` : '' const resp = await fetch(`${BASE_URL}/collections${path}`, { - headers:{ + headers: { Authorization: `Bearer ${token}`, }, }) if (!resp.ok) { - throw new Error(`Errornous HTTP response (${resp.status} ${resp.statusText})`) + throw new Error( + `Errornous HTTP response (${resp.status} ${resp.statusText})`, + ) } return adaptData(await resp.json()) } - // eslint-disable-next-line class-methods-use-this - async download ({ id, token }) { + async download({ id, token }) { const resp = await fetch(`${BASE_URL}/photos/${id}`, { headers: { Authorization: `Bearer ${token}`, }, }) - if (!resp.ok) { - throw new Error(`Errornous HTTP response (${resp.status} ${resp.statusText})`) - } - return { stream: Readable.fromWeb(resp.body) } - } - - // eslint-disable-next-line class-methods-use-this - async size ({ id, token }) { - const resp = await fetch(`${BASE_URL}/photos/${id}`, { - headers: { - Authorization: `Bearer ${token}`, - }, - }) + const contentLengthStr = resp.headers['content-length'] + const contentLength = parseInt(contentLengthStr, 10) + const size = + !Number.isNaN(contentLength) && contentLength >= 0 + ? contentLength + : undefined if (!resp.ok) { - throw new Error(`Errornous HTTP response (${resp.status} ${resp.statusText})`) + throw new Error( + `Errornous HTTP response (${resp.status} ${resp.statusText})`, + ) } - - const { size } = await resp.json() - return size + return { stream: Readable.fromWeb(resp.body), size } } } - -module.exports = MyCustomProvider diff --git a/examples/custom-provider/server/index.cjs b/examples/companion-custom-provider/server/index.cjs similarity index 88% rename from examples/custom-provider/server/index.cjs rename to examples/companion-custom-provider/server/index.cjs index fb9dc91ab..9e0dee72c 100644 --- a/examples/custom-provider/server/index.cjs +++ b/examples/companion-custom-provider/server/index.cjs @@ -2,7 +2,9 @@ const { mkdtempSync } = require('node:fs') const os = require('node:os') const path = require('node:path') -require('dotenv').config({ path: path.join(__dirname, '..', '..', '..', '.env') }) +require('dotenv').config({ + path: path.join(__dirname, '..', '..', '..', '.env'), +}) const express = require('express') // the ../../../packages is just to use the local version // instead of the npm version—in a real app use `require('@uppy/companion')` @@ -14,11 +16,13 @@ const MyCustomProvider = require('./CustomProvider.cjs') const app = express() app.use(bodyParser.json()) -app.use(session({ - secret: 'some-secret', - resave: true, - saveUninitialized: true, -})) +app.use( + session({ + secret: 'some-secret', + resave: true, + saveUninitialized: true, + }), +) // Routes app.get('/', (req, res) => { @@ -71,7 +75,7 @@ app.use((req, res) => { // handle server errors app.use((err, req, res) => { console.error('\x1b[31m', err.stack, '\x1b[0m') - res.status(err.status || 500).json({ message: err.message, error: err }) + res.status(500).json({ message: err.message, error: err }) }) uppy.socket(app.listen(3020), uppyOptions) diff --git a/examples/digitalocean-spaces/.gitignore b/examples/companion-digitalocean-spaces/.gitignore similarity index 100% rename from examples/digitalocean-spaces/.gitignore rename to examples/companion-digitalocean-spaces/.gitignore diff --git a/examples/digitalocean-spaces/README.md b/examples/companion-digitalocean-spaces/README.md similarity index 100% rename from examples/digitalocean-spaces/README.md rename to examples/companion-digitalocean-spaces/README.md diff --git a/examples/digitalocean-spaces/index.html b/examples/companion-digitalocean-spaces/index.html similarity index 100% rename from examples/digitalocean-spaces/index.html rename to examples/companion-digitalocean-spaces/index.html diff --git a/examples/digitalocean-spaces/main.js b/examples/companion-digitalocean-spaces/main.js similarity index 100% rename from examples/digitalocean-spaces/main.js rename to examples/companion-digitalocean-spaces/main.js index 9b050e7ab..a6ade2ae6 100644 --- a/examples/digitalocean-spaces/main.js +++ b/examples/companion-digitalocean-spaces/main.js @@ -1,6 +1,6 @@ +import AwsS3 from '@uppy/aws-s3' import Uppy from '@uppy/core' import Dashboard from '@uppy/dashboard' -import AwsS3 from '@uppy/aws-s3' import '@uppy/core/dist/style.css' import '@uppy/dashboard/dist/style.css' diff --git a/examples/digitalocean-spaces/package.json b/examples/companion-digitalocean-spaces/package.json similarity index 77% rename from examples/digitalocean-spaces/package.json rename to examples/companion-digitalocean-spaces/package.json index 2339161ec..a63155cc7 100644 --- a/examples/digitalocean-spaces/package.json +++ b/examples/companion-digitalocean-spaces/package.json @@ -1,18 +1,18 @@ { - "name": "@uppy-example/digitalocean-spaces", + "name": "example-companion-digitalocean-spaces", "version": "0.0.0", "type": "module", "dependencies": { "@uppy/aws-s3": "workspace:*", "@uppy/core": "workspace:*", "@uppy/dashboard": "workspace:*", - "body-parser": "^1.18.3", + "body-parser": "^1.20.3", "cors": "^2.8.5" }, "devDependencies": { "dotenv": "^16.0.1", "express": "^4.19.2", - "vite": "^5.0.0" + "vite": "^7.0.6" }, "private": true, "scripts": { diff --git a/examples/digitalocean-spaces/server.cjs b/examples/companion-digitalocean-spaces/server.cjs similarity index 65% rename from examples/digitalocean-spaces/server.cjs rename to examples/companion-digitalocean-spaces/server.cjs index 5cbeefc25..3b6765d7a 100644 --- a/examples/digitalocean-spaces/server.cjs +++ b/examples/companion-digitalocean-spaces/server.cjs @@ -17,10 +17,22 @@ const companion = require('../../packages/@uppy/companion') * - COMPANION_AWS_FORCE_PATH_STYLE - Indicates if s3ForcePathStyle should be used rather than subdomain for S3 buckets. */ -if (!process.env.COMPANION_AWS_REGION) throw new Error('Missing Space region, please set the COMPANION_AWS_REGION environment variable (eg. "COMPANION_AWS_REGION=ams3")') -if (!process.env.COMPANION_AWS_KEY) throw new Error('Missing access key, please set the COMPANION_AWS_KEY environment variable') -if (!process.env.COMPANION_AWS_SECRET) throw new Error('Missing secret key, please set the COMPANION_AWS_SECRET environment variable') -if (!process.env.COMPANION_AWS_BUCKET) throw new Error('Missing Space name, please set the COMPANION_AWS_BUCKET environment variable') +if (!process.env.COMPANION_AWS_REGION) + throw new Error( + 'Missing Space region, please set the COMPANION_AWS_REGION environment variable (eg. "COMPANION_AWS_REGION=ams3")', + ) +if (!process.env.COMPANION_AWS_KEY) + throw new Error( + 'Missing access key, please set the COMPANION_AWS_KEY environment variable', + ) +if (!process.env.COMPANION_AWS_SECRET) + throw new Error( + 'Missing secret key, please set the COMPANION_AWS_SECRET environment variable', + ) +if (!process.env.COMPANION_AWS_BUCKET) + throw new Error( + 'Missing Space name, please set the COMPANION_AWS_BUCKET environment variable', + ) // Prepare the server. const PORT = process.env.PORT || 3452 @@ -54,9 +66,11 @@ const { app: companionApp } = companion.app({ app.use('/companion', companionApp) -require('vite').createServer({ clearScreen: false, server:{ middlewareMode: true } }).then(({ middlewares }) => { - app.use(middlewares) - app.listen(PORT, () => { - console.log(`Listening on http://localhost:${PORT}/...`) +require('vite') + .createServer({ clearScreen: false, server: { middlewareMode: true } }) + .then(({ middlewares }) => { + app.use(middlewares) + app.listen(PORT, () => { + console.log(`Listening on http://localhost:${PORT}/...`) + }) }) -}) diff --git a/examples/digitalocean-spaces/setcors.xml b/examples/companion-digitalocean-spaces/setcors.xml similarity index 100% rename from examples/digitalocean-spaces/setcors.xml rename to examples/companion-digitalocean-spaces/setcors.xml diff --git a/examples/uppy-with-companion/.gitignore b/examples/companion/.gitignore similarity index 100% rename from examples/uppy-with-companion/.gitignore rename to examples/companion/.gitignore diff --git a/examples/uppy-with-companion/README.md b/examples/companion/README.md similarity index 100% rename from examples/uppy-with-companion/README.md rename to examples/companion/README.md diff --git a/examples/uppy-with-companion/client/index.html b/examples/companion/client/index.html similarity index 87% rename from examples/uppy-with-companion/client/index.html rename to examples/companion/client/index.html index 638520bb2..26489beb8 100644 --- a/examples/uppy-with-companion/client/index.html +++ b/examples/companion/client/index.html @@ -5,7 +5,7 @@ @@ -19,7 +19,7 @@ Instagram, GoogleDrive, Tus, - } from 'https://releases.transloadit.com/uppy/v4.3.0/uppy.min.mjs' + } from 'https://releases.transloadit.com/uppy/v4.18.0/uppy.min.mjs' const uppy = new Uppy({ debug: true, autoProceed: false }) .use(Dashboard, { trigger: '#uppyModalOpener' }) diff --git a/examples/uppy-with-companion/output/.empty b/examples/companion/output/.empty similarity index 100% rename from examples/uppy-with-companion/output/.empty rename to examples/companion/output/.empty diff --git a/examples/uppy-with-companion/package.json b/examples/companion/package.json similarity index 56% rename from examples/uppy-with-companion/package.json rename to examples/companion/package.json index 310e183cb..0ab988f01 100644 --- a/examples/uppy-with-companion/package.json +++ b/examples/companion/package.json @@ -1,13 +1,12 @@ { - "name": "@uppy-example/uppy-with-companion", + "name": "example-companion", "version": "0.0.0", "dependencies": { "@uppy/companion": "workspace:*", - "body-parser": "^1.18.2", + "body-parser": "^1.20.3", "express": "^4.19.2", "express-session": "^1.15.6", - "light-server": "^2.4.0", - "upload-server": "^1.1.6" + "light-server": "^2.4.0" }, "license": "ISC", "main": "index.js", @@ -15,7 +14,6 @@ "scripts": { "client": "light-server -p 3000 -s client", "server": "node ./server/index.js", - "start": "yarn run server & yarn run client", - "test": "echo \"Error: no test specified\" && exit 1" + "start": "yarn run server & yarn run client" } } diff --git a/examples/uppy-with-companion/server/index.js b/examples/companion/server/index.js similarity index 86% rename from examples/uppy-with-companion/server/index.js rename to examples/companion/server/index.js index 204fc4b27..98c970404 100644 --- a/examples/uppy-with-companion/server/index.js +++ b/examples/companion/server/index.js @@ -6,11 +6,13 @@ const companion = require('@uppy/companion') const app = express() app.use(bodyParser.json()) -app.use(session({ - secret: 'some-secret', - resave: true, - saveUninitialized: true, -})) +app.use( + session({ + secret: 'some-secret', + resave: true, + saveUninitialized: true, + }), +) app.use((req, res, next) => { res.setHeader('Access-Control-Allow-Origin', req.headers.origin || '*') @@ -44,6 +46,7 @@ const companionOptions = { }, // you can also add options for additional providers here }, + corsOrigins: ['*'], // Note: this is not safe for production server: { host: 'localhost:3020', protocol: 'http', @@ -64,7 +67,7 @@ app.use((req, res) => { // handle server errors app.use((err, req, res) => { console.error('\x1b[31m', err.stack, '\x1b[0m') - res.status(err.status || 500).json({ message: err.message, error: err }) + res.status(500).json({ message: err.message, error: err }) }) companion.socket(app.listen(3020)) diff --git a/examples/multiple-instances/README.md b/examples/multiple-instances/README.md deleted file mode 100644 index ca6a469b5..000000000 --- a/examples/multiple-instances/README.md +++ /dev/null @@ -1,23 +0,0 @@ -# Multiple Instances - -This example uses Uppy with the `@uppy/golden-retriever` plugin. It has two -instances on the same page, side-by-side, but with different `id`s so their -stored files don't interfere with each other. - -## Run it - -To run this example, make sure you've correctly installed the **repository -root**: - -```bash -corepack yarn install -corepack yarn build -``` - -That will also install the dependencies for this example. - -Then, again in the **repository root**, start this example by doing: - -```bash -corepack yarn workspace @uppy-example/multiple-instances start -``` diff --git a/examples/multiple-instances/index.html b/examples/multiple-instances/index.html deleted file mode 100644 index 7f23f4115..000000000 --- a/examples/multiple-instances/index.html +++ /dev/null @@ -1,33 +0,0 @@ - - - - - - Uppy example: Multiple instances - - - -
    -
    -

    Instance A

    -
    -
    -
    -

    Instance B

    -
    -
    -
    - - - - - diff --git a/examples/multiple-instances/main.js b/examples/multiple-instances/main.js deleted file mode 100644 index 973432237..000000000 --- a/examples/multiple-instances/main.js +++ /dev/null @@ -1,33 +0,0 @@ -import Uppy from '@uppy/core' -import Dashboard from '@uppy/dashboard' -import GoldenRetriever from '@uppy/golden-retriever' - -import '@uppy/core/dist/style.css' -import '@uppy/dashboard/dist/style.css' - -// Initialise two Uppy instances with the GoldenRetriever plugin, -// but with different `id`s. -const a = new Uppy({ - id: 'a', - debug: true, -}) - .use(Dashboard, { - target: '#a', - inline: true, - width: 400, - }) - .use(GoldenRetriever, { serviceWorker: false }) - -const b = new Uppy({ - id: 'b', - debug: true, -}) - .use(Dashboard, { - target: '#b', - inline: true, - width: 400, - }) - .use(GoldenRetriever, { serviceWorker: false }) - -window.a = a -window.b = b diff --git a/examples/multiple-instances/package.json b/examples/multiple-instances/package.json deleted file mode 100644 index 7fdd29d02..000000000 --- a/examples/multiple-instances/package.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "name": "@uppy-example/multiple-instances", - "version": "0.0.0", - "type": "module", - "dependencies": { - "@uppy/core": "workspace:*", - "@uppy/dashboard": "workspace:*", - "@uppy/golden-retriever": "workspace:*" - }, - "devDependencies": { - "vite": "^5.0.0" - }, - "private": true, - "scripts": { - "start": "vite" - } -} diff --git a/examples/node-xhr/server.js b/examples/node-xhr/server.js deleted file mode 100755 index d62135d37..000000000 --- a/examples/node-xhr/server.js +++ /dev/null @@ -1,52 +0,0 @@ -#!/usr/bin/env node - -/* eslint-disable no-console */ - -import http from 'node:http' -import { fileURLToPath } from 'node:url' -import { mkdir } from 'node:fs/promises' - -import formidable from 'formidable' - -const UPLOAD_DIR = new URL('./uploads/', import.meta.url) - -await mkdir(UPLOAD_DIR, { recursive: true }) - -http.createServer((req, res) => { - const headers = { - 'Content-Type': 'application/json', - 'Access-Control-Allow-Origin': '*', - 'Access-Control-Allow-Methods': 'OPTIONS, POST, GET', - 'Access-Control-Max-Age': 2592000, // 30 days - /** add other headers as per requirement */ - } - - if (req.method === 'OPTIONS') { - res.writeHead(204, headers) - res.end() - return - } - if (req.url === '/upload' && req.method.toLowerCase() === 'post') { - // parse a file upload - const form = formidable({ - keepExtensions: true, - uploadDir: fileURLToPath(UPLOAD_DIR), - }) - - form.parse(req, (err, fields, files) => { - if (err) { - console.log('some error', err) - res.writeHead(200, headers) - res.write(JSON.stringify(err)) - return res.end() - } - const { file:[{ filepath, originalFilename, mimetype, size }] } = files - console.log('saved file', { filepath, originalFilename, mimetype, size }) - res.writeHead(200, headers) - res.write(JSON.stringify({ fields, files })) - return res.end() - }) - } -}).listen(3020, () => { - console.log('server started') -}) diff --git a/examples/react-example/App.tsx b/examples/react-example/App.tsx deleted file mode 100644 index 3387e8045..000000000 --- a/examples/react-example/App.tsx +++ /dev/null @@ -1,91 +0,0 @@ -/* eslint-disable */ -import React from 'react' -import { createRoot } from 'react-dom/client' -import Uppy, { - UIPlugin, - type Meta, - type Body, - type UIPluginOptions, - type State, -} from '@uppy/core' -import Tus from '@uppy/tus' -import Webcam from '@uppy/webcam' -import { Dashboard, useUppyState } from '@uppy/react' - -import '@uppy/core/dist/style.css' -import '@uppy/dashboard/dist/style.css' -import '@uppy/webcam/dist/style.css' - -interface MyPluginOptions extends UIPluginOptions {} - -interface MyPluginState extends Record {} - -// Custom plugin example inside React -class MyPlugin extends UIPlugin< - MyPluginOptions, - M, - B, - MyPluginState -> { - container!: HTMLElement - - constructor(uppy: Uppy, opts?: MyPluginOptions) { - super(uppy, opts) - this.type = 'acquirer' - this.id = this.opts.id || 'TEST' - this.title = 'Test' - } - - override install() { - const { target } = this.opts - if (target) { - this.mount(target, this) - } - } - - override uninstall() { - this.unmount() - } - - override render(state: State, container: HTMLElement) { - // Important: during the initial render is not defined. Safely return. - if (!container) return - createRoot(container).render( -

    React component inside Uppy's Preact UI

    , - ) - } -} - -const metaFields = [ - { id: 'license', name: 'License', placeholder: 'specify license' }, -] - -function createUppy() { - return new Uppy({ restrictions: { requiredMetaFields: ['license'] } }) - .use(Tus, { endpoint: 'https://tusd.tusdemo.net/files/' }) - .use(Webcam) - .use(MyPlugin) -} - -export default function App() { - // IMPORTANT: passing an initaliser function to useState - // to prevent creating a new Uppy instance on every render. - // useMemo is a performance hint, not a guarantee. - const [uppy] = React.useState(createUppy) - // You can access state reactively with useUppyState - const fileCount = useUppyState( - uppy, - (state) => Object.keys(state.files).length, - ) - const totalProgress = useUppyState(uppy, (state) => state.totalProgress) - // Also possible to get the state of all plugins. - const plugins = useUppyState(uppy, (state) => state.plugins) - - return ( - <> -

    File count: {fileCount}

    -

    Total progress: {totalProgress}

    - - - ) -} diff --git a/examples/react-example/README.md b/examples/react-example/README.md deleted file mode 100644 index 8ecd1a362..000000000 --- a/examples/react-example/README.md +++ /dev/null @@ -1,30 +0,0 @@ -# React example - -This is minimal example created to demonstrate how to integrate Uppy in your -React app. - -To spawn the demo, use the following commands: - -```sh -corepack yarn install -corepack yarn build -corepack yarn workspace @uppy-example/react dev -``` - -If you'd like to use a different package manager than Yarn (e.g. npm) to work -with this example, you can extract it from the workspace like this: - -```sh -corepack yarn workspace @uppy-example/react pack - -# The above command should have create a .tgz file, we're going to extract it to -# a new directory outside of the Uppy workspace. -mkdir ../react-example -tar -xzf examples/react-example/package.tgz -C ../react-example --strip-components 1 -rm -f examples/react-example/package.tgz - -# Now you can leave the Uppy workspace and use the example as a standalone JS project: -cd ../react-example -npm i -npm run dev -``` diff --git a/examples/react-example/index.html b/examples/react-example/index.html deleted file mode 100644 index c1ea87c94..000000000 --- a/examples/react-example/index.html +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - Uppy React Example - - - -
    - - - diff --git a/examples/react-example/main.tsx b/examples/react-example/main.tsx deleted file mode 100644 index 5969f2d93..000000000 --- a/examples/react-example/main.tsx +++ /dev/null @@ -1,6 +0,0 @@ -/* eslint-disable */ -import React from 'react' -import { createRoot } from 'react-dom/client' -import App from './App.tsx' - -createRoot(document.querySelector('#app')!).render() diff --git a/examples/react-example/package.json b/examples/react-example/package.json deleted file mode 100644 index d0f96ca09..000000000 --- a/examples/react-example/package.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "name": "@uppy-example/react", - "version": "0.0.0", - "type": "module", - "dependencies": { - "@uppy/core": "workspace:*", - "@uppy/dashboard": "workspace:*", - "@uppy/react": "workspace:*", - "@uppy/remote-sources": "workspace:*", - "@uppy/tus": "workspace:*", - "react": "^18.0.0", - "react-dom": "^18.0.0" - }, - "private": true, - "scripts": { - "dev": "vite", - "build": "vite build", - "preview": "vite preview --port 5050" - }, - "devDependencies": { - "@vitejs/plugin-react": "^4.0.0", - "vite": "^5.0.0" - } -} diff --git a/examples/react-example/tsconfig.json b/examples/react-example/tsconfig.json deleted file mode 100644 index ca9e45c59..000000000 --- a/examples/react-example/tsconfig.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "compilerOptions": { - "jsxImportSource": "react", - "jsx": "react-jsx", - "esModuleInterop": true, - "skipLibCheck": true, - "target": "es2022", - "allowJs": true, - "resolveJsonModule": true, - "moduleDetection": "force", - "isolatedModules": true, - "verbatimModuleSyntax": true, - "strict": true, - "noUncheckedIndexedAccess": true, - "noImplicitOverride": true, - "module": "NodeNext", - "outDir": "dist", - "sourceMap": true, - "lib": ["es2022", "dom", "dom.iterable"], - }, -} diff --git a/examples/react-native-expo/.eslintrc.json b/examples/react-native-expo/.eslintrc.json deleted file mode 100644 index 7d13fa101..000000000 --- a/examples/react-native-expo/.eslintrc.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "rules": { - "react/react-in-jsx-scope": "off", - "no-use-before-define": "off" - } -} diff --git a/examples/react-native-expo/.expo-shared/assets.json b/examples/react-native-expo/.expo-shared/assets.json deleted file mode 100644 index 1e6decfbb..000000000 --- a/examples/react-native-expo/.expo-shared/assets.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "12bb71342c6255bbf50437ec8f4441c083f47cdb74bd89160c15e4f43e52a1cb": true, - "40b842e832070c58deac6aa9e08fa459302ee3f9da492c7e77d93d2fbf4a56fd": true -} diff --git a/examples/react-native-expo/.gitignore b/examples/react-native-expo/.gitignore deleted file mode 100644 index ec8a36a25..000000000 --- a/examples/react-native-expo/.gitignore +++ /dev/null @@ -1,14 +0,0 @@ -node_modules/ -.expo/ -dist/ -npm-debug.* -*.jks -*.p8 -*.p12 -*.key -*.mobileprovision -*.orig.* -web-build/ - -# macOS -.DS_Store diff --git a/examples/react-native-expo/App.js b/examples/react-native-expo/App.js deleted file mode 100644 index f380b4251..000000000 --- a/examples/react-native-expo/App.js +++ /dev/null @@ -1,170 +0,0 @@ -import React, { useEffect, useState, useCallback } from 'react' -import { Text, View, Image, StyleSheet } from 'react-native' -import AsyncStorage from '@react-native-async-storage/async-storage' -import Uppy from '@uppy/core' -import Tus from '@uppy/tus' -import FilePicker from '@uppy/react-native' -import FileList from './FileList' -import PauseResumeButton from './PauseResumeButton' -import ProgressBar from './ProgressBar' -import SelectFiles from './SelectFilesButton' -import getTusFileReader from './tusFileReader' - -export default function App () { - const [state, _setState] = useState({ - progress: 0, - total: 0, - file: null, - uploadURL: null, - isFilePickerVisible: false, - isPaused: false, - uploadStarted: false, - uploadComplete: false, - info: null, - totalProgress: 0, - }) - - const setState = useCallback((newState) => _setState((oldState) => ({ ...oldState, ...newState })), []) - - const [uppy] = useState(() => new Uppy({ autoProceed: true, debug: true }) - .use(Tus, { - endpoint: 'https://tusd.tusdemo.net/files/', - urlStorage: AsyncStorage, - fileReader: getTusFileReader, - chunkSize: 10 * 1024 * 1024, // keep the chunk size small to avoid memory exhaustion - })); - - useEffect(() => { - uppy.on('upload-progress', (file, progress) => { - setState({ - progress: progress.bytesUploaded, - total: progress.bytesTotal, - totalProgress: uppy.state.totalProgress, - uploadStarted: true, - }) - }) - uppy.on('upload-success', () => { - // console.log(file.name, response) - }) - uppy.on('complete', (result) => { - setState({ - status: result.successful[0] ? 'Upload complete ✅' : 'Upload errored ❌', - uploadURL: result.successful[0] ? result.successful[0].uploadURL : null, - uploadComplete: true, - uploadStarted: false, - }) - console.log('Upload complete:', result) - }) - uppy.on('info-visible', () => { - const { info } = uppy.getState() - setState({ - info, - }) - console.log('uppy-info:', info) - }) - uppy.on('info-hidden', () => { - setState({ - info: null, - }) - }) - }, [setState, uppy]) - - const showFilePicker = () => { - setState({ - isFilePickerVisible: true, - uploadStarted: false, - uploadComplete: false, - }) - } - - const hideFilePicker = () => { - setState({ - isFilePickerVisible: false, - }) - } - - const togglePauseResume = () => { - if (state.isPaused) { - uppy.resumeAll() - setState({ - isPaused: false, - }) - } else { - uppy.pauseAll() - setState({ - isPaused: true, - }) - } - } - - return ( - - - Uppy in React Native - - - - - - - {state.info ? ( - - {state.info.message} - - ) : null} - - - - - - {uppy && ( - - )} - - {uppy && } - - {state.status && Status: {state.status}} - {state.progress} of {state.total} - - ) -} - -const styles = StyleSheet.create({ - root: { - paddingTop: 100, - paddingBottom: 20, - paddingLeft: 50, - paddingRight: 50, - flex: 1, - }, - title: { - fontSize: 25, - marginBottom: 20, - textAlign: 'center', - }, - logo: { width: 80, height: 78, marginBottom: 50 }, -}) diff --git a/examples/react-native-expo/FileList.js b/examples/react-native-expo/FileList.js deleted file mode 100644 index 87c3fd381..000000000 --- a/examples/react-native-expo/FileList.js +++ /dev/null @@ -1,126 +0,0 @@ -import React from 'react' -import { StyleSheet, View, FlatList, Text, Image } from 'react-native' - -import getFileTypeIcon from '@uppy/dashboard/lib/utils/getFileTypeIcon.js' -import renderStringFromJSX from 'preact-render-to-string' - -const fileIcon = require('./assets/file-icon.png') - -const truncateString = (str) => { - const maxChars = 20 - if (str.length > maxChars) { - return `${str.substring(0, 25)}...` - } - - return str -} - -function FileIcon () { - return ( - - - - ) -} - -function UppyDashboardFileIcon ({ type }) { - const icon = renderStringFromJSX(getFileTypeIcon(type).icon) - if (!icon) { - return - } - const { color } = getFileTypeIcon(type) - return ( - - logo - - ) -} - -export default function FileList ({ uppy }) { - const uppyFiles = uppy.store.state.files - const uppyFilesArray = Object.keys(uppyFiles).map((id) => uppyFiles[id]) - - return ( - - item.id} - numColumns={2} - renderItem={({ item }) => { - return ( - - {item.type === 'image' ? ( - - ) : ( - - )} - {truncateString(item.name, 20)} - {item.type} - - ) - }} - /> - - ) -} - -const styles = StyleSheet.create({ - container: { - marginTop: 20, - marginBottom: 20, - flex: 1, - justifyContent: 'center', - alignItems:'center', - marginRight: -25, - }, - item: { - width: 100, - marginTop: 5, - marginBottom: 15, - marginRight: 25, - }, - itemImage: { - width: 100, - height: 100, - borderRadius: 5, - marginBottom: 5, - }, - itemIconContainer: { - width: 100, - height: 100, - borderRadius: 5, - marginBottom: 5, - backgroundColor: '#cfd3d6', - alignItems: 'center', - justifyContent: 'center', - }, - itemIcon: { - width: 42, - height: 56, - }, - itemIconSVG: { - width: 50, - height: 50, - }, - itemName: { - fontSize: 13, - color: '#2c3e50', - fontWeight: '600', - }, - itemType: { - fontWeight: '600', - fontSize: 12, - color: '#95a5a6', - }, -}) diff --git a/examples/react-native-expo/PauseResumeButton.js b/examples/react-native-expo/PauseResumeButton.js deleted file mode 100644 index 80ae0bf4b..000000000 --- a/examples/react-native-expo/PauseResumeButton.js +++ /dev/null @@ -1,33 +0,0 @@ -import React from 'react' -import { StyleSheet, Text, TouchableHighlight } from 'react-native' - -export default function PauseResumeButton ({ uploadStarted, uploadComplete, isPaused, onPress }) { - if (!uploadStarted || uploadComplete) { - return null - } - - return ( - - - {isPaused ? 'Resume' : 'Pause'} - - - ) -} - -const styles = StyleSheet.create({ - button: { - backgroundColor: '#cc0077', - padding: 10, - }, - text: { - color: '#fff', - textAlign: 'center', - fontSize: 17, - }, -}) diff --git a/examples/react-native-expo/ProgressBar.js b/examples/react-native-expo/ProgressBar.js deleted file mode 100644 index e8e393046..000000000 --- a/examples/react-native-expo/ProgressBar.js +++ /dev/null @@ -1,37 +0,0 @@ -import React from 'react' -import { View, Text, StyleSheet } from 'react-native' - -const colorGreen = '#0b8600' -const colorBlue = '#006bb7' - -export default function ProgressBar ({ progress }) { - return ( - - - - - {progress ? `${progress}%` : null} - - ) -} - -const styles = StyleSheet.create({ - root: { - marginTop: 15, - marginBottom: 15, - }, - wrapper:{ - height: 5, - overflow: 'hidden', - backgroundColor: '#dee1e3', - }, - bar: { - height: 5, - }, -}) diff --git a/examples/react-native-expo/SelectFilesButton.js b/examples/react-native-expo/SelectFilesButton.js deleted file mode 100644 index b147b0f5f..000000000 --- a/examples/react-native-expo/SelectFilesButton.js +++ /dev/null @@ -1,25 +0,0 @@ -import React from 'react' -import { Text, TouchableHighlight, StyleSheet } from 'react-native' - -export default function SelectFiles ({ showFilePicker }) { - return ( - - Select files - - ) -} - -const styles = StyleSheet.create({ - button: { - backgroundColor: '#cc0077', - padding: 15, - }, - text: { - color: '#fff', - textAlign: 'center', - fontSize: 17, - }, -}) diff --git a/examples/react-native-expo/app.json b/examples/react-native-expo/app.json deleted file mode 100644 index f9755f55e..000000000 --- a/examples/react-native-expo/app.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "expo": { - "name": "react-native-expo", - "slug": "react-native-expo", - "version": "1.0.0", - "orientation": "portrait", - "icon": "./assets/icon.png", - "splash": { - "image": "./assets/splash.png", - "resizeMode": "contain", - "backgroundColor": "#ffffff" - }, - "updates": { - "fallbackToCacheTimeout": 0 - }, - "assetBundlePatterns": ["**/*"], - "ios": { - "supportsTablet": true - }, - "android": { - "adaptiveIcon": { - "foregroundImage": "./assets/adaptive-icon.png", - "backgroundColor": "#FFFFFF" - } - }, - "web": { - "favicon": "./assets/favicon.png" - } - } -} diff --git a/examples/react-native-expo/assets/adaptive-icon.png b/examples/react-native-expo/assets/adaptive-icon.png deleted file mode 100644 index 03d6f6b6c..000000000 Binary files a/examples/react-native-expo/assets/adaptive-icon.png and /dev/null differ diff --git a/examples/react-native-expo/assets/favicon.png b/examples/react-native-expo/assets/favicon.png deleted file mode 100644 index e75f697b1..000000000 Binary files a/examples/react-native-expo/assets/favicon.png and /dev/null differ diff --git a/examples/react-native-expo/assets/file-icon.png b/examples/react-native-expo/assets/file-icon.png deleted file mode 100644 index e263aa608..000000000 Binary files a/examples/react-native-expo/assets/file-icon.png and /dev/null differ diff --git a/examples/react-native-expo/assets/icon.png b/examples/react-native-expo/assets/icon.png deleted file mode 100644 index 3f5bbc0ca..000000000 Binary files a/examples/react-native-expo/assets/icon.png and /dev/null differ diff --git a/examples/react-native-expo/assets/splash.png b/examples/react-native-expo/assets/splash.png deleted file mode 100644 index 4f9ade699..000000000 Binary files a/examples/react-native-expo/assets/splash.png and /dev/null differ diff --git a/examples/react-native-expo/assets/uppy-logo.png b/examples/react-native-expo/assets/uppy-logo.png deleted file mode 100644 index b4556d135..000000000 Binary files a/examples/react-native-expo/assets/uppy-logo.png and /dev/null differ diff --git a/examples/react-native-expo/babel.config.js b/examples/react-native-expo/babel.config.js deleted file mode 100644 index 9f057eaaa..000000000 --- a/examples/react-native-expo/babel.config.js +++ /dev/null @@ -1,6 +0,0 @@ -module.exports = (api) => { - api.cache(true) - return { - presets: ['babel-preset-expo'], - } -} diff --git a/examples/react-native-expo/index.js b/examples/react-native-expo/index.js deleted file mode 100644 index a8644b15a..000000000 --- a/examples/react-native-expo/index.js +++ /dev/null @@ -1,8 +0,0 @@ -import { registerRootComponent } from 'expo' - -import App from './App' - -// registerRootComponent calls AppRegistry.registerComponent('main', () => App); -// It also ensures that whether you load the app in Expo Go or in a native build, -// the environment is set up appropriately -registerRootComponent(App) diff --git a/examples/react-native-expo/metro.config.js b/examples/react-native-expo/metro.config.js deleted file mode 100644 index 87788da11..000000000 --- a/examples/react-native-expo/metro.config.js +++ /dev/null @@ -1,22 +0,0 @@ -// Learn more https://docs.expo.dev/guides/monorepos -const { getDefaultConfig } = require('expo/metro-config') -const path = require('node:path') - -// Find the project and workspace directories -const projectRoot = __dirname -// This can be replaced with `find-yarn-workspace-root` -const workspaceRoot = path.resolve(projectRoot, '../../') - -const config = getDefaultConfig(projectRoot) - -// 1. Watch all files within the monorepo -config.watchFolders = [workspaceRoot] -// 2. Let Metro know where to resolve packages and in what order -config.resolver.nodeModulesPaths = [ - path.resolve(projectRoot, 'node_modules'), - path.resolve(workspaceRoot, 'node_modules'), -] -// 3. Force Metro to resolve (sub)dependencies only from the `nodeModulesPaths` -config.resolver.disableHierarchicalLookup = true - -module.exports = config diff --git a/examples/react-native-expo/package.json b/examples/react-native-expo/package.json deleted file mode 100644 index 2a7bcb5d1..000000000 --- a/examples/react-native-expo/package.json +++ /dev/null @@ -1,36 +0,0 @@ -{ - "name": "@uppy-example/react-native-expo", - "version": "1.0.0", - "main": "index.js", - "scripts": { - "start": "expo start", - "android": "expo start --android", - "ios": "expo start --ios", - "web": "expo start --web", - "eject": "expo eject" - }, - "dependencies": { - "@react-native-async-storage/async-storage": "~1.15.0", - "@uppy/core": "workspace:*", - "@uppy/dashboard": "workspace:*", - "@uppy/instagram": "workspace:*", - "@uppy/react": "workspace:*", - "@uppy/react-native": "workspace:*", - "@uppy/tus": "workspace:*", - "@uppy/url": "workspace:*", - "@uppy/xhr-upload": "workspace:*", - "base64-js": "^1.3.0", - "expo": "~43.0.2", - "expo-file-system": "~13.0.3", - "expo-status-bar": "~1.1.0", - "preact-render-to-string": "^5.1.0", - "react": "17.0.1", - "react-dom": "17.0.1", - "react-native": "0.64.3", - "react-native-web": "0.17.1" - }, - "devDependencies": { - "@babel/core": "^7.12.9" - }, - "private": true -} diff --git a/examples/react-native-expo/readme.md b/examples/react-native-expo/readme.md deleted file mode 100644 index aa2933f0b..000000000 --- a/examples/react-native-expo/readme.md +++ /dev/null @@ -1,29 +0,0 @@ -# Uppy in React Native Expo (Beta) - -⚠️ In Beta - -`@uppy/react-native` is a basic Uppy component for React Native with Expo. It is -in Beta, and is not full-featured. You can select local images or videos, take a -picture with a camera or add any file from a remote url with Uppy Companion. - -## Run it - -To run this example, make sure you've correctly installed the **repository -root**: - -```bash -yarn install -yarn run build -``` - -That will also install the dependencies for this example. - -Then, start this example by doing: - -```bash -cd examples/react-native-expo -yarn start -``` - -Then you'll see a menu within your terminal where you can chose where to open -the app (Android, iOS, device etc.) diff --git a/examples/react-native-expo/tusFileReader.js b/examples/react-native-expo/tusFileReader.js deleted file mode 100644 index fc672ac1f..000000000 --- a/examples/react-native-expo/tusFileReader.js +++ /dev/null @@ -1,26 +0,0 @@ -import * as FileSystem from 'expo-file-system' -import base64 from 'base64-js' - -export default function getTusFileReader (file, chunkSize, cb) { - FileSystem.getInfoAsync(file.uri, { size: true }).then((info) => { - cb(null, new TusFileReader(file, info.size)) - }).catch(cb) -} - -class TusFileReader { - constructor (file, size) { - this.file = file - this.size = size - } - - slice (start, end, cb) { - const options = { - encoding: FileSystem.EncodingType.Base64, - length: Math.min(end, this.size) - start, - position: start, - } - FileSystem.readAsStringAsync(this.file.uri, options).then((data) => { - cb(null, base64.toByteArray(data)) - }).catch(cb) - } -} diff --git a/examples/react/.stackblitzrc b/examples/react/.stackblitzrc new file mode 100644 index 000000000..370683512 --- /dev/null +++ b/examples/react/.stackblitzrc @@ -0,0 +1,7 @@ +{ + "installDependencies": false, + "startCommand": "node -e \"const fs=require('fs'); const p='package.json'; const j=JSON.parse(fs.readFileSync(p,'utf8')); ['dependencies','devDependencies','peerDependencies'].forEach(k=>{ if(j[k]) for(const n in j[k]) if(typeof j[k][n]==='string' && j[k][n].startsWith('workspace:')) j[k][n]='latest'; }); fs.writeFileSync(p, JSON.stringify(j,null,2));\" && npm install && npm run dev", + "env": { + "NODE_ENV": "development" + } +} \ No newline at end of file diff --git a/examples/react/index.html b/examples/react/index.html new file mode 100644 index 000000000..ba530a2c7 --- /dev/null +++ b/examples/react/index.html @@ -0,0 +1,12 @@ + + + + + + Vite App + + +
    + + + diff --git a/examples/react/package.json b/examples/react/package.json new file mode 100644 index 000000000..3e052b0da --- /dev/null +++ b/examples/react/package.json @@ -0,0 +1,34 @@ +{ + "name": "example-react", + "private": true, + "type": "module", + "scripts": { + "build": "tsc && vite build", + "test": "vitest run --browser.headless", + "dev": "vite", + "serve": "vite preview" + }, + "dependencies": { + "@tailwindcss/vite": "^4.1.11", + "@uppy/core": "workspace:*", + "@uppy/react": "workspace:*", + "@uppy/remote-sources": "workspace:*", + "@uppy/screen-capture": "workspace:*", + "@uppy/tus": "workspace:*", + "@uppy/webcam": "workspace:*", + "react": "^19.0.0", + "react-dom": "^19.0.0", + "tailwindcss": "^4.0.9" + }, + "devDependencies": { + "@types/react": "^19.0.10", + "@types/react-dom": "^19.0.4", + "@vitejs/plugin-react": "^4.6.0", + "@vitest/browser": "^3.2.4", + "playwright": "1.54.1", + "typescript": "^5.7.3", + "vite": "^7.0.6", + "vitest": "^3.2.4", + "vitest-browser-react": "^1.0.0" + } +} diff --git a/examples/react/src/App.tsx b/examples/react/src/App.tsx new file mode 100644 index 000000000..d9d639d2c --- /dev/null +++ b/examples/react/src/App.tsx @@ -0,0 +1,95 @@ +/** biome-ignore-all lint/nursery/useUniqueElementIds: it's fine */ +import Uppy from '@uppy/core' +import { + Dropzone, + FilesGrid, + FilesList, + UploadButton, + UppyContextProvider, +} from '@uppy/react' +import UppyRemoteSources from '@uppy/remote-sources' +import UppyScreenCapture from '@uppy/screen-capture' +import Tus from '@uppy/tus' +import UppyWebcam from '@uppy/webcam' +import { useRef, useState } from 'react' +import CustomDropzone from './CustomDropzone' +import { RemoteSource } from './RemoteSource' +import ScreenCapture from './ScreenCapture' +import Webcam from './Webcam' + +import './app.css' +import '@uppy/react/css/style.css' + +function App() { + const [uppy] = useState(() => + new Uppy() + .use(Tus, { + endpoint: 'https://tusd.tusdemo.net/files/', + }) + .use(UppyWebcam) + .use(UppyScreenCapture) + .use(UppyRemoteSources, { companionUrl: 'http://localhost:3020' }), + ) + + const dialogRef = useRef(null) + const [modalPlugin, setModalPlugin] = useState< + 'webcam' | 'dropbox' | 'screen-capture' | null + >(null) + + function openModal(plugin: 'webcam' | 'dropbox' | 'screen-capture') { + setModalPlugin(plugin) + dialogRef.current?.showModal() + } + + function closeModal() { + setModalPlugin(null) + dialogRef.current?.close() + } + + return ( + +
    +

    Welcome to React.

    + + + + + {(() => { + switch (modalPlugin) { + case 'webcam': + return closeModal()} /> + case 'dropbox': + return closeModal()} /> + case 'screen-capture': + return closeModal()} /> + default: + return null + } + })()} + + +
    +

    With list

    + + +
    + +
    +

    With grid

    + + +
    + +
    +

    With custom dropzone

    + openModal(plugin)} /> +
    +
    +
    + ) +} + +export default App diff --git a/examples/react/src/CustomDropzone.tsx b/examples/react/src/CustomDropzone.tsx new file mode 100644 index 000000000..79dad0efa --- /dev/null +++ b/examples/react/src/CustomDropzone.tsx @@ -0,0 +1,68 @@ +import { ProviderIcon, useDropzone, useFileInput } from '@uppy/react' + +export interface CustomDropzoneProps { + openModal: (plugin: 'webcam' | 'dropbox' | 'screen-capture') => void +} + +export function CustomDropzone({ openModal }: CustomDropzoneProps) { + const { getRootProps, getInputProps } = useDropzone({ noClick: true }) + const { getButtonProps, getInputProps: getFileInputProps } = useFileInput() + + return ( +
    + +
    +
    + + + + + + + + +
    +
    +
    + ) +} + +export default CustomDropzone diff --git a/examples/react/src/MediaCapture.tsx b/examples/react/src/MediaCapture.tsx new file mode 100644 index 000000000..6e9a34286 --- /dev/null +++ b/examples/react/src/MediaCapture.tsx @@ -0,0 +1,106 @@ +type ButtonProps = { + type: 'button' + onClick: () => void + disabled: boolean +} + +interface ErrorDisplayProps { + error: Error | null +} + +function ErrorDisplay({ error }: ErrorDisplayProps) { + if (!error) return null + + const errorMessage = error.message + ? `Camera error: ${error.message}` + : 'An unknown camera error occurred.' + + return ( +
    +

    Error

    +

    {errorMessage}

    +
    + ) +} + +export interface MediaCaptureProps { + title: string + close: () => void + getVideoProps: () => Record + getPrimaryActionButtonProps: () => ButtonProps + primaryActionButtonLabel: string + mediaError: Error | null + getRecordButtonProps: () => ButtonProps + getStopRecordingButtonProps: () => ButtonProps + getSubmitButtonProps: () => ButtonProps + getDiscardButtonProps: () => ButtonProps +} + +export function MediaCapture({ + title, + close, + getVideoProps, + getPrimaryActionButtonProps, + primaryActionButtonLabel, + getRecordButtonProps, + mediaError, + getStopRecordingButtonProps, + getSubmitButtonProps, + getDiscardButtonProps, +}: MediaCaptureProps) { + return ( +
    +
    +

    {title}

    + +
    + + +
    + + + + + +
    +
    + ) +} + +export default MediaCapture diff --git a/examples/react/src/RemoteSource.tsx b/examples/react/src/RemoteSource.tsx new file mode 100644 index 000000000..e5e1f893d --- /dev/null +++ b/examples/react/src/RemoteSource.tsx @@ -0,0 +1,186 @@ +import type { PartialTreeFile, PartialTreeFolderNode } from '@uppy/core' +import { useRemoteSource } from '@uppy/react' +import type { AvailablePluginsKeys } from '@uppy/remote-sources' +import { useEffect, useRef } from 'react' + +const dtf = new Intl.DateTimeFormat('en-US', { + dateStyle: 'short', + timeStyle: 'short', +}) + +function File({ + item, + checkbox, +}: { + item: PartialTreeFile + checkbox: (item: PartialTreeFile, checked: boolean) => void +}) { + return ( +
  • + checkbox(item, false)} + checked={item.status === 'checked'} + /> + {item.data.thumbnail && ( + + )} +
    {item.data.name}
    +

    + {dtf.format(new Date(item.data.modifiedDate))} +

    +
  • + ) +} +function Folder({ + item, + checkbox, + open, +}: { + item: PartialTreeFolderNode + checkbox: (item: PartialTreeFolderNode, checked: boolean) => void + open: (folderId: string | null) => Promise +}) { + const ref = useRef(null) + + useEffect(() => { + if (ref.current && item.status === 'partial') { + // Can only be set via JS + ref.current.indeterminate = true + } + }, [item.status]) + + return ( +
  • + checkbox(item, false)} + checked={item.status === 'checked'} + /> + +
  • + ) +} + +export function RemoteSource({ + close, + id, +}: { + close: () => void + id: AvailablePluginsKeys +}) { + const { state, login, logout, checkbox, open, done, cancel } = + useRemoteSource(id) + + if (!state.authenticated) { + return ( +
    + +
    + ) + } + + return ( +
    +
    + {state.breadcrumbs.map((breadcrumb, index) => ( + <> + {index > 0 && >}{' '} + {index === state.breadcrumbs.length - 1 ? ( + + {breadcrumb.type === 'root' ? 'Dropbox' : breadcrumb.data.name} + + ) : ( + + )} + + ))} +
    + +
    +
    + +
      + {state.loading ? ( +

      loading...

      + ) : ( + state.partialTree.map((item) => { + if (item.type === 'file') { + return + } + if (item.type === 'folder') { + return ( + + ) + } + return null + }) + )} +
    + + {state.selectedAmount > 0 && ( +
    + + +

    + Selected {state.selectedAmount} items +

    +
    + )} +
    + ) +} diff --git a/examples/react/src/ScreenCapture.tsx b/examples/react/src/ScreenCapture.tsx new file mode 100644 index 000000000..019ddf797 --- /dev/null +++ b/examples/react/src/ScreenCapture.tsx @@ -0,0 +1,45 @@ +import { useScreenCapture } from '@uppy/react' +import { useEffect } from 'react' +import MediaCapture from './MediaCapture.tsx' + +export interface ScreenCaptureProps { + close: () => void +} + +export function ScreenCapture({ close }: ScreenCaptureProps) { + const { + start, + stop, + getVideoProps, + getScreenshotButtonProps, + getRecordButtonProps, + getStopRecordingButtonProps, + getSubmitButtonProps, + getDiscardButtonProps, + state, + } = useScreenCapture({ onSubmit: close }) + + useEffect(() => { + start() + return () => { + stop() + } + }, [start, stop]) + + return ( + + ) +} + +export default ScreenCapture diff --git a/examples/react/src/Webcam.tsx b/examples/react/src/Webcam.tsx new file mode 100644 index 000000000..4a35f3c0f --- /dev/null +++ b/examples/react/src/Webcam.tsx @@ -0,0 +1,45 @@ +import { useWebcam } from '@uppy/react' +import { useEffect } from 'react' +import MediaCapture from './MediaCapture.tsx' + +export interface WebcamProps { + close: () => void +} + +export function Webcam({ close }: WebcamProps) { + const { + start, + stop, + getVideoProps, + getSnapshotButtonProps, + getRecordButtonProps, + getStopRecordingButtonProps, + getSubmitButtonProps, + getDiscardButtonProps, + state, + } = useWebcam({ onSubmit: close }) + + useEffect(() => { + start() + return () => { + stop() + } + }, [start, stop]) + + return ( + + ) +} + +export default Webcam diff --git a/examples/react/src/app.css b/examples/react/src/app.css new file mode 100644 index 000000000..ea3c0bdb3 --- /dev/null +++ b/examples/react/src/app.css @@ -0,0 +1,6 @@ +@import "tailwindcss"; + +/* example how to use the data attributes to apply custom styles */ +/* button[data-uppy-element="upload-button"][data-state="uploading"] { + background-color: red; +} */ diff --git a/examples/react/src/main.tsx b/examples/react/src/main.tsx new file mode 100644 index 000000000..5ca69bda3 --- /dev/null +++ b/examples/react/src/main.tsx @@ -0,0 +1,9 @@ +import React from 'react' +import ReactDOM from 'react-dom/client' +import App from './App.js' + +ReactDOM.createRoot(document.getElementById('root')!).render( + + + , +) diff --git a/examples/react/src/vite-env.d.ts b/examples/react/src/vite-env.d.ts new file mode 100644 index 000000000..11f02fe2a --- /dev/null +++ b/examples/react/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/examples/react/test/index.test.tsx b/examples/react/test/index.test.tsx new file mode 100644 index 000000000..4a5573a8d --- /dev/null +++ b/examples/react/test/index.test.tsx @@ -0,0 +1,124 @@ +import { userEvent } from '@vitest/browser/context' +import { describe, expect, test } from 'vitest' +import { render } from 'vitest-browser-react' +import App from '../src/App' + +const createMockFile = (name: string, type: string, size: number = 1024) => { + return new File(['test content'], name, { type }) +} + +describe('App', () => { + test('renders all main sections and upload button is initially disabled', async () => { + const screen = render() + + await expect.element(screen.getByText('With list')).toBeInTheDocument() + await expect.element(screen.getByText('With grid')).toBeInTheDocument() + await expect + .element(screen.getByText('With custom dropzone')) + .toBeInTheDocument() + + const uploadButton = screen.getByRole('button', { name: /upload/i }) + await expect.element(uploadButton).toBeInTheDocument() + await expect.element(uploadButton).toBeDisabled() + }) + + test('can add and remove files and upload', async () => { + const screen = render() + + const fileInput = document.getElementById( + 'uppy-dropzone-file-input', + ) as Element + await userEvent.upload(fileInput, createMockFile('test.txt', 'text/plain')) + + // for list and grid + for (const element of screen.getByText('test.txt').elements()) { + await expect.element(element).toBeInTheDocument() + } + await screen.getByText('remove').first().click() + for (const element of screen.getByText('test.txt').elements()) { + await expect.element(element).not.toBeInTheDocument() + } + + await userEvent.upload(fileInput, createMockFile('test.txt', 'text/plain')) + await screen.getByRole('button', { name: /upload/i }).click() + await expect + .element(screen.getByRole('button', { name: /complete/i })) + .toBeInTheDocument() + }) +}) + +describe('ScreenCapture Component', () => { + test('renders with title, control buttons, and close functionality works', async () => { + const screen = render() + + await screen.getByRole('button', { name: 'Screen Capture' }).click() + + await expect + .element(screen.getByRole('heading', { name: 'Screen Capture' })) + .toBeInTheDocument() + + await expect + .element(screen.getByRole('button', { name: 'Screenshot' })) + .toBeInTheDocument() + await expect + .element(screen.getByRole('button', { name: 'Record' })) + .toBeInTheDocument() + await expect + .element(screen.getByRole('button', { name: 'Stop' })) + .toBeInTheDocument() + await expect + .element(screen.getByRole('button', { name: 'Submit' })) + .toBeInTheDocument() + await expect + .element(screen.getByRole('button', { name: 'Discard' })) + .toBeInTheDocument() + + const closeButton = screen.getByText('✕') + await closeButton.click() + }) +}) + +describe('Webcam Component', () => { + test('renders with title, control buttons, and close functionality works', async () => { + const screen = render() + + await screen.getByRole('button', { name: 'Webcam' }).click() + + await expect + .element(screen.getByRole('heading', { name: 'Camera' })) + .toBeInTheDocument() + + await expect + .element(screen.getByRole('button', { name: 'Snapshot' })) + .toBeInTheDocument() + await expect + .element(screen.getByRole('button', { name: 'Record' })) + .toBeInTheDocument() + await expect + .element(screen.getByRole('button', { name: 'Stop' })) + .toBeInTheDocument() + await expect + .element(screen.getByRole('button', { name: 'Submit' })) + .toBeInTheDocument() + await expect + .element(screen.getByRole('button', { name: 'Discard' })) + .toBeInTheDocument() + + const closeButton = screen.getByText('✕') + await closeButton.click() + }) +}) + +describe('RemoteSource Component', () => { + test('renders login button and login interaction works', async () => { + const screen = render() + + await screen.getByRole('button', { name: 'Dropbox' }).click() + + const loginButton = screen.getByRole('button', { name: 'Login' }) + await expect.element(loginButton).toBeInTheDocument() + + await loginButton.click() + await expect.element(loginButton).toBeInTheDocument() + }) +}) diff --git a/examples/react/tsconfig.json b/examples/react/tsconfig.json new file mode 100644 index 000000000..f17a78ed2 --- /dev/null +++ b/examples/react/tsconfig.json @@ -0,0 +1,22 @@ +{ + "compilerOptions": { + "target": "ESNext", + "useDefineForClassFields": true, + "lib": ["DOM", "DOM.Iterable", "ESNext"], + "allowJs": false, + "skipLibCheck": true, + "esModuleInterop": false, + "allowSyntheticDefaultImports": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "allowImportingTsExtensions": true, + "module": "ESNext", + "moduleResolution": "Node", + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx" + }, + "include": ["src", "src/**/*.tsx"], + "references": [{ "path": "./tsconfig.node.json" }] +} diff --git a/examples/react/tsconfig.node.json b/examples/react/tsconfig.node.json new file mode 100644 index 000000000..e993792cb --- /dev/null +++ b/examples/react/tsconfig.node.json @@ -0,0 +1,8 @@ +{ + "compilerOptions": { + "composite": true, + "module": "esnext", + "moduleResolution": "node" + }, + "include": ["vite.config.ts"] +} diff --git a/examples/react-example/vite.config.js b/examples/react/vite.config.ts similarity index 51% rename from examples/react-example/vite.config.js rename to examples/react/vite.config.ts index 5a33944a9..6535e3b1f 100644 --- a/examples/react-example/vite.config.js +++ b/examples/react/vite.config.ts @@ -1,7 +1,9 @@ -import { defineConfig } from 'vite' +// @ts-expect-error untyped for some reason but fine +import tailwindcss from '@tailwindcss/vite' import react from '@vitejs/plugin-react' +import { defineConfig } from 'vite' // https://vitejs.dev/config/ export default defineConfig({ - plugins: [react()], + plugins: [react(), tailwindcss()], }) diff --git a/examples/react/vitest.config.ts b/examples/react/vitest.config.ts new file mode 100644 index 000000000..b054ef272 --- /dev/null +++ b/examples/react/vitest.config.ts @@ -0,0 +1,14 @@ +import tailwindcss from '@tailwindcss/vite' +import react from '@vitejs/plugin-react' +import { defineConfig } from 'vitest/config' + +export default defineConfig({ + plugins: [react(), tailwindcss()], + test: { + browser: { + enabled: true, + provider: 'playwright', + instances: [{ browser: 'chromium' }], + }, + }, +}) diff --git a/examples/redux/README.md b/examples/redux/README.md deleted file mode 100644 index 991066bf6..000000000 --- a/examples/redux/README.md +++ /dev/null @@ -1,28 +0,0 @@ -# Redux - -This example uses Uppy with a Redux store. The same Redux store is also used for -other parts of the application, namely the counter example. Each action is -logged to the console using -[redux-logger](https://github.com/theaqua/redux-logger). - -This example supports the -[Redux Devtools extension](https://github.com/zalmoxisus/redux-devtools-extension), -including time travel. - -## Run it - -To run this example, make sure you've correctly installed the **repository -root**: - -```sh -corepack yarn install -corepack yarn build -``` - -That will also install the dependencies for this example. - -Then, again in the **repository root**, start this example by doing: - -```sh -corepack yarn workspace @uppy-example/redux start -``` diff --git a/examples/redux/index.html b/examples/redux/index.html deleted file mode 100644 index 54e51d371..000000000 --- a/examples/redux/index.html +++ /dev/null @@ -1,27 +0,0 @@ - - - - - - Uppy example: Redux - - -
    -

    A counter

    -
    -

    - Clicked: 0 times - - - - -

    -
    -

    An Uppy

    -
    -
    - - - - - diff --git a/examples/redux/main.js b/examples/redux/main.js deleted file mode 100644 index 3deafacbb..000000000 --- a/examples/redux/main.js +++ /dev/null @@ -1,95 +0,0 @@ -import { compose, combineReducers, applyMiddleware } from 'redux' -import { configureStore } from '@reduxjs/toolkit' -import logger from 'redux-logger' -import Uppy from '@uppy/core' -import ReduxStore from '@uppy/store-redux' -import * as uppyReduxStore from '@uppy/store-redux' -import Dashboard from '@uppy/dashboard' -import Tus from '@uppy/tus' - -import '@uppy/core/dist/style.css' -import '@uppy/dashboard/dist/style.css' - -function counter (state = 0, action) { - switch (action.type) { - case 'INCREMENT': - return state + 1 - case 'DECREMENT': - return state - 1 - default: - return state - } -} - -const reducer = combineReducers({ - counter, - // You don't have to use the `uppy` key. But if you don't, - // you need to provide a custom `selector` to the `uppyReduxStore` call below. - uppy: uppyReduxStore.reducer, -}) - -let enhancer = applyMiddleware( - uppyReduxStore.middleware(), - logger, -) -if (typeof __REDUX_DEVTOOLS_EXTENSION__ !== 'undefined') { - // eslint-disable-next-line no-undef - enhancer = compose(enhancer, __REDUX_DEVTOOLS_EXTENSION__()) -} - -const store = configureStore({ - reducer, - enhancers: [enhancer], - middleware: (getDefaultMiddleware) => getDefaultMiddleware({ - serializableCheck: { - ignoredActions: [uppyReduxStore.STATE_UPDATE], - ignoreState: true, - }, - }), -}) - -// Counter example from https://github.com/reactjs/redux/blob/master/examples/counter-vanilla/index.html -const valueEl = document.querySelector('#value') - -function getCounter () { return store.getState().counter } -function render () { - valueEl.innerHTML = getCounter().toString() -} -render() -store.subscribe(render) - -document.querySelector('#increment').onclick = () => { - store.dispatch({ type: 'INCREMENT' }) -} -document.querySelector('#decrement').onclick = () => { - store.dispatch({ type: 'DECREMENT' }) -} -document.querySelector('#incrementIfOdd').onclick = () => { - if (getCounter() % 2 !== 0) { - store.dispatch({ type: 'INCREMENT' }) - } -} -document.querySelector('#incrementAsync').onclick = () => { - setTimeout(() => store.dispatch({ type: 'INCREMENT' }), 1000) -} - -// Uppy using the same store -const uppy = new Uppy({ - id: 'redux', - store: new ReduxStore({ store }), - // If we had placed our `reducer` elsewhere in Redux, eg. under an `uppy` key in the state for a profile page, - // we'd do something like: - // - // store: new ReduxStore({ - // store: store, - // id: 'avatar', - // selector: state => state.pages.profile.uppy - // }), - debug: true, -}) -uppy.use(Dashboard, { - target: '#app', - inline: true, - width: 400, -}) -uppy.use(Tus, { endpoint: 'https://tusd.tusdemo.net/files/' }) diff --git a/examples/redux/package.json b/examples/redux/package.json deleted file mode 100644 index 501dd8941..000000000 --- a/examples/redux/package.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "name": "@uppy-example/redux", - "version": "0.0.0", - "type": "module", - "dependencies": { - "@reduxjs/toolkit": "^1.9.3", - "@uppy/core": "workspace:*", - "@uppy/dashboard": "workspace:*", - "@uppy/store-redux": "workspace:*", - "@uppy/tus": "workspace:*", - "redux": "^4.2.1", - "redux-logger": "^3.0.6" - }, - "devDependencies": { - "vite": "^5.0.0" - }, - "private": true, - "scripts": { - "start": "vite" - } -} diff --git a/examples/svelte-example/.gitignore b/examples/svelte-example/.gitignore deleted file mode 100644 index 8c39b2ae7..000000000 --- a/examples/svelte-example/.gitignore +++ /dev/null @@ -1,12 +0,0 @@ -/node_modules/ -/uploads/ - -.DS_Store -/build/ -/.svelte-kit -/package -.env -.env.* -!.env.example -vite.config.js.timestamp-* -vite.config.ts.timestamp-* diff --git a/examples/svelte-example/README.md b/examples/svelte-example/README.md deleted file mode 100644 index 957e96796..000000000 --- a/examples/svelte-example/README.md +++ /dev/null @@ -1,17 +0,0 @@ -# Uppy with Svelte - -## Run it - -To run this example, make sure you've correctly installed the **repository -root**: - -```sh -corepack yarn install -corepack yarn build -``` - -Then, again in the **repository root**, start this example by doing: - -```sh -corepack yarn workspace @uppy-example/svelte-app dev -``` diff --git a/examples/svelte-example/package.json b/examples/svelte-example/package.json deleted file mode 100644 index 003d7ed23..000000000 --- a/examples/svelte-example/package.json +++ /dev/null @@ -1,37 +0,0 @@ -{ - "name": "@uppy-example/svelte-app", - "version": "0.0.0", - "scripts": { - "dev": "npm-run-all --parallel dev:frontend dev:backend", - "dev:frontend": "vite dev", - "dev:backend": "node --watch ./server.js", - "build": "vite build", - "preview": "vite preview", - "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", - "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch" - }, - "devDependencies": { - "@sveltejs/adapter-static": "^3.0.1", - "@sveltejs/kit": "^2.0.0", - "@sveltejs/vite-plugin-svelte": "^3.0.0", - "@types/formidable": "^3", - "npm-run-all": "^4.1.5", - "svelte": "^4.2.7", - "svelte-check": "^3.6.0", - "tslib": "^2.4.1", - "typescript": "~5.4", - "vite": "^5.0.0" - }, - "dependencies": { - "@uppy/core": "workspace:*", - "@uppy/dashboard": "workspace:*", - "@uppy/drag-drop": "workspace:*", - "@uppy/progress-bar": "workspace:*", - "@uppy/svelte": "workspace:*", - "@uppy/webcam": "workspace:*", - "@uppy/xhr-upload": "workspace:*", - "formidable": "^3.5.1" - }, - "type": "module", - "private": true -} diff --git a/examples/svelte-example/src/routes/+layout.ts b/examples/svelte-example/src/routes/+layout.ts deleted file mode 100644 index 7f52afb88..000000000 --- a/examples/svelte-example/src/routes/+layout.ts +++ /dev/null @@ -1,6 +0,0 @@ -import '@uppy/core/dist/style.min.css' -import '@uppy/dashboard/dist/style.min.css' -import '@uppy/drag-drop/dist/style.min.css' -import '@uppy/progress-bar/dist/style.min.css' - -export const ssr = false diff --git a/examples/svelte-example/src/routes/+page.svelte b/examples/svelte-example/src/routes/+page.svelte deleted file mode 100644 index 90c2753d0..000000000 --- a/examples/svelte-example/src/routes/+page.svelte +++ /dev/null @@ -1,69 +0,0 @@ - - -
    -

    Welcome to the @uppy/svelte demo!

    -

    Inline Dashboard

    - - {#if showInlineDashboard} - - {/if} -

    Modal Dashboard

    -
    - - open = false, - plugins: ['Webcam'] - }} - /> -
    - -

    Drag Drop Area

    - - -

    Progress Bar

    - -
    - \ No newline at end of file diff --git a/examples/sveltekit/.gitignore b/examples/sveltekit/.gitignore new file mode 100644 index 000000000..3b462cb0c --- /dev/null +++ b/examples/sveltekit/.gitignore @@ -0,0 +1,23 @@ +node_modules + +# Output +.output +.vercel +.netlify +.wrangler +/.svelte-kit +/build + +# OS +.DS_Store +Thumbs.db + +# Env +.env +.env.* +!.env.example +!.env.test + +# Vite +vite.config.js.timestamp-* +vite.config.ts.timestamp-* diff --git a/examples/sveltekit/.npmrc b/examples/sveltekit/.npmrc new file mode 100644 index 000000000..b6f27f135 --- /dev/null +++ b/examples/sveltekit/.npmrc @@ -0,0 +1 @@ +engine-strict=true diff --git a/examples/sveltekit/.stackblitzrc b/examples/sveltekit/.stackblitzrc new file mode 100644 index 000000000..370683512 --- /dev/null +++ b/examples/sveltekit/.stackblitzrc @@ -0,0 +1,7 @@ +{ + "installDependencies": false, + "startCommand": "node -e \"const fs=require('fs'); const p='package.json'; const j=JSON.parse(fs.readFileSync(p,'utf8')); ['dependencies','devDependencies','peerDependencies'].forEach(k=>{ if(j[k]) for(const n in j[k]) if(typeof j[k][n]==='string' && j[k][n].startsWith('workspace:')) j[k][n]='latest'; }); fs.writeFileSync(p, JSON.stringify(j,null,2));\" && npm install && npm run dev", + "env": { + "NODE_ENV": "development" + } +} \ No newline at end of file diff --git a/examples/sveltekit/README.md b/examples/sveltekit/README.md new file mode 100644 index 000000000..ff0d9bde6 --- /dev/null +++ b/examples/sveltekit/README.md @@ -0,0 +1,41 @@ +# sv + +Everything you need to build a Svelte project, powered by +[`sv`](https://github.com/sveltejs/cli). + +## Creating a project + +If you're seeing this, you've probably already done this step. Congrats! + +```bash +# create a new project in the current directory +npx sv create + +# create a new project in my-app +npx sv create my-app +``` + +## Developing + +Once you've created a project and installed dependencies with `npm install` (or +`pnpm install` or `yarn`), start a development server: + +```bash +npm run dev + +# or start the server and open the app in a new browser tab +npm run dev -- --open +``` + +## Building + +To create a production version of your app: + +```bash +npm run build +``` + +You can preview the production build with `npm run preview`. + +> To deploy your app, you may need to install an +> [adapter](https://svelte.dev/docs/kit/adapters) for your target environment. diff --git a/examples/sveltekit/package.json b/examples/sveltekit/package.json new file mode 100644 index 000000000..8e9919a41 --- /dev/null +++ b/examples/sveltekit/package.json @@ -0,0 +1,38 @@ +{ + "name": "example-sveltekit", + "private": true, + "version": "0.0.1", + "type": "module", + "scripts": { + "dev": "vite dev", + "build": "vite build", + "test": "vitest run --browser.headless", + "preview": "vite preview", + "prepare": "svelte-kit sync || echo ''", + "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", + "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch" + }, + "dependencies": { + "@uppy/core": "workspace:*", + "@uppy/dashboard": "workspace:*", + "@uppy/screen-capture": "workspace:*", + "@uppy/svelte": "workspace:*", + "@uppy/tus": "workspace:*", + "@uppy/webcam": "workspace:*" + }, + "devDependencies": { + "@sveltejs/adapter-auto": "^4.0.0", + "@sveltejs/kit": "^2.16.0", + "@sveltejs/vite-plugin-svelte": "^5.0.0", + "@tailwindcss/vite": "^4.0.0", + "@vitest/browser": "^3.2.4", + "playwright": "1.54.1", + "svelte": "^5.0.0", + "svelte-check": "^4.0.0", + "tailwindcss": "^4.0.0", + "typescript": "^5.0.0", + "vite": "^6.2.5", + "vitest": "^3.2.4", + "vitest-browser-svelte": "^1.0.0" + } +} diff --git a/examples/sveltekit/src/app.css b/examples/sveltekit/src/app.css new file mode 100644 index 000000000..f1d8c73cd --- /dev/null +++ b/examples/sveltekit/src/app.css @@ -0,0 +1 @@ +@import "tailwindcss"; diff --git a/examples/svelte-example/src/app.d.ts b/examples/sveltekit/src/app.d.ts similarity index 82% rename from examples/svelte-example/src/app.d.ts rename to examples/sveltekit/src/app.d.ts index 367926a58..c0c081684 100644 --- a/examples/svelte-example/src/app.d.ts +++ b/examples/sveltekit/src/app.d.ts @@ -1,4 +1,4 @@ -// See https://kit.svelte.dev/docs/types#app +// See https://svelte.dev/docs/kit/types#app.d.ts // for information about these interfaces declare global { namespace App { diff --git a/examples/svelte-example/src/app.html b/examples/sveltekit/src/app.html similarity index 66% rename from examples/svelte-example/src/app.html rename to examples/sveltekit/src/app.html index e39ebf140..84ffad166 100644 --- a/examples/svelte-example/src/app.html +++ b/examples/sveltekit/src/app.html @@ -2,10 +2,11 @@ + %sveltekit.head% - %sveltekit.body% +
    %sveltekit.body%
    diff --git a/examples/sveltekit/src/components/CustomDropzone.svelte b/examples/sveltekit/src/components/CustomDropzone.svelte new file mode 100644 index 000000000..75de4d9e3 --- /dev/null +++ b/examples/sveltekit/src/components/CustomDropzone.svelte @@ -0,0 +1,64 @@ + + +
    + +
    +
    + + + + + + + + +
    +
    +
    diff --git a/examples/sveltekit/src/components/MediaCapture.svelte b/examples/sveltekit/src/components/MediaCapture.svelte new file mode 100644 index 000000000..cb68b6391 --- /dev/null +++ b/examples/sveltekit/src/components/MediaCapture.svelte @@ -0,0 +1,81 @@ + + +
    +
    +

    {title}

    + +
    + {#if mediaError} +
    +

    Error

    +

    {mediaError.message ? `Camera error: ${mediaError.message}` : 'An unknown camera error occurred.'}

    +
    + {/if} + +
    + + + + + +
    +
    diff --git a/examples/sveltekit/src/components/RemoteSource.svelte b/examples/sveltekit/src/components/RemoteSource.svelte new file mode 100644 index 000000000..cc151800e --- /dev/null +++ b/examples/sveltekit/src/components/RemoteSource.svelte @@ -0,0 +1,150 @@ + + +{#if !remoteSource.state.authenticated} +
    + +
    +{:else} +
    +
    + {#each remoteSource.state.breadcrumbs as breadcrumb, index (breadcrumb.id)} + {#if index > 0} + > + {/if} + {#if index === remoteSource.state.breadcrumbs.length - 1} + + {breadcrumb.type === 'root' ? id : breadcrumb.data.name} + + {:else} + + {/if} + {/each} +
    + +
    +
    + +
      + {#if remoteSource.state.loading} +

      loading...

      + {:else} + {#each remoteSource.state.partialTree as item (item.id)} + {#if item.type === 'file'} + +
    • + remoteSource.checkbox(item, false)} + checked={item.status === 'checked'} + /> + {#if item.data.thumbnail} + + {/if} +
      {item.data.name}
      +

      + {dtf.format(new Date(item.data.modifiedDate))} +

      +
    • + {:else if item.type === 'folder'} + +
    • + remoteSource.checkbox(item, false)} + checked={item.status === 'checked'} + /> + +
    • + {/if} + {/each} + {/if} +
    + + {#if remoteSource.state.selectedAmount > 0} +
    + + +

    + Selected {remoteSource.state.selectedAmount} items +

    +
    + {/if} +
    +{/if} diff --git a/examples/sveltekit/src/components/ScreenCapture.svelte b/examples/sveltekit/src/components/ScreenCapture.svelte new file mode 100644 index 000000000..91d85a8a3 --- /dev/null +++ b/examples/sveltekit/src/components/ScreenCapture.svelte @@ -0,0 +1,32 @@ + + + diff --git a/examples/sveltekit/src/components/Webcam.svelte b/examples/sveltekit/src/components/Webcam.svelte new file mode 100644 index 000000000..b0dd9d834 --- /dev/null +++ b/examples/sveltekit/src/components/Webcam.svelte @@ -0,0 +1,32 @@ + + + diff --git a/examples/sveltekit/src/routes/+layout.svelte b/examples/sveltekit/src/routes/+layout.svelte new file mode 100644 index 000000000..39dd3e176 --- /dev/null +++ b/examples/sveltekit/src/routes/+layout.svelte @@ -0,0 +1,7 @@ + + +{@render children()} diff --git a/examples/sveltekit/src/routes/+page.svelte b/examples/sveltekit/src/routes/+page.svelte new file mode 100644 index 000000000..a134ca9ed --- /dev/null +++ b/examples/sveltekit/src/routes/+page.svelte @@ -0,0 +1,81 @@ + + + +
    +

    Welcome to SvelteKit.

    + + + + + {#if modalPlugin === 'webcam'} + + {/if} + {#if modalPlugin === 'dropbox'} + + {/if} + {#if modalPlugin === 'screen-capture'} + + {/if} + + +
    +

    With list

    + + +
    + +
    +

    With grid

    + + +
    + +
    +

    With custom dropzone

    + +
    +
    +
    diff --git a/examples/sveltekit/src/routes/+page.ts b/examples/sveltekit/src/routes/+page.ts new file mode 100644 index 000000000..62ad4e4f4 --- /dev/null +++ b/examples/sveltekit/src/routes/+page.ts @@ -0,0 +1 @@ +export const ssr = false diff --git a/examples/sveltekit/static/favicon.png b/examples/sveltekit/static/favicon.png new file mode 100644 index 000000000..825b9e65a Binary files /dev/null and b/examples/sveltekit/static/favicon.png differ diff --git a/examples/sveltekit/svelte.config.js b/examples/sveltekit/svelte.config.js new file mode 100644 index 000000000..e92eb5e12 --- /dev/null +++ b/examples/sveltekit/svelte.config.js @@ -0,0 +1,18 @@ +import adapter from '@sveltejs/adapter-auto' +import { vitePreprocess } from '@sveltejs/vite-plugin-svelte' + +/** @type {import('@sveltejs/kit').Config} */ +const config = { + // Consult https://svelte.dev/docs/kit/integrations + // for more information about preprocessors + preprocess: vitePreprocess(), + + kit: { + // adapter-auto only supports some environments, see https://svelte.dev/docs/kit/adapter-auto for a list. + // If your environment is not supported, or you settled on a specific environment, switch out the adapter. + // See https://svelte.dev/docs/kit/adapters for more information about adapters. + adapter: adapter(), + }, +} + +export default config diff --git a/examples/sveltekit/test/index.test.ts b/examples/sveltekit/test/index.test.ts new file mode 100644 index 000000000..7a33fb9e5 --- /dev/null +++ b/examples/sveltekit/test/index.test.ts @@ -0,0 +1,126 @@ +import { userEvent } from '@vitest/browser/context' +import { describe, expect, test } from 'vitest' +import { render } from 'vitest-browser-svelte' +import App from '../src/routes/+page.svelte' + +const createMockFile = (name: string, type: string, size: number = 1024) => { + return new File(['test content'], name, { type }) +} + +describe('App', () => { + test('renders all main sections and upload button is initially disabled', async () => { + const screen = render(App) + + await expect.element(screen.getByText('With list')).toBeInTheDocument() + await expect.element(screen.getByText('With grid')).toBeInTheDocument() + await expect + .element(screen.getByText('With custom dropzone')) + .toBeInTheDocument() + + const uploadButton = screen.getByRole('button', { name: /upload/i }) + await expect.element(uploadButton).toBeInTheDocument() + await expect.element(uploadButton).toBeDisabled() + }) + + test('can add and remove files and upload', async () => { + const screen = render(App) + + const fileInput = document.getElementById( + 'uppy-dropzone-file-input', + ) as Element + await userEvent.upload(fileInput, createMockFile('test.txt', 'text/plain')) + + // for list and grid + for (const element of screen.getByText('test.txt').elements()) { + await expect.element(element).toBeInTheDocument() + } + await screen.getByText('remove').first().click() + for (const element of screen.getByText('test.txt').elements()) { + await expect.element(element).not.toBeInTheDocument() + } + + await userEvent.upload(fileInput, createMockFile('test.txt', 'text/plain')) + await screen.getByRole('button', { name: /upload/i }).click() + await expect + .element(screen.getByRole('button', { name: /complete/i })) + .toBeInTheDocument() + }) +}) + +describe('ScreenCapture Component', () => { + test('renders with title, control buttons, and close functionality works', async () => { + const screen = render(App) + + await screen + .getByRole('button', { name: 'Screen Capture', exact: true }) + .click() + + await expect + .element(screen.getByRole('heading', { name: 'Screen Capture' })) + .toBeInTheDocument() + + await expect + .element(screen.getByRole('button', { name: 'Screenshot' })) + .toBeInTheDocument() + await expect + .element(screen.getByRole('button', { name: 'Record' })) + .toBeInTheDocument() + await expect + .element(screen.getByRole('button', { name: 'Stop' })) + .toBeInTheDocument() + await expect + .element(screen.getByRole('button', { name: 'Submit' })) + .toBeInTheDocument() + await expect + .element(screen.getByRole('button', { name: 'Discard' })) + .toBeInTheDocument() + + const closeButton = screen.getByText('✕') + await closeButton.click() + }) +}) + +describe('Webcam Component', () => { + test('renders with title, control buttons, and close functionality works', async () => { + const screen = render(App) + + await screen.getByRole('button', { name: 'Webcam', exact: true }).click() + + await expect + .element(screen.getByRole('heading', { name: 'Camera' })) + .toBeInTheDocument() + + await expect + .element(screen.getByRole('button', { name: 'Snapshot' })) + .toBeInTheDocument() + await expect + .element(screen.getByRole('button', { name: 'Record' })) + .toBeInTheDocument() + await expect + .element(screen.getByRole('button', { name: 'Stop' })) + .toBeInTheDocument() + await expect + .element(screen.getByRole('button', { name: 'Submit' })) + .toBeInTheDocument() + await expect + .element(screen.getByRole('button', { name: 'Discard' })) + .toBeInTheDocument() + + const closeButton = screen.getByText('✕') + await closeButton.click() + }) +}) + +describe('RemoteSource Component', () => { + test('renders login button and login interaction works', async () => { + const screen = render(App) + + await screen.getByRole('button', { name: 'Dropbox', exact: true }).click() + + const loginButton = screen.getByRole('button', { name: 'Login' }) + await expect.element(loginButton).toBeInTheDocument() + + await loginButton.click() + await expect.element(loginButton).toBeInTheDocument() + }) +}) diff --git a/examples/svelte-example/tsconfig.json b/examples/sveltekit/tsconfig.json similarity index 72% rename from examples/svelte-example/tsconfig.json rename to examples/sveltekit/tsconfig.json index d42a3c13e..68383fa08 100644 --- a/examples/svelte-example/tsconfig.json +++ b/examples/sveltekit/tsconfig.json @@ -9,11 +9,11 @@ "skipLibCheck": true, "sourceMap": true, "strict": true, - "module": "ESNext", - "moduleResolution": "bundler", - }, - // Path aliases are handled by https://kit.svelte.dev/docs/configuration#alias - // except $lib which is handled by https://kit.svelte.dev/docs/configuration#files + "module": "preserve", + "moduleResolution": "bundler" + } + // Path aliases are handled by https://svelte.dev/docs/kit/configuration#alias + // except $lib which is handled by https://svelte.dev/docs/kit/configuration#files // // If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes // from the referenced tsconfig.json - TypeScript does not merge them in diff --git a/examples/sveltekit/vite.config.ts b/examples/sveltekit/vite.config.ts new file mode 100644 index 000000000..85c6af9d2 --- /dev/null +++ b/examples/sveltekit/vite.config.ts @@ -0,0 +1,7 @@ +import { sveltekit } from '@sveltejs/kit/vite' +import tailwindcss from '@tailwindcss/vite' +import { defineConfig } from 'vite' + +export default defineConfig({ + plugins: [tailwindcss(), sveltekit()], +}) diff --git a/examples/sveltekit/vitest.config.ts b/examples/sveltekit/vitest.config.ts new file mode 100644 index 000000000..924d3f7db --- /dev/null +++ b/examples/sveltekit/vitest.config.ts @@ -0,0 +1,14 @@ +import { sveltekit } from '@sveltejs/kit/vite' +import tailwindcss from '@tailwindcss/vite' +import { defineConfig } from 'vite' + +export default defineConfig({ + plugins: [tailwindcss(), sveltekit()], + test: { + browser: { + enabled: true, + provider: 'playwright', + instances: [{ browser: 'chromium' }], + }, + }, +}) diff --git a/examples/transloadit-markdown-bin/README.md b/examples/transloadit-markdown-bin/README.md deleted file mode 100644 index 5a8f2f878..000000000 --- a/examples/transloadit-markdown-bin/README.md +++ /dev/null @@ -1,21 +0,0 @@ -# Uppy Markdown Editor - -This example uses Uppy to handle images in a markdown editor. - -## Run it - -To run this example, make sure you've correctly installed the **repository -root**: - -```sh -corepack yarn install -corepack yarn build -``` - -That will also install the dependencies for this example. - -Then, again in the **repository root**, start this example by doing: - -```sh -corepack yarn workspace @uppy-example/transloadit-markdown-bin start -``` diff --git a/examples/transloadit-markdown-bin/index.html b/examples/transloadit-markdown-bin/index.html deleted file mode 100644 index 3868967f2..000000000 --- a/examples/transloadit-markdown-bin/index.html +++ /dev/null @@ -1,173 +0,0 @@ - - - - - - - -
    -

    Markdown Bin

    -
    -
    -

    - Markdown Bin is a demo app that works a bit like Github Gists or - pastebin. You can add markdown snippets, and add file attachments to - each snippet by clicking "Upload an attachment" or by dragging files - onto the text area. Transloadit generates an inline preview image for - images, videos, and audio files. - » View the Assembly Template here. -

    - -

    - ⚠️ For this demo, snippets are stored locally in your browser. - Attachments are stored in Transloadit's temporary storage and expire - after about 24 hours. In a real app, you can easily export files to a - permanent storage solution like Amazon S3 or Google Cloud. - » Learn more -

    - -
    -

    Create a new snippet

    - - -

    - -

    -
    - -

    Previous snippets

    - -
    -
    - - - - - diff --git a/examples/transloadit-markdown-bin/main.js b/examples/transloadit-markdown-bin/main.js deleted file mode 100644 index bd7016e05..000000000 --- a/examples/transloadit-markdown-bin/main.js +++ /dev/null @@ -1,184 +0,0 @@ -import { marked } from 'marked' -import Uppy from '@uppy/core' -import DropTarget from '@uppy/drop-target' -import Dashboard from '@uppy/dashboard' -import Transloadit from '@uppy/transloadit' -import RemoteSources from '@uppy/remote-sources' -import Webcam from '@uppy/webcam' -import ImageEditor from '@uppy/image-editor' - -import '@uppy/core/dist/style.css' -import '@uppy/dashboard/dist/style.css' -import '@uppy/image-editor/dist/style.css' - -const TRANSLOADIT_EXAMPLE_KEY = '35c1aed03f5011e982b6afe82599b6a0' -const TRANSLOADIT_EXAMPLE_TEMPLATE = '0b2ee2bc25dc43619700c2ce0a75164a' - -function matchFilesAndThumbs (results) { - const filesById = {} - const thumbsById = {} - - for (const [stepName, result] of Object.entries(results)) { - // eslint-disable-next-line no-shadow - result.forEach(result => { - if (stepName === 'thumbnails') { - thumbsById[result.original_id] = result - } else { - filesById[result.original_id] = result - } - }) - } - - return Object.keys(filesById).map((key) => ({ - file: filesById[key], - thumb: thumbsById[key], - })) -} - -/** - * A textarea for markdown text, with support for file attachments. - */ -class MarkdownTextarea { - constructor (element) { - this.element = element - this.controls = document.createElement('div') - this.controls.classList.add('mdtxt-controls') - this.uploadLine = document.createElement('button') - this.uploadLine.setAttribute('type', 'button') - this.uploadLine.classList.add('form-upload') - - this.uploadLine.appendChild( - document.createTextNode('Tap here to upload an attachment'), - ) - } - - install () { - const { element } = this - const wrapper = document.createElement('div') - wrapper.classList.add('mdtxt') - element.parentNode.replaceChild(wrapper, element) - wrapper.appendChild(this.controls) - wrapper.appendChild(element) - wrapper.appendChild(this.uploadLine) - - this.setupUppy() - } - - setupUppy = () => { - this.uppy = new Uppy({ autoProceed: true }) - .use(Transloadit, { - waitForEncoding: true, - params: { - auth: { key: TRANSLOADIT_EXAMPLE_KEY }, - template_id: TRANSLOADIT_EXAMPLE_TEMPLATE, - }, - }) - .use(DropTarget, { target: this.element }) - .use(Dashboard, { closeAfterFinish: true, trigger: '.form-upload' }) - .use(ImageEditor, { target: Dashboard }) - .use(Webcam, { target: Dashboard }) - .use(RemoteSources, { companionUrl: Transloadit.COMPANION }) - - this.uppy.on('complete', (result) => { - const { successful, failed, transloadit } = result - if (successful.length !== 0) { - this.insertAttachments( - matchFilesAndThumbs(transloadit[0].results), - ) - } else { - failed.forEach(error => { - console.error(error) - this.reportUploadError(error) - }) - } - this.uppy.cancelAll() - }) - } - - reportUploadError (err) { - this.uploadLine.classList.add('error') - const message = document.createElement('span') - message.appendChild(document.createTextNode(err.message)) - this.uploadLine.insertChild(message, this.uploadLine.firstChild) - } - - unreportUploadError () { - this.uploadLine.classList.remove('error') - const message = this.uploadLine.querySelector('message') - if (message) { - this.uploadLine.removeChild(message) - } - } - - insertAttachments (attachments) { - attachments.forEach((attachment) => { - const { file, thumb } = attachment - const link = `\n[LABEL](${file.ssl_url})\n` - const labelText = `View File ${file.basename}` - if (thumb) { - this.element.value += link.replace('LABEL', `![${labelText}](${thumb.ssl_url})`) - } else { - this.element.value += link.replace('LABEL', labelText) - } - }) - } - - uploadFiles = (files) => { - const filesForUppy = files.map(file => { - return { - data: file, - type: file.type, - name: file.name, - meta: file.meta || {}, - } - }) - this.uppy.addFiles(filesForUppy) - } -} - -const textarea = new MarkdownTextarea(document.querySelector('#new textarea')) -textarea.install() - -function renderSnippet (title, text) { - const template = document.querySelector('#snippet') - const newSnippet = document.importNode(template.content, true) - const titleEl = newSnippet.querySelector('.snippet-title') - const contentEl = newSnippet.querySelector('.snippet-content') - - titleEl.appendChild(document.createTextNode(title)) - contentEl.innerHTML = marked.parse(text) - - const list = document.querySelector('#snippets') - list.insertBefore(newSnippet, list.firstChild) -} - -function saveSnippet (title, text) { - const id = parseInt(localStorage.numSnippets || 0, 10) - localStorage[`snippet_${id}`] = JSON.stringify({ title, text }) - localStorage.numSnippets = id + 1 -} - -function loadSnippets () { - for (let id = 0; localStorage[`snippet_${id}`] != null; id += 1) { - const { title, text } = JSON.parse(localStorage[`snippet_${id}`]) - renderSnippet(title, text) - } -} - -document.querySelector('#new').addEventListener('submit', (event) => { - event.preventDefault() - - const title = event.target.elements['title'].value - || 'Unnamed Snippet' - const text = textarea.element.value - - saveSnippet(title, text) - renderSnippet(title, text) - - // eslint-disable-next-line no-param-reassign - event.target.querySelector('input').value = '' - // eslint-disable-next-line no-param-reassign - event.target.querySelector('textarea').value = '' -}) - -window.addEventListener('DOMContentLoaded', loadSnippets, { once: true }) diff --git a/examples/transloadit-markdown-bin/package.json b/examples/transloadit-markdown-bin/package.json deleted file mode 100644 index 00a956f54..000000000 --- a/examples/transloadit-markdown-bin/package.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "name": "@uppy-example/transloadit-markdown-bin", - "version": "0.0.0", - "type": "module", - "dependencies": { - "@uppy/core": "workspace:*", - "@uppy/dashboard": "workspace:*", - "@uppy/drop-target": "workspace:*", - "@uppy/image-editor": "workspace:*", - "@uppy/remote-sources": "workspace:*", - "@uppy/transloadit": "workspace:*", - "@uppy/webcam": "workspace:*", - "marked": "^12.0.0" - }, - "devDependencies": { - "vite": "^5.0.0" - }, - "private": true, - "scripts": { - "start": "vite" - } -} diff --git a/examples/transloadit/index.html b/examples/transloadit/index.html index ac0cf2a5e..a3919ae1f 100644 --- a/examples/transloadit/index.html +++ b/examples/transloadit/index.html @@ -37,7 +37,7 @@

    Uppy Transloadit diff --git a/examples/transloadit/main.js b/examples/transloadit/main.js index 8470c630a..667a8d63e 100644 --- a/examples/transloadit/main.js +++ b/examples/transloadit/main.js @@ -1,16 +1,14 @@ -import Transloadit, { COMPANION_URL } from '@uppy/transloadit' import Uppy from '@uppy/core' -import Form from '@uppy/form' import Dashboard from '@uppy/dashboard' -import RemoteSources from '@uppy/remote-sources' +import Form from '@uppy/form' import ImageEditor from '@uppy/image-editor' +import RemoteSources from '@uppy/remote-sources' +import Transloadit, { COMPANION_URL } from '@uppy/transloadit' import Webcam from '@uppy/webcam' -import ProgressBar from '@uppy/progress-bar' import '@uppy/core/dist/style.css' import '@uppy/dashboard/dist/style.css' import '@uppy/image-editor/dist/style.css' -import '@uppy/progress-bar/dist/style.css' const TRANSLOADIT_KEY = '35c1aed03f5011e982b6afe82599b6a0' // A trivial template that resizes images, just for example purposes. @@ -59,13 +57,11 @@ const formUppy = new Uppy({ }) formUppy.on('error', (err) => { - document.querySelector('#test-form .error') - .textContent = err.message + document.querySelector('#test-form .error').textContent = err.message }) formUppy.on('upload-error', (file, err) => { - document.querySelector('#test-form .error') - .textContent = err.message + document.querySelector('#test-form .error').textContent = err.message }) formUppy.on('complete', ({ transloadit }) => { @@ -163,15 +159,13 @@ const dashboardModal = new Uppy({ dashboardModal.on('complete', ({ transloadit, successful, failed }) => { if (failed?.length !== 0) { - // eslint-disable-next-line no-console console.error('it failed', failed) } else { - // eslint-disable-next-line no-console console.log('success', { transloadit, successful }) } }) -function openModal () { +function openModal() { dashboardModal.getPlugin('Dashboard').openModal() } @@ -186,15 +180,13 @@ const uppyWithoutUI = new Uppy({ restrictions: { allowedFileTypes: ['.png'], }, +}).use(Transloadit, { + waitForEncoding: true, + params: { + auth: { key: TRANSLOADIT_KEY }, + template_id: TEMPLATE_ID, + }, }) - .use(Transloadit, { - waitForEncoding: true, - params: { - auth: { key: TRANSLOADIT_KEY }, - template_id: TEMPLATE_ID, - }, - }) - .use(ProgressBar, { target: '#upload-progress' }) window.doUpload = (event) => { const resultEl = document.querySelector('#upload-result') @@ -208,7 +200,7 @@ window.doUpload = (event) => { errorEl.classList.add('hidden') resultEl.textContent = JSON.stringify(transloadit[0].results, null, 2) - const resizedUrl = transloadit[0].results['resize'][0]['ssl_url'] + const resizedUrl = transloadit[0].results.resize[0].ssl_url const img = document.createElement('img') img.src = resizedUrl document.getElementById('upload-result-image').appendChild(img) diff --git a/examples/transloadit/package.json b/examples/transloadit/package.json index 205b2f373..02c43d5da 100644 --- a/examples/transloadit/package.json +++ b/examples/transloadit/package.json @@ -1,10 +1,10 @@ { - "name": "@uppy-example/transloadit", + "name": "example-transloadit", "version": "0.0.0", "type": "module", "devDependencies": { "npm-run-all": "^4.1.5", - "vite": "^5.0.0" + "vite": "^7.0.6" }, "dependencies": { "@uppy/core": "workspace:*", @@ -12,7 +12,6 @@ "@uppy/drop-target": "workspace:*", "@uppy/form": "workspace:*", "@uppy/image-editor": "workspace:*", - "@uppy/progress-bar": "workspace:*", "@uppy/remote-sources": "workspace:*", "@uppy/transloadit": "workspace:*", "@uppy/webcam": "workspace:*", diff --git a/examples/transloadit/server.js b/examples/transloadit/server.js index 0769b2bf0..765301bd5 100755 --- a/examples/transloadit/server.js +++ b/examples/transloadit/server.js @@ -1,13 +1,12 @@ #!/usr/bin/env node -/* eslint-disable compat/compat */ import http from 'node:http' import qs from 'node:querystring' import he from 'he' const e = he.encode -function Header () { +function Header() { return ` @@ -28,7 +27,7 @@ function Header () { ` } -function Footer () { +function Footer() { return ` @@ -36,31 +35,29 @@ function Footer () { ` } -function FormFields (fields) { - function Field ([name, value]) { +function FormFields(fields) { + // biome-ignore lint/nursery/noNestedComponentDefinitions: not a react component + function Field([name, value]) { if (name === 'transloadit') return '' let isValueJSON = false if (value.startsWith('{') || value.startsWith('[')) { try { - // eslint-disable-next-line no-param-reassign - value = JSON.stringify( - JSON.parse(value), - null, - 2, - ) + value = JSON.stringify(JSON.parse(value), null, 2) isValueJSON = true } catch { // Nothing } } - const prettyValue = isValueJSON ? ` + const prettyValue = isValueJSON + ? `
    ${e(value)}
    - ` : e(value) + ` + : e(value) return `
    ${e(name)}
    @@ -78,8 +75,9 @@ function FormFields (fields) { ` } -function UploadsList (uploads) { - function Upload (upload) { +function UploadsList(uploads) { + // biome-ignore lint/nursery/noNestedComponentDefinitions: not a react component + function Upload(upload) { return `
  • ${e(upload.name)}
  • ` } @@ -90,12 +88,14 @@ function UploadsList (uploads) { ` } -function ResultsList (results) { - function Result (result) { +function ResultsList(results) { + // biome-ignore lint/nursery/noNestedComponentDefinitions: not a react component + function Result(result) { return `
  • ${e(result.name)} View
  • ` } - function ResultsSection (stepName) { + // biome-ignore lint/nursery/noNestedComponentDefinitions: not a react component + function ResultsSection(stepName) { return `

    ${e(stepName)}