├── .browserslistrc ├── .editorconfig ├── .eslintignore ├── .eslintrc.yaml ├── .github ├── assets │ ├── bytescale-upload-widget-react.svg │ ├── demo.webp │ └── logo.svg └── workflows │ └── ci.yml ├── .gitignore ├── .husky └── pre-commit ├── .prettierignore ├── .prettierrc ├── BUILD.md ├── LICENSE ├── MIGRATE.md ├── README.md ├── babel.config.js ├── jest.config.js ├── jest.config.pretest.js ├── package-lock.json ├── package.json ├── src ├── BasicMouseEvent.ts ├── UploadButton.tsx ├── UploadDropzone.tsx ├── UploadWidgetReactConfig.ts ├── dev │ ├── components │ │ └── MyApp.tsx │ ├── index.html │ └── index.tsx ├── hooks │ ├── UseAutoUpdatingOptions.ts │ ├── UseElementRef.ts │ └── UseObjectDep.ts ├── index.cdn.ts └── index.ts ├── tests ├── UploadButton.test.tsx └── UploadDropzone.test.tsx ├── tsconfig.build.json ├── tsconfig.json ├── webpack.config.cdn.js ├── webpack.config.dev.js └── webpack.config.js /.browserslistrc: -------------------------------------------------------------------------------- 1 | > .01% 2 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # top-most EditorConfig file 2 | root = true 3 | 4 | # Unix-style newlines with a newline ending every file 5 | [*] 6 | end_of_line = lf 7 | insert_final_newline = true 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | indent_style = space 11 | indent_size = 2 12 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | coverage 4 | -------------------------------------------------------------------------------- /.eslintrc.yaml: -------------------------------------------------------------------------------- 1 | # ESLint at project-level as linting settings may differ between browser-based and node-based projects. 2 | # Prettier at repository-level as formatting settings will be the same across all projects. 3 | # IMPORTANT: Sync with all other '.eslintrc.yaml' files! 4 | root: true 5 | parser: "@typescript-eslint/parser" 6 | parserOptions: 7 | project: "./tsconfig.json" 8 | plugins: 9 | - "@typescript-eslint" 10 | - "return-types-object-literals" 11 | overrides: 12 | - files: 13 | - "*.js" 14 | rules: 15 | "@typescript-eslint/explicit-function-return-type": off 16 | "return-types-object-literals/require-return-types-for-object-literals": off 17 | rules: 18 | "return-types-object-literals/require-return-types-for-object-literals": error 19 | "no-else-return": error 20 | "object-shorthand": error 21 | "@typescript-eslint/no-var-requires": error 22 | "@typescript-eslint/no-redeclare": off 23 | "@typescript-eslint/no-extraneous-class": off 24 | "@typescript-eslint/no-namespace": off 25 | "@typescript-eslint/member-ordering": 26 | - error 27 | - classes: 28 | order: as-written 29 | memberTypes: 30 | # Index signature 31 | - "signature" 32 | 33 | # Fields 34 | - "static-field" 35 | - "decorated-field" 36 | - "instance-field" 37 | - "abstract-field" 38 | 39 | - "field" 40 | 41 | # Constructors 42 | - "public-constructor" 43 | - "protected-constructor" 44 | - "private-constructor" 45 | 46 | - "constructor" 47 | 48 | # Methods 49 | - "public-static-method" 50 | - "protected-static-method" 51 | 52 | - "public-decorated-method" 53 | - "protected-decorated-method" 54 | 55 | - "public-instance-method" 56 | - "protected-instance-method" 57 | 58 | - "public-abstract-method" 59 | - "protected-abstract-method" 60 | 61 | - "public-method" 62 | - "protected-method" 63 | 64 | - "private-static-method" 65 | - "private-decorated-method" 66 | - "private-instance-method" 67 | - "private-abstract-method" 68 | - "private-method" 69 | 70 | - "static-method" 71 | - "instance-method" 72 | - "abstract-method" 73 | 74 | - "decorated-method" 75 | 76 | - "method" 77 | default: 78 | order: alphabetically 79 | memberTypes: 80 | # Index signature 81 | - "signature" 82 | 83 | # Fields 84 | - "static-field" 85 | - "decorated-field" 86 | - "instance-field" 87 | - "abstract-field" 88 | 89 | - "field" 90 | 91 | # Constructors 92 | - "public-constructor" 93 | - "protected-constructor" 94 | - "private-constructor" 95 | 96 | - "constructor" 97 | 98 | # Methods 99 | - "public-static-method" 100 | - "protected-static-method" 101 | 102 | - "public-decorated-method" 103 | - "protected-decorated-method" 104 | 105 | - "public-instance-method" 106 | - "protected-instance-method" 107 | 108 | - "public-abstract-method" 109 | - "protected-abstract-method" 110 | 111 | - "public-method" 112 | - "protected-method" 113 | 114 | - "private-static-method" 115 | - "private-decorated-method" 116 | - "private-instance-method" 117 | - "private-abstract-method" 118 | - "private-method" 119 | 120 | - "static-method" 121 | - "instance-method" 122 | - "abstract-method" 123 | 124 | - "decorated-method" 125 | 126 | - "method" 127 | extends: 128 | - standard-with-typescript 129 | - prettier 130 | - prettier/@typescript-eslint 131 | env: 132 | node: true 133 | -------------------------------------------------------------------------------- /.github/assets/bytescale-upload-widget-react.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /.github/assets/demo.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bytescale/bytescale-upload-widget-react/b0ba0a7559b161a4ce30a2d1157c5ae5f9fb66cd/.github/assets/demo.webp -------------------------------------------------------------------------------- /.github/assets/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: Continuous Integration 2 | 3 | on: 4 | pull_request: 5 | branches: ["*"] 6 | push: 7 | branches: ["*"] 8 | 9 | defaults: 10 | run: 11 | shell: bash 12 | 13 | jobs: 14 | build: 15 | runs-on: ubuntu-22.04 16 | 17 | steps: 18 | - uses: actions/checkout@v2 19 | 20 | - name: "Install node & npm" 21 | uses: actions/setup-node@v1 22 | with: 23 | node-version: 18.x 24 | 25 | - name: "Install repository dependencies" 26 | run: npm ci 27 | 28 | - name: "Check code style" 29 | run: find . -type f \( -iname \*.js -o -iname \*.jsx -o -iname \*.ts -o -iname \*.tsx \) | grep -v node_modules | grep -v /dist/ | xargs ./node_modules/.bin/prettier --check 30 | 31 | # Note: we put other steps between the 'npm start' and the test runs, just to make time for the server to spin-up. 32 | - name: "Tests" 33 | run: npm test 34 | 35 | - name: "Publish" 36 | if: github.ref == 'refs/heads/main' 37 | env: 38 | AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} 39 | AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} 40 | AWS_EC2_METADATA_DISABLED: true 41 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Automatically generated by GitHub / is not in our secrets. 42 | NPM_AUTH_TOKEN: ${{ secrets.BYTESCALE_NPM_AUTH_TOKEN }} 43 | NODE_OPTIONS: --openssl-legacy-provider 44 | run: | 45 | npm set //registry.npmjs.org/:_authToken=${NPM_AUTH_TOKEN} 46 | npm run publish:executeIfReleaseCommit 47 | 48 | - name: "Notification on success" 49 | if: github.ref == 'refs/heads/main' 50 | uses: rtCamp/action-slack-notify@v2 51 | env: 52 | SLACK_CHANNEL: deployments 53 | SLACK_COLOR: "#17BB5E" 54 | SLACK_TITLE: "Built: @bytescale/upload-widget-react :rocket:" 55 | SLACK_FOOTER: "This package was successfully built." 56 | MSG_MINIMAL: true 57 | SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }} 58 | 59 | - name: "Notification on failure" 60 | if: github.ref == 'refs/heads/main' && failure() 61 | uses: rtCamp/action-slack-notify@v2 62 | env: 63 | SLACK_CHANNEL: deployments 64 | SLACK_COLOR: "#BB1717" 65 | SLACK_TITLE: "Failed: @bytescale/upload-widget-react :boom:" 66 | SLACK_FOOTER: "No packages published." 67 | MSG_MINIMAL: true 68 | SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }} 69 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # OSX 2 | .DS_Store 3 | .AppleDouble 4 | .LSOverride 5 | Icon 6 | ._* 7 | .Spotlight-V100 8 | .Trashes 9 | 10 | # Windows 11 | Thumbs.db 12 | ehthumbs.db 13 | Desktop.ini 14 | $RECYCLE.BIN/ 15 | *.cab 16 | *.msi 17 | *.msm 18 | *.msp 19 | 20 | # IntelliJ 21 | .idea/ 22 | *.iml 23 | 24 | # Node.js 25 | node_modules/ 26 | npm-debug.log* 27 | 28 | # NPM Packages 29 | *.tgz 30 | dist/ 31 | 32 | # Standard 33 | tmp/ 34 | *.log 35 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | lint-staged 5 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | docs/POLYFILLS.md.template.md 2 | README.md 3 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "arrowParens": "avoid", 3 | "bracketSpacing": true, 4 | "jsxBracketSameLine": true, 5 | "jsxSingleQuote": false, 6 | "printWidth": 120, 7 | "proseWrap": "preserve", 8 | "quoteProps": "consistent", 9 | "semi": true, 10 | "singleQuote": false, 11 | "tabWidth": 2, 12 | "trailingComma": "none", 13 | "useTabs": false 14 | } 15 | -------------------------------------------------------------------------------- /BUILD.md: -------------------------------------------------------------------------------- 1 | # Building From Source 2 | 3 | This repository contains a hot-reloading sandbox for developing the `@bytescale/upload-widget-react` NPM package. 4 | 5 | ## Prerequisites 6 | 7 | `node` and `npm` are required — we actively support the following versions: 8 | 9 | | Package | Version | 10 | | ------- | -------- | 11 | | Node | v18.12.1 | 12 | | NPM | v8.19.2 | 13 | 14 | ## How To Build & Run 15 | 16 | ### 1. Clone 17 | 18 | ```shell 19 | git clone git@github.com:bytescale/upload-widget-react.git 20 | cd upload-widget-react 21 | ``` 22 | 23 | ### 2. Setup Environment 24 | 25 | ``` 26 | export NODE_OPTIONS=--openssl-legacy-provider 27 | ``` 28 | 29 | ### 3. Install Dependencies 30 | 31 | ```shell 32 | npm install 33 | ``` 34 | 35 | ### 4. Run The Sandbox 36 | 37 | ```shell 38 | npm start 39 | ``` 40 | 41 | The above launches a **hot-reloading** server on `http://127.0.0.1:3060` that uses `@bytescale/upload-widget-react` from source. 42 | 43 | _Please ensure nothing else is running on TCP port `3060`_. 44 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Bytescale Ltd 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /MIGRATE.md: -------------------------------------------------------------------------------- 1 | # Migration Guide 2 | 3 | ## From React Uploader (`react-uploader`) 4 | 5 | Steps: 6 | 7 | 1. Install `@bytescale/upload-widget-react` 8 | 2. Uninstall `react-uploader` and `uploader` 9 | 3. Replace `"react-uploader"` with `"@bytescale/upload-widget-react"` in your `import` statements. 10 | 4. Replace `uploader` with `upload-widget` in all CSS class name overrides (if you have any). 11 | 5. Replace `onUpdate: (files) => {}` with `onUpdate: ({uploadedFiles}) => {}`. 12 | 6. Remove `uploader` (from imports and props) 13 | 7. Add `apiKey` as a field to the object passed to the `options` prop (add it if you don't have one). 14 | 15 | ### Before 16 | 17 | ```jsx 18 | import { Uploader } from "uploader"; 19 | import { UploadDropzone } from "react-uploader"; 20 | 21 | // Get production API keys from Bytescale.com 22 | const uploader = Uploader({ 23 | apiKey: "free" 24 | }); 25 | 26 | // Customize the dropzone UI (see "customization"): 27 | const options = { multi: true }; 28 | 29 | // Render the file upload dropzone: 30 | const MyDropzoneComponent = () => ( 31 | { 37 | // Optional. 38 | if (files.length === 0) { 39 | console.log("No files selected."); 40 | } else { 41 | console.log("Files uploaded:"); 42 | console.log(files.map(f => f.fileUrl)); 43 | } 44 | }} 45 | /> 46 | ); 47 | ``` 48 | 49 | ### After 50 | 51 | ```jsx 52 | import { UploadDropzone } from "@bytescale/upload-widget-react"; 53 | 54 | const options = { 55 | apiKey: "free", // Get API keys from: www.bytescale.com 56 | multi: true // Full Config: https://www.bytescale.com/docs/upload-widget#configuration 57 | }; 58 | 59 | // Render the file upload dropzone: 60 | const MyDropzoneComponent = () => ( 61 | { 66 | // Optional. 67 | if (uploadedFiles.length === 0) { 68 | console.log("No files selected."); 69 | } else { 70 | console.log("Files uploaded:"); 71 | console.log(uploadedFiles.map(f => f.fileUrl)); 72 | } 73 | }} 74 | /> 75 | ); 76 | ``` 77 | 78 | ## See also 79 | 80 | Bytescale migration guides listed below: 81 | 82 | - [Migrating from `upload-js` to `@bytescale/sdk`](https://github.com/bytescale/bytescale-javascript-sdk/blob/main/MIGRATE.md) 83 | - [Migrating from `uploader` to `@bytescale/upload-widget`](https://github.com/bytescale/bytescale-upload-widget/blob/main/MIGRATE.md) 84 | - [Migrating from `react-uploader` to `@bytescale/upload-widget-react`](https://github.com/bytescale/bytescale-upload-widget-react/blob/main/MIGRATE.md) 85 | - [Migrating from `angular-uploader` to `@bytescale/upload-widget-angular`](https://github.com/bytescale/bytescale-upload-widget-angular/blob/main/MIGRATE.md) 86 | - [Migrating from `@upload-io/vue-uploader` to `@bytescale/upload-widget-vue`](https://github.com/bytescale/bytescale-upload-widget-vue/blob/main/MIGRATE.md) 87 | - [Migrating from `@upload-io/jquery-uploader` to `@bytescale/upload-widget-jquery`](https://github.com/bytescale/bytescale-upload-widget-jquery/blob/main/MIGRATE.md) 88 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 | Bytescale Upload Widget for React 4 | 5 |

6 |

Beautiful File Upload Widget for React
(Works out-the-box, storage included)

7 |
8 |

9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 |
25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | Twitter URL 36 | 37 | 38 |

39 |

40 | Get Started — 41 | 42 | Try on CodePen 43 | 44 |

45 | 46 |

Upload Widget Demo

47 | 48 |

100% Serverless File Upload Widget
Powered by Bytescale

49 | 50 | 51 |
52 | 53 |

54 | Supports: Image Cropping, Video Previews, Document Previews, Drag & Drop, and more... 55 |
56 |
57 | Full DocumentationHeadless SDKMedia Processing APIsStorageCDN 58 |

59 | 60 |
61 | 62 |
63 | 64 | ## Installation 65 | 66 | Install via NPM: 67 | 68 | ```shell 69 | npm install @bytescale/upload-widget-react 70 | ``` 71 | 72 | Or via YARN: 73 | 74 | ```shell 75 | yarn add @bytescale/upload-widget-react 76 | ``` 77 | 78 | Or via a ` 82 | ``` 83 | 84 | ## Usage 85 | 86 | ### UploadButton — [Try on CodePen](https://codepen.io/bytescale/pen/popWJpX?editors=0010) 87 | 88 | The `UploadButton` component uses a [render prop](https://reactjs.org/docs/render-props.html) to provide an `onClick` callback to your button element. 89 | 90 | When clicked, a file upload modal will appear: 91 | 92 | ```javascript 93 | import { UploadButton } from "@bytescale/upload-widget-react"; 94 | 95 | // Full Configuration: 96 | // https://www.bytescale.com/docs/upload-widget#configuration 97 | const options = { 98 | apiKey: "free", // Get API keys from: www.bytescale.com 99 | maxFileCount: 10 100 | }; 101 | 102 | const MyApp = () => ( 103 | alert(files.map(x => x.fileUrl).join("\n"))}> 105 | {({onClick}) => 106 | 109 | } 110 | 111 | ); 112 | ``` 113 | 114 | Required props: 115 | - `options` 116 | - `children` 117 | 118 | Optional props: 119 | - `onComplete` 120 | - `onUpdate` 121 | 122 | ### UploadDropzone — [Try on CodePen](https://codepen.io/bytescale/pen/LYrMzaB?editors=0010) 123 | 124 | The `UploadDropzone` component renders an inline drag-and-drop file upload dropzone: 125 | 126 | ```javascript 127 | import { UploadDropzone } from "@bytescale/upload-widget-react"; 128 | 129 | // Full Configuration: 130 | // https://www.bytescale.com/docs/upload-widget#configuration 131 | const options = { 132 | apiKey: "free", // Get API keys from: www.bytescale.com 133 | maxFileCount: 10 134 | }; 135 | 136 | const MyApp = () => ( 137 | { 139 | console.log(uploadedFiles.map(x => x.fileUrl).join("\n")) 140 | }} 141 | width="600px" 142 | height="375px" /> 143 | ); 144 | ``` 145 | 146 | > **Special behaviour for dropzones:** 147 | > 148 | > `onComplete` only fires if `showFinishButton = true` (when the user clicks "Finish"). 149 | > 150 | > `onUpdate` must be used when `showFinishButton = false`. 151 | > 152 | > Default value: `showFinishButton = false` 153 | 154 | Required props: 155 | - `options` 156 | 157 | Optional props: 158 | - `onComplete` 159 | - `onUpdate` 160 | - `width` 161 | - `height` 162 | 163 | ## Result 164 | 165 | The callbacks receive a `Array`: 166 | 167 | ```javascript 168 | { 169 | fileUrl: "https://upcdn.io/FW25...", // URL to use when serving this file. 170 | filePath: "/uploads/example.jpg", // File path (we recommend saving this to your database). 171 | 172 | accountId: "FW251aX", // Bytescale account the file was uploaded to. 173 | 174 | editedFile: undefined, // Edited file (for image crops). Same structure as below. 175 | 176 | originalFile: { 177 | fileUrl: "https://upcdn.io/FW25...", // Uploaded file URL. 178 | filePath: "/uploads/example.jpg", // Uploaded file path (relative to your raw file directory). 179 | accountId: "FW251aX", // Bytescale account the file was uploaded to. 180 | originalFileName: "example.jpg", // Original file name from the user's machine. 181 | file: { ... }, // Original DOM file object from the element. 182 | size: 12345, // File size in bytes. 183 | lastModified: 1663410542397, // Epoch timestamp of when the file was uploaded or updated. 184 | mime: "image/jpeg", // File MIME type. 185 | metadata: { 186 | ... // User-provided JSON object. 187 | }, 188 | tags: [ 189 | "tag1", // User-provided & auto-generated tags. 190 | "tag2", 191 | ... 192 | ] 193 | } 194 | } 195 | ``` 196 | 197 | ## ⚙️ Configuration 198 | 199 | All configuration is optional (except for the `apiKey` field, which is required). 200 | 201 | ```javascript 202 | const options = { 203 | apiKey: "free", // Get API keys from: www.bytescale.com 204 | locale: myCustomLocale, // EN_US by default. (See "Localization" section below.) 205 | maxFileCount: 5, // Unlimited by default (or 1 if multi: false). 206 | maxFileSizeBytes: 1024 ** 2, // Unlimited by default. 207 | mimeTypes: ["image/*"], // Unrestricted by default. Supports * wildcard suffix. 208 | multi: false, // False by default. 209 | onInit: ({ // Exposes lifecycle methods for the component. 210 | close, // Closes the widget when called. 211 | reset, // Resets the widget when called. 212 | updateConfig // Updates the widget's config by passing a new config 213 | }) => {}, // object to the method's first parameter. 214 | onUpdate: (event) => { // Called each time the Upload Widget's list of files change. 215 | // event.pendingFiles // Array of files that are either uploading or queued. 216 | // event.failedFiles // Array of files that failed to upload (due to network or validation reasons). 217 | // event.uploadedFiles // Array of files that have been uploaded and not removed. 218 | }, 219 | onPreUpload: async file => ({ 220 | errorMessage: "Uh oh!", // Displays this validation error to the user (if set). 221 | transformedFile: file // Uploads 'transformedFile' instead of 'file' (if set). 222 | }), 223 | showFinishButton: true, // Show/hide the "finish" button in the widget. 224 | showRemoveButton: true, // Show/hide the "remove" button next to each file. 225 | styles: { 226 | breakpoints: { 227 | fullScreenWidth: 750, // Full-screen mode activates when the screen is at or below this width. 228 | fullScreenHeight: 420 // Full-screen mode activates when the screen is at or below this height. 229 | }, 230 | colors: { 231 | primary: "#377dff", // Primary buttons & links 232 | active: "#528fff", // Primary buttons & links (hover). Inferred if undefined. 233 | error: "#d23f4d", // Error messages 234 | shade100: "#333", // Standard text 235 | shade200: "#7a7a7a", // Secondary button text 236 | shade300: "#999", // Secondary button text (hover) 237 | shade400: "#a5a6a8", // Welcome text 238 | shade500: "#d3d3d3", // Modal close button 239 | shade600: "#dddddd", // Border 240 | shade700: "#f0f0f0", // Progress indicator background 241 | shade800: "#f8f8f8", // File item background 242 | shade900: "#fff" // Various (draggable crop buttons, etc.) 243 | }, 244 | fontFamilies: { 245 | base: "arial, sans-serif" // Base font family (comma-delimited). 246 | }, 247 | fontSizes: { 248 | base: 16 // Base font size (px). 249 | } 250 | }, 251 | path: { // Optional: a string (full file path) or object like so: 252 | fileName: "Example.jpg", // Supports path variables (e.g. {ORIGINAL_FILE_EXT}). 253 | folderPath: "/uploads" // Please refer to docs for all path variables. 254 | }, 255 | metadata: { 256 | hello: "world" // Arbitrary JSON metadata (saved against the file). 257 | }, 258 | tags: ["profile_picture"], // Requires a Bytescale account. 259 | editor: { 260 | images: { 261 | allowResizeOnMove: true, // True by default. If false, prevents cropper from resizing when moved. 262 | preview: true, // True by default if cropping is enabled. Previews PDFs and videos too. 263 | crop: true, // True by default. 264 | cropFilePath: image => { // Choose the file path used for JSON image crop files. 265 | const {filePath} = image // In: https://www.bytescale.com/docs/types/UploadedFile 266 | return `${filePath}.crop` // Out: https://www.bytescale.com/docs/types/FilePathDefinition 267 | }, 268 | cropRatio: 4 / 3, // Width / Height. Undefined enables freeform (default). 269 | cropShape: "rect" // "rect" (default) or "circ". 270 | } 271 | }, 272 | } 273 | ``` 274 | 275 | ### 🏳️ Localization 276 | 277 | Default is [EN_US](https://github.com/bytescale/bytescale-upload-widget/blob/main/lib/src/modules/locales/EN_US.ts): 278 | 279 | ```javascript 280 | const myCustomLocale = { 281 | addAnotherFileBtn: "Add another file...", 282 | addAnotherImageBtn: "Add another image...", 283 | cancelBtn: "cancel", 284 | cancelBtnClicked: "cancelled", 285 | cancelPreviewBtn: "Cancel", 286 | continueBtn: "Continue", 287 | cropBtn: "Crop", 288 | customValidationFailed: "Failed to validate file.", 289 | doneBtn: "Done", 290 | fileSizeLimitPrefix: "File size limit:", 291 | finishBtn: "Finished", 292 | finishBtnIcon: true, 293 | imageCropNumberPrefix: "Image", 294 | maxFilesReachedPrefix: "Maximum number of files:", 295 | maxImagesReachedPrefix: "Maximum number of images:", 296 | orDragDropFile: "...or drag and drop a file.", 297 | orDragDropFileMulti: "...or drag and drop files.", 298 | orDragDropImage: "...or drag and drop an image.", 299 | orDragDropImageMulti: "...or drag and drop images.", 300 | processingFile: "Processing file...", 301 | removeBtn: "remove", 302 | removeBtnClicked: "removed", 303 | submitBtnError: "Error!", 304 | submitBtnLoading: "Please wait...", 305 | unsupportedFileType: "File type not supported.", 306 | uploadFileBtn: "Upload a File", 307 | uploadFileMultiBtn: "Upload Files", 308 | uploadImageBtn: "Upload an Image", 309 | uploadImageMultiBtn: "Upload Images", 310 | xOfY: "of" 311 | } 312 | ``` 313 | 314 | # 🌐 API Support 315 | 316 | ## 🌐 File Management APIs 317 | 318 | Bytescale provides a wide range of [File Management APIs](https://www.bytescale.com/docs/apis): 319 | 320 | - **[File Listing](https://www.bytescale.com/docs/folder-api/ListFolder)** 321 | - **[File Deleting](https://www.bytescale.com/docs/file-api/DeleteFile)** 322 | - **[File Copying](https://www.bytescale.com/docs/file-api/CopyFile)** 323 | - [And more...](https://www.bytescale.com/docs/apis) 324 | 325 | ## 🌐 Media Processing APIs (Image/Video/Audio) 326 | 327 | Bytescale also provides real-time [Media Processing APIs](https://www.bytescale.com/docs/media-processing-apis): 328 | 329 | - **[Image Processing APIs](https://www.bytescale.com/docs/image-processing-api)** ([resize](https://www.bytescale.com/docs/image-processing-api#image-resizing-api), [crop](https://www.bytescale.com/docs/image-processing-api#image-cropping-api), [convert](https://www.bytescale.com/docs/image-processing-api#f), [compress](https://www.bytescale.com/docs/image-processing-api#image-compression-api) & [watermark](https://www.bytescale.com/docs/image-processing-api#Text-layering-api)) 330 | - **[Video Processing APIs](https://www.bytescale.com/docs/video-processing-api)** ([transcode](https://www.bytescale.com/docs/video-processing-api#video-transcoding-api), [optimize](https://www.bytescale.com/docs/video-processing-api#video-compression-api), [resize](https://www.bytescale.com/docs/video-processing-api#video-resizing-api) & [extract metadata](https://www.bytescale.com/docs/video-processing-api#video-metadata-api)) 331 | - **[Audio Processing APIs](https://www.bytescale.com/docs/audio-processing-api)** ([transcode](https://www.bytescale.com/docs/audio-processing-api#audio-transcoding-api), [optimize](https://www.bytescale.com/docs/audio-processing-api#audio-compression-api), [trim](https://www.bytescale.com/docs/audio-processing-api#audio-trimming-api) & [extract metadata](https://www.bytescale.com/docs/audio-processing-api#audio-metadata-api)) 332 | 333 | ### Image Processing API (Original Image) 334 | 335 | Here's an example using [a photo of Chicago](https://upcdn.io/W142hJk/raw/example/city-landscape.jpg): 336 | 337 | 338 | 339 | ``` 340 | https://upcdn.io/W142hJk/raw/example/city-landscape.jpg 341 | ``` 342 | 343 | ### Image Processing API (Transformed Image) 344 | 345 | Using the [Image Processing API](https://www.bytescale.com/docs/image-processing-api), you can produce [this image](https://upcdn.io/W142hJk/image/example/city-landscape.jpg?w=900&h=600&fit=crop&f=webp&q=80&blur=4&text=WATERMARK&layer-opacity=80&blend=overlay&layer-rotate=315&font-size=100&padding=10&font-weight=900&color=ffffff&repeat=true&text=Chicago&gravity=bottom&padding-x=50&padding-bottom=20&font=/example/fonts/Lobster.ttf&color=ffe400): 346 | 347 | 348 | 349 | ``` 350 | https://upcdn.io/W142hJk/image/example/city-landscape.jpg 351 | ?w=900 352 | &h=600 353 | &fit=crop 354 | &f=webp 355 | &q=80 356 | &blur=4 357 | &text=WATERMARK 358 | &layer-opacity=80 359 | &blend=overlay 360 | &layer-rotate=315 361 | &font-size=100 362 | &padding=10 363 | &font-weight=900 364 | &color=ffffff 365 | &repeat=true 366 | &text=Chicago 367 | &gravity=bottom 368 | &padding-x=50 369 | &padding-bottom=20 370 | &font=/example/fonts/Lobster.ttf 371 | &color=ffe400 372 | ``` 373 | 374 | ## Authentication 375 | 376 | Bytescale supports two types of authentication: 377 | 378 | ### API Keys 379 | 380 | The Bytescale Upload Widget uses the `apiKey` parameter to authenticate with [Bytescale](https://www.bytescale.com/). 381 | 382 | With API key auth, the requester has access to the resources available to the API key: 383 | 384 | - Secret API keys (`secret_***`) can perform all API operations (see: [Bytescale JavaScript SDK](https://www.bytescale.com/docs/sdks/javascript)). 385 | 386 | - Public API keys (`public_***`) can perform file uploads and file downloads only. File overwrites, file deletes, and all other destructive operations cannot be performed using public API keys. 387 | 388 | You must always use **public API keys** (e.g. `public_***`) in your client-side code. 389 | 390 | Each API key can have its read/write access limited to a subset of files/folders. 391 | 392 | ### JWTs 393 | 394 | JWTs are optional. 395 | 396 | With JWTs, users can download private files directly via the URL, as authentication is performed implicitly via a session cookie _or_ via an `authorization` header if service workers are enabled (see the `serviceWorkerScript` param on the `AuthManager.beginAuthSession` method). This allows the browser to display private files in ``, `