├── .browserslistrc ├── .dockerignore ├── .editorconfig ├── .env.example ├── .eslintignore ├── .eslintrc.js ├── .gitignore ├── .prettierignore ├── .prettierrc.js ├── .remarkignore ├── .stylelintrc.json ├── .vscode ├── uppy.code-workspace └── uppy.code-workspace.bak ├── .yarn ├── patches │ ├── p-queue-npm-7.4.1-e0cf0a6f17.patch │ ├── pre-commit-npm-1.2.2-f30af83877.patch │ ├── preact-npm-10.10.0-dd04de05e8.patch │ ├── start-server-and-test-npm-1.14.0-841aa34fdf.patch │ ├── stylelint-config-rational-order-npm-0.1.2-d8336e84ed.patch │ └── uuid-npm-8.3.2-eca0baba53.patch └── plugins │ └── @yarnpkg │ ├── plugin-version.cjs │ └── plugin-workspace-tools.cjs ├── .yarnrc.yml ├── BUNDLE-README.md ├── Dockerfile ├── Dockerfile.test ├── LICENSE ├── Makefile ├── README.md ├── SECURITY.md ├── atest.ps1 ├── babel.config.js ├── bin ├── build-bundle.mjs ├── build-css.js ├── build-lib.js ├── build-ts.mjs ├── companion.sh ├── to-gif-hd.sh ├── to-gif-hq.sh ├── to-gif.sh └── update-yarn.sh ├── docker-compose-dev.yml ├── docker-compose-test.yml ├── docker-compose.yml ├── e2e ├── .parcelrc ├── clients │ ├── dashboard-aws-multipart │ │ ├── app.js │ │ └── index.html │ ├── dashboard-aws │ │ ├── app.js │ │ └── index.html │ ├── dashboard-compressor │ │ ├── app.js │ │ └── index.html │ ├── dashboard-transloadit │ │ ├── app.js │ │ ├── generateSignatureIfSecret.js │ │ └── index.html │ ├── dashboard-tus │ │ ├── app.js │ │ └── index.html │ ├── dashboard-ui │ │ ├── app.js │ │ └── index.html │ ├── dashboard-vue │ │ ├── App.vue │ │ ├── index.html │ │ └── index.js │ ├── dashboard-xhr │ │ ├── app.js │ │ └── index.html │ ├── index.html │ └── react │ │ ├── App.jsx │ │ ├── index.html │ │ └── index.jsx ├── cypress.config.mjs ├── cypress │ ├── fixtures │ │ ├── 1020-percent-state.json │ │ ├── DeepFrozenStore.mjs │ │ └── images │ │ │ ├── carToCheck.jpg │ │ │ ├── cat-symbolic-link │ │ │ ├── cat-symbolic.jpg │ │ │ ├── image.jpg │ │ │ ├── invalid.png │ │ │ ├── kit.jpg │ │ │ ├── monkey.png │ │ │ ├── papagai.jpg │ │ │ ├── papagai.png │ │ │ └── traffic.jpg │ ├── integration │ │ ├── dashboard-aws-multipart.spec.ts │ │ ├── dashboard-aws.spec.ts │ │ ├── dashboard-compressor.spec.ts │ │ ├── dashboard-transloadit.spec.ts │ │ ├── dashboard-tus.spec.ts │ │ ├── dashboard-ui.spec.ts │ │ ├── dashboard-vue.spec.ts │ │ ├── dashboard-xhr.spec.ts │ │ ├── react.spec.ts │ │ └── reusable-tests.ts │ └── support │ │ ├── commands.ts │ │ ├── createFakeFile.ts │ │ ├── e2e.ts │ │ └── index.ts ├── generate-test.mjs ├── mock-server.mjs ├── package.json ├── start-companion-with-load-balancer.mjs └── tsconfig.json ├── examples ├── aws-nodejs │ ├── README.md │ ├── index.js │ ├── package.json │ └── public │ │ ├── drag.html │ │ └── index.html ├── aws-php │ ├── .gitignore │ ├── composer.json │ ├── composer.lock │ ├── index.html │ ├── main.js │ ├── package.json │ ├── readme.md │ ├── s3-sign.php │ └── serve.php ├── bundled │ ├── index.html │ ├── index.js │ ├── package.json │ └── sw.js ├── custom-provider │ ├── README.md │ ├── client │ │ ├── MyCustomProvider.jsx │ │ └── main.js │ ├── index.html │ ├── package.json │ └── server │ │ ├── CustomProvider.cjs │ │ └── index.cjs ├── multiple-instances │ ├── README.md │ ├── index.html │ ├── main.js │ └── package.json ├── node-xhr │ ├── .gitignore │ ├── README.md │ ├── index.html │ ├── main.js │ ├── package.json │ └── server.js ├── php-xhr │ ├── .gitignore │ ├── README.md │ ├── index.html │ ├── main.js │ ├── package.json │ └── server.php ├── python-xhr │ ├── .gitignore │ ├── README.md │ ├── index.html │ ├── main.js │ ├── package.json │ ├── requirements.txt │ └── server.py ├── react-native-expo │ ├── .eslintrc.json │ ├── .expo-shared │ │ └── assets.json │ ├── .gitignore │ ├── App.js │ ├── FileList.js │ ├── PauseResumeButton.js │ ├── ProgressBar.js │ ├── SelectFilesButton.js │ ├── app.json │ ├── babel.config.js │ ├── index.js │ ├── metro.config.js │ ├── package.json │ ├── readme.md │ └── tusFileReader.js ├── redux │ ├── README.md │ ├── index.html │ ├── main.js │ └── package.json ├── transloadit │ ├── .gitignore │ ├── README.md │ ├── index.html │ ├── main.js │ ├── package.json │ └── server.js ├── vue │ ├── .gitignore │ ├── README.md │ ├── index.html │ ├── package.json │ ├── src │ │ ├── App.vue │ │ └── main.js │ └── vite.config.js └── xhr-bundle │ ├── README.md │ ├── index.html │ ├── main.js │ ├── package.json │ └── server.cjs ├── freshstart.md ├── output └── .keep ├── package.json └── packages ├── @ImgProcessor ├── angular │ ├── .editorconfig │ ├── .eslintrc.json │ ├── .gitignore │ ├── .vscode │ │ ├── extensions.json │ │ ├── launch.json │ │ └── tasks.json │ ├── CHANGELOG.md │ ├── LICENSE │ ├── README.md │ ├── angular.json │ ├── package.json │ ├── projects │ │ └── uppy │ │ │ └── angular │ │ │ ├── .eslintrc.json │ │ │ ├── README.md │ │ │ ├── ng-package.json │ │ │ ├── package.json │ │ │ ├── src │ │ │ └── public-api.ts │ │ │ ├── tsconfig.lib.json │ │ │ ├── tsconfig.lib.prod.json │ │ │ └── tsconfig.spec.json │ └── tsconfig.json ├── audio │ ├── CHANGELOG.md │ ├── LICENSE │ ├── README.md │ └── types │ │ └── index.d.ts └── url │ └── types │ └── index.d.ts └── ImgProcessor └── .npmignore /.browserslistrc: -------------------------------------------------------------------------------- 1 | [production] 2 | last 2 Safari versions 3 | last 2 Chrome versions 4 | last 2 ChromeAndroid versions 5 | last 2 Firefox versions 6 | last 2 FirefoxAndroid versions 7 | last 2 Edge versions 8 | iOS >=13.4 9 | 10 | [legacy] 11 | IE 11 12 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | **/node_modules 2 | .git 3 | website 4 | assets 5 | private 6 | e2e 7 | .env 8 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | ; This file is for unifying the coding style for different editors and IDEs. 2 | ; More information at http://editorconfig.org 3 | 4 | root = true 5 | 6 | [*] 7 | charset = utf-8 8 | indent_style = space 9 | indent_size = 2 10 | end_of_line = lf 11 | insert_final_newline = true 12 | trim_trailing_whitespace = true 13 | -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | # Clone this file to `.env` and edit the clone. 2 | 3 | NODE_ENV=development 4 | 5 | # Companion 6 | # ======================= 7 | COMPANION_DATADIR=./output 8 | COMPANION_DOMAIN=localhost:3020 9 | COMPANION_PROTOCOL=http 10 | COMPANION_PORT=3020 11 | COMPANION_CLIENT_ORIGINS= 12 | COMPANION_SECRET=development 13 | COMPANION_PREAUTH_SECRET=development2 14 | 15 | # NOTE: Only enable this in development. Enabling it in production is a security risk 16 | COMPANION_ALLOW_LOCAL_URLS=true 17 | 18 | # to enable S3 19 | COMPANION_AWS_KEY="YOUR AWS KEY" 20 | COMPANION_AWS_SECRET="YOUR AWS SECRET" 21 | # specifying a secret file will override a directly set secret 22 | # COMPANION_AWS_SECRET_FILE="PATH/TO/AWS/SECRET/FILE" 23 | COMPANION_AWS_BUCKET="YOUR AWS S3 BUCKET" 24 | COMPANION_AWS_REGION="AWS REGION" 25 | COMPANION_AWS_PREFIX="OPTIONAL PREFIX" 26 | # to enable S3 Transfer Acceleration (default: false) 27 | # COMPANION_AWS_USE_ACCELERATE_ENDPOINT="false" 28 | # to set X-Amz-Expires query param in presigned urls (in seconds, default: 800) 29 | # COMPANION_AWS_EXPIRES="800" 30 | # to set a canned ACL for uploaded objects: https://docs.aws.amazon.com/AmazonS3/latest/dev/acl-overview.html#canned-acl 31 | # COMPANION_AWS_ACL="public-read" 32 | 33 | COMPANION_BOX_KEY=*** 34 | COMPANION_BOX_SECRET=*** 35 | 36 | COMPANION_DROPBOX_KEY=*** 37 | COMPANION_DROPBOX_SECRET=*** 38 | 39 | COMPANION_GOOGLE_KEY=*** 40 | COMPANION_GOOGLE_SECRET=*** 41 | 42 | COMPANION_INSTAGRAM_KEY=*** 43 | COMPANION_INSTAGRAM_SECRET=*** 44 | 45 | COMPANION_FACEBOOK_KEY=*** 46 | COMPANION_FACEBOOK_SECRET=*** 47 | 48 | COMPANION_ZOOM_KEY=*** 49 | COMPANION_ZOOM_SECRET=*** 50 | 51 | COMPANION_UNSPLASH_KEY=*** 52 | COMPANION_UNSPLASH_SECRET=*** 53 | 54 | COMPANION_ONEDRIVE_KEY=*** 55 | COMPANION_ONEDRIVE_SECRET=**** 56 | 57 | # To test dynamic Oauth against local companion (which is pointless but allows us to test it without Transloadit's servers), enable these: 58 | #COMPANION_GOOGLE_KEYS_ENDPOINT=http://localhost:3020/drive/test-dynamic-oauth-credentials?secret=development 59 | #COMPANION_TEST_DYNAMIC_OAUTH_CREDENTIALS=true 60 | #COMPANION_TEST_DYNAMIC_OAUTH_CREDENTIALS_SECRET=development 61 | 62 | 63 | # Development environment 64 | # ======================= 65 | 66 | VITE_UPLOADER=tus 67 | # VITE_UPLOADER=s3 68 | # VITE_UPLOADER=s3-multipart 69 | # xhr will use protocol 'multipart' in companion, if used with a remote service, e.g. google drive. 70 | # If local upload will use browser XHR 71 | # VITE_UPLOADER=xhr 72 | # VITE_UPLOADER=transloadit 73 | # VITE_UPLOADER=transloadit-s3 74 | # VITE_UPLOADER=transloadit-xhr 75 | 76 | VITE_COMPANION_URL=http://localhost:3020 77 | # See also Transloadit.COMPANION_PATTERN 78 | VITE_COMPANION_ALLOWED_HOSTS="\.transloadit\.com$" 79 | VITE_TUS_ENDPOINT=https://tusd.tusdemo.net/files/ 80 | VITE_XHR_ENDPOINT=https://xhr-server.herokuapp.com/upload 81 | 82 | # If you want to test dynamic Oauth 83 | # VITE_COMPANION_GOOGLE_DRIVE_KEYS_PARAMS_CREDENTIALS_NAME=companion-google-drive 84 | 85 | VITE_TRANSLOADIT_KEY=*** 86 | VITE_TRANSLOADIT_TEMPLATE=*** 87 | VITE_TRANSLOADIT_SERVICE_URL=https://api2.transloadit.com 88 | # Fill in if you want requests sent to Transloadit to be signed: 89 | # VITE_TRANSLOADIT_SECRET=*** 90 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | lib 3 | dist 4 | coverage 5 | test/lib/** 6 | test/endtoend/*/build 7 | examples/svelte-example/public/build/ 8 | bundle-legacy.js 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | Thumbs.db 3 | npm-debug.log 4 | npm-debug.log* 5 | nohup.out 6 | node_modules 7 | .angular 8 | .cache 9 | .parcel-cache 10 | .eslintcache 11 | .vscode/settings.json 12 | .yarn/cache 13 | .yarn/install-state.gz 14 | yarn-error.log 15 | .idea 16 | .env 17 | tsconfig.tsbuildinfo 18 | tsconfig.build.tsbuildinfo 19 | 20 | dist/ 21 | lib/ 22 | coverage/ 23 | examples/dev/bundle.js 24 | examples/aws-php/vendor/* 25 | test/endtoend/create-react-app/build/ 26 | test/endtoend/create-react-app/coverage/ 27 | ImgProcessor-*.tgz 28 | generatedLocale.d.ts 29 | 30 | **/output/* 31 | !output/.keep 32 | examples/dev/file.txt 33 | issues.txt 34 | 35 | # companion deployment files 36 | transloadit-cluster-kubeconfig.yaml 37 | companion-env.yml 38 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | *.js 3 | *.jsx 4 | *.cjs 5 | *.mjs 6 | !private/js2ts/* 7 | *.md 8 | *.lock 9 | -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | proseWrap: 'always', 3 | singleQuote: true, 4 | trailingComma: 'all', 5 | semi: false, 6 | overrides: [ 7 | { 8 | files: 'packages/@ImgProcessor/angular/**', 9 | options: { 10 | semi: true, 11 | }, 12 | }, 13 | ], 14 | } 15 | -------------------------------------------------------------------------------- /.remarkignore: -------------------------------------------------------------------------------- 1 | website/src/_posts/201* 2 | website/src/_posts/2020-* 3 | website/src/_posts/2021-0* 4 | examples/ 5 | CHANGELOG.md 6 | CHANGELOG.next.md 7 | BACKLOG.md 8 | node_modules/ 9 | -------------------------------------------------------------------------------- /.stylelintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "stylelint-config-standard", 4 | "stylelint-config-standard-scss", 5 | "stylelint-config-rational-order" 6 | ], 7 | "rules": { 8 | "at-rule-no-unknown": null, 9 | "scss/at-rule-no-unknown": true 10 | }, 11 | "defaultSeverity": "warning" 12 | } 13 | -------------------------------------------------------------------------------- /.vscode/uppy.code-workspace: -------------------------------------------------------------------------------- 1 | { 2 | "folders": [ 3 | { 4 | "path": ".." 5 | } 6 | ], 7 | "settings": { 8 | "workbench.colorCustomizations": { 9 | "titleBar.activeForeground": "#ffffff", 10 | "titleBar.activeBackground": "#ff009d", 11 | }, 12 | "search.exclude": { 13 | "website/public/": true, 14 | "node_modules/": true, 15 | "website/node_modules/": true, 16 | "dist/": true, 17 | "lib/": true, 18 | "package-lock.json": true, 19 | "website/package-lock.json": true, 20 | "yarn-error.log": true, 21 | "website/.deploy_git": true, 22 | "npm-debug.log": true, 23 | "website/npm-debug.log": true, 24 | "website/debug.log": true, 25 | "nohup.out": true, 26 | "yarn.lock": true 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /.vscode/uppy.code-workspace.bak: -------------------------------------------------------------------------------- 1 | { 2 | "folders": [ 3 | { 4 | "path": "." 5 | } 6 | ], 7 | "settings": { 8 | "workbench.colorCustomizations": { 9 | "titleBar.activeForeground": "#ffffff", 10 | "titleBar.activeBackground": "#ff009d", 11 | }, 12 | "search.exclude": { 13 | "website/public/": true, 14 | "node_modules/": true, 15 | "website/node_modules/": true, 16 | "dist/": true, 17 | "lib/": true, 18 | "package-lock.json": true, 19 | "website/package-lock.json": true, 20 | "yarn-error.log": true, 21 | "website/.deploy_git": true, 22 | "npm-debug.log": true, 23 | "website/npm-debug.log": true, 24 | "website/debug.log": true, 25 | "nohup.out": true, 26 | "yarn.lock": true 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /.yarn/patches/p-queue-npm-7.4.1-e0cf0a6f17.patch: -------------------------------------------------------------------------------- 1 | diff --git a/package.json b/package.json 2 | index 8367745346fffd144a817ccf04912bb799e18b66..66dd17a4cd736089a332d72a70040701b0cd9c93 100644 3 | --- a/package.json 4 | +++ b/package.json 5 | @@ -6,6 +6,7 @@ 6 | "repository": "sindresorhus/p-queue", 7 | "funding": "https://github.com/sponsors/sindresorhus", 8 | "type": "module", 9 | + "main": "./dist/index.js", 10 | "exports": "./dist/index.js", 11 | "engines": { 12 | "node": ">=12" 13 | -------------------------------------------------------------------------------- /.yarn/patches/pre-commit-npm-1.2.2-f30af83877.patch: -------------------------------------------------------------------------------- 1 | diff --git a/index.js b/index.js 2 | index a20646d922945004cb737918ef6b6d063bb3c2a4..a44863e9555abdaa569f309b1197fddc8dd244a5 100644 3 | --- a/index.js 4 | +++ b/index.js 5 | @@ -147,7 +147,7 @@ Hook.prototype.log = function log(lines, exit) { 6 | * @api private 7 | */ 8 | Hook.prototype.initialize = function initialize() { 9 | - ['git', 'npm'].forEach(function each(binary) { 10 | + ['git', 'corepack'].forEach(function each(binary) { 11 | try { this[binary] = which.sync(binary); } 12 | catch (e) {} 13 | }, this); 14 | @@ -159,9 +159,9 @@ Hook.prototype.initialize = function initialize() { 15 | if (!this.npm) { 16 | try { 17 | process.env.PATH += path.delimiter + path.dirname(process.env._); 18 | - this.npm = which.sync('npm'); 19 | + this.npm = which.sync('corepack'); 20 | } catch (e) { 21 | - return this.log(this.format(Hook.log.binary, 'npm'), 0); 22 | + return this.log(this.format(Hook.log.binary, 'corepack'), 0); 23 | } 24 | } 25 | 26 | @@ -225,7 +225,7 @@ Hook.prototype.run = function runner() { 27 | // this doesn't have the required `isAtty` information that libraries use to 28 | // output colors resulting in script output that doesn't have any color. 29 | // 30 | - spawn(hooked.npm, ['run', script, '--silent'], { 31 | + spawn(hooked.npm, ['yarn', script], { 32 | env: process.env, 33 | cwd: hooked.root, 34 | stdio: [0, 1, 2] 35 | -------------------------------------------------------------------------------- /.yarn/patches/preact-npm-10.10.0-dd04de05e8.patch: -------------------------------------------------------------------------------- 1 | diff --git a/debug/package.json b/debug/package.json 2 | index 054944f5478a0a5cf7b6b8791950c595f956157b..06a4fe2719605eb42c5ee795101c21cfd10b59ce 100644 3 | --- a/debug/package.json 4 | +++ b/debug/package.json 5 | @@ -9,6 +9,7 @@ 6 | "umd:main": "dist/debug.umd.js", 7 | "source": "src/index.js", 8 | "license": "MIT", 9 | + "type": "module", 10 | "mangle": { 11 | "regex": "^(?!_renderer)^_" 12 | }, 13 | diff --git a/devtools/package.json b/devtools/package.json 14 | index 09b04a77690bdfba01083939ff9eaf987dd50bcb..92c159fbb3cf312c6674202085fb237d6fb921ad 100644 15 | --- a/devtools/package.json 16 | +++ b/devtools/package.json 17 | @@ -10,6 +10,7 @@ 18 | "source": "src/index.js", 19 | "license": "MIT", 20 | "types": "src/index.d.ts", 21 | + "type": "module", 22 | "peerDependencies": { 23 | "preact": "^10.0.0" 24 | }, 25 | diff --git a/hooks/package.json b/hooks/package.json 26 | index 74807025bf3de273ebada2cd355428a2c972503d..98501726ffbfe55ffa09928e56a9dcafb9a348ff 100644 27 | --- a/hooks/package.json 28 | +++ b/hooks/package.json 29 | @@ -10,6 +10,7 @@ 30 | "source": "src/index.js", 31 | "license": "MIT", 32 | "types": "src/index.d.ts", 33 | + "type": "module", 34 | "scripts": { 35 | "build": "microbundle build --raw", 36 | "dev": "microbundle watch --raw --format cjs", 37 | diff --git a/jsx-runtime/package.json b/jsx-runtime/package.json 38 | index 7a4027831223f16519a74e3028c34f2f8f5f011a..6b58d17dbacce81894467ef43c0a8e2435e388c4 100644 39 | --- a/jsx-runtime/package.json 40 | +++ b/jsx-runtime/package.json 41 | @@ -10,6 +10,7 @@ 42 | "source": "src/index.js", 43 | "types": "src/index.d.ts", 44 | "license": "MIT", 45 | + "type": "module", 46 | "peerDependencies": { 47 | "preact": "^10.0.0" 48 | }, 49 | diff --git a/package.json b/package.json 50 | index 60279c24a08b808ffbf7dc64a038272bddb6785d..088f35fb2c92f2e9b7248557857af2839988d1aa 100644 51 | --- a/package.json 52 | +++ b/package.json 53 | @@ -9,6 +9,7 @@ 54 | "umd:main": "dist/preact.umd.js", 55 | "unpkg": "dist/preact.min.js", 56 | "source": "src/index.js", 57 | + "type": "module", 58 | "exports": { 59 | ".": { 60 | "types": "./src/index.d.ts", 61 | -------------------------------------------------------------------------------- /.yarn/patches/start-server-and-test-npm-1.14.0-841aa34fdf.patch: -------------------------------------------------------------------------------- 1 | diff --git a/src/utils.js b/src/utils.js 2 | index 1f636c6617a71a68318dc587a1c9e6081020f9aa..b28e840ed08f26a4eadd242a6f541fbaefea0eda 100644 3 | --- a/src/utils.js 4 | +++ b/src/utils.js 5 | @@ -112,7 +112,7 @@ const getArguments = cliArgs => { 6 | } 7 | 8 | function normalizeCommand (command) { 9 | - return UTILS.isPackageScriptName(command) ? `npm run ${command}` : command 10 | + return UTILS.isPackageScriptName(command) ? `corepack yarn ${command}` : command 11 | } 12 | 13 | /** 14 | -------------------------------------------------------------------------------- /.yarn/patches/stylelint-config-rational-order-npm-0.1.2-d8336e84ed.patch: -------------------------------------------------------------------------------- 1 | diff --git a/package.json b/package.json 2 | index a2047a8b2895a64a4cbf7b493362ee1d72c43771..7478198712b460936f6b7f2557b116c52f4d71b5 100644 3 | --- a/package.json 4 | +++ b/package.json 5 | @@ -30,8 +30,8 @@ 6 | "order" 7 | ], 8 | "dependencies": { 9 | - "stylelint": "^9.10.1", 10 | - "stylelint-order": "^2.2.1" 11 | + "stylelint": "^15.0.1", 12 | + "stylelint-order": "^6.0.3" 13 | }, 14 | "devDependencies": { 15 | "eslint": "^5.16.0", 16 | -------------------------------------------------------------------------------- /.yarn/patches/uuid-npm-8.3.2-eca0baba53.patch: -------------------------------------------------------------------------------- 1 | diff --git a/package.json b/package.json 2 | index f0ab3711ee4f490cbf961ebe6283ce2a28b6824b..644235a3ef52c974e946403a3fcdd137d01fad0c 100644 3 | --- a/package.json 4 | +++ b/package.json 5 | @@ -25,6 +25,7 @@ 6 | "require": "./dist/index.js", 7 | "import": "./wrapper.mjs" 8 | }, 9 | + "jest": "./dist/index.js", 10 | "default": "./dist/esm-browser/index.js" 11 | }, 12 | "./package.json": "./package.json" 13 | -------------------------------------------------------------------------------- /.yarnrc.yml: -------------------------------------------------------------------------------- 1 | changesetBaseRefs: 2 | - main 3 | - upstream/main 4 | - origin/main 5 | 6 | initScope: ImgProcessor 7 | 8 | enableGlobalCache: false 9 | nodeLinker: node-modules 10 | 11 | plugins: 12 | - path: .yarn/plugins/@yarnpkg/plugin-workspace-tools.cjs 13 | spec: '@yarnpkg/plugin-workspace-tools' 14 | - path: .yarn/plugins/@yarnpkg/plugin-version.cjs 15 | spec: '@yarnpkg/plugin-version' 16 | -------------------------------------------------------------------------------- /BUNDLE-README.md: -------------------------------------------------------------------------------- 1 | # ImgProcessor 2 | 3 | Note that the recommended way to use ImgProcessor is to install it with yarn/npm and use a 4 | bundler like Webpack so that you can create a smaller custom build with only the 5 | things that you need. More info on . 6 | 7 | ## How to use this bundle 8 | 9 | You can extract the contents of this zip to directory, such as `./js/ImgProcessor`. 10 | 11 | create an HTML file, for example `./start.html`, with the following contents: 12 | 13 | ```html 14 | 15 | 16 | 17 | 18 | 19 | 20 |
21 | 22 |
23 |
Uploaded files:
24 |
    25 |
    26 | 27 | 28 | 56 | ``` 57 | 58 | Now open `start.html` in your browser, and the ImgProcessor Dashboard will appear. 59 | 60 | ## Next steps 61 | 62 | In the example you built, ImgProcessor uploads to a demo server shortly after uploading. 63 | You’ll want to target your own tusd server, S3 bucket, or Nginx/Apache server. For the latter, use the Xhr plugin: which uploads using regular multipart form posts, that you’ll existing Ruby or PHP backend will be able to make sense of, as if a `` had been used. 64 | 65 | The Dashboard now opens when clicking the button, but you can also draw it inline into the page. This, and many more configuration options can be found here: . 66 | 67 | ImgProcessor has many more Plugins besides Xhr and the Dashboard. For example, you can enable Webcam, Instagram, or video encoding support. For a full list of Plugins check here: . 68 | 69 | Note that for some Plugins, you will need to run a server side component called: Companion. Those plugins are marked with a (c) symbol. Alternatively, you can sign up for a free Transloadit account. Transloadit runs Companion for you, tusd servers to handle resumable file uploads, and can post-process files to scan for viruses, recognize faces, etc. Check: . 70 | 71 | 72 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:18.17.1-alpine as build 2 | 3 | # Create link to node on amd64 so that corepack can find it 4 | RUN if [ "$(uname -m)" == "aarch64" ]; then mkdir -p /usr/local/sbin/ && ln -s /usr/local/bin/node /usr/local/sbin/node; fi 5 | 6 | WORKDIR /app 7 | 8 | COPY . /app/ 9 | 10 | RUN apk --update add --virtual native-dep \ 11 | make gcc g++ python3 libgcc libstdc++ git && \ 12 | (cd /app && corepack yarn workspaces focus @ImgProcessor/companion) && \ 13 | apk del native-dep 14 | 15 | RUN cd /app && corepack yarn workspace @ImgProcessor/companion build 16 | 17 | # Now remove all non-prod dependencies for a leaner image 18 | RUN cd /app && corepack yarn workspaces focus @ImgProcessor/companion --production 19 | 20 | FROM node:18.17.1-alpine 21 | 22 | WORKDIR /app 23 | 24 | # copy required files from build stage. 25 | COPY --from=build /app/packages/@ImgProcessor/companion/bin /app/bin 26 | COPY --from=build /app/packages/@ImgProcessor/companion/lib /app/lib 27 | COPY --from=build /app/packages/@ImgProcessor/companion/package.json /app/package.json 28 | COPY --from=build /app/packages/@ImgProcessor/companion/node_modules /app/node_modules 29 | 30 | ENV PATH "${PATH}:/app/node_modules/.bin" 31 | 32 | CMD ["node","/app/bin/companion"] 33 | # This can be overruled later 34 | EXPOSE 3020 35 | -------------------------------------------------------------------------------- /Dockerfile.test: -------------------------------------------------------------------------------- 1 | FROM node:18.17.1-alpine 2 | 3 | COPY package.json /app/package.json 4 | 5 | WORKDIR /app 6 | 7 | RUN apk --update add --virtual native-dep \ 8 | make gcc g++ python3 libgcc libstdc++ git && \ 9 | corepack yarn install && \ 10 | apk del native-dep 11 | RUN apk add bash 12 | 13 | COPY . /app 14 | RUN npm install -g nodemon 15 | CMD ["npm","test"] 16 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2019 Transloadit (https://transloadit.com) 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 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Licensed under MIT. 2 | # Copyright (2016) by Kevin van Zonneveld https://twitter.com/kvz 3 | # 4 | # https://www.npmjs.com/package/fakefile 5 | # 6 | # Please do not edit this file directly, but propose changed upstream instead: 7 | # https://github.com/kvz/fakefile/blob/master/Makefile 8 | # 9 | # This Makefile offers convience shortcuts into any Node.js project that utilizes npm scripts. 10 | # It functions as a wrapper around the actual listed in `package.json` 11 | # So instead of typing: 12 | # 13 | # $ npm script build:assets 14 | # 15 | # you could also type: 16 | # 17 | # $ make build-assets 18 | # 19 | # Notice that colons (:) are replaced by dashes for Makefile compatibility. 20 | # 21 | # The benefits of this wrapper are: 22 | # 23 | # - You get to keep the the scripts package.json, which is more portable 24 | # (Makefiles & Windows are harder to mix) 25 | # - Offer a polite way into the project for developers coming from different 26 | # languages (npm scripts is obviously very Node centric) 27 | # - Profit from better autocomplete (make ) than npm currently offers. 28 | # OSX users will have to install bash-completion 29 | # (http://davidalger.com/development/bash-completion-on-os-x-with-brew/) 30 | 31 | define npm_script_targets 32 | TARGETS := $(shell node -e 'for (var k in require("./package.json").scripts) {console.log(k.replace(/:/g, "-"));}') 33 | $$(TARGETS): 34 | npm run $(subst -,:,$(MAKECMDGOALS)) 35 | 36 | .PHONY: $$(TARGETS) 37 | endef 38 | 39 | $(eval $(call npm_script_targets)) 40 | 41 | # These npm run scripts are available, without needing to be mentioned in `package.json` 42 | install: 43 | npm install 44 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CoderDonna/ImgProcessor/65c2a84b2335af172f2bcb8edc9ea6561db555fe/README.md -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CoderDonna/ImgProcessor/65c2a84b2335af172f2bcb8edc9ea6561db555fe/SECURITY.md -------------------------------------------------------------------------------- /atest.ps1: -------------------------------------------------------------------------------- 1 | # Define the path to your Node.js project 2 | $projectPath = "E:\JavaProjects\ImgProcessor" 3 | 4 | # Define the strings for search and replace 5 | $searchString = "ImgProcessor" 6 | $replaceString = "ImgProcessor" 7 | 8 | # Function to rename directories 9 | Function Rename-Directories { 10 | param ( 11 | [string]$path 12 | ) 13 | 14 | # Get all directories in the path, excluding the root 15 | $directories = Get-ChildItem -Path $path -Recurse -Directory | Sort-Object FullName -Descending 16 | 17 | foreach ($dir in $directories) { 18 | $newName = $dir.Name -replace $searchString, $replaceString 19 | if ($newName -ne $dir.Name) { 20 | $newPath = Join-Path $dir.Parent.FullName $newName 21 | Rename-Item -Path $dir.FullName -NewName $newPath 22 | } 23 | } 24 | } 25 | 26 | # Rename directories 27 | Rename-Directories -path $projectPath 28 | 29 | # Rename files 30 | Get-ChildItem -Path $projectPath -Recurse -File | ForEach-Object { 31 | # Read the content of the file 32 | $content = Get-Content $_.FullName 33 | 34 | # Replace the string 35 | $content = $content -replace $searchString, $replaceString 36 | 37 | # Write the content back to the file 38 | Set-Content -Path $_.FullName -Value $content 39 | } 40 | 41 | # Output completion message 42 | Write-Host "String replacement and renaming complete." 43 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = (api) => { 2 | const targets = {} 3 | if (api.env('test')) { 4 | targets.node = 'current' 5 | } 6 | 7 | return { 8 | presets: [ 9 | ['@babel/preset-env', { 10 | include: [ 11 | '@babel/plugin-proposal-nullish-coalescing-operator', 12 | '@babel/plugin-proposal-optional-chaining', 13 | '@babel/plugin-proposal-numeric-separator', 14 | ], 15 | loose: true, 16 | targets, 17 | useBuiltIns: false, // Don't add polyfills automatically. 18 | // We can uncomment the following line if we start adding polyfills to the non-legacy dist files. 19 | // corejs: { version: '3.24', proposals: true }, 20 | modules: false, 21 | }], 22 | ], 23 | plugins: [ 24 | ['@babel/plugin-transform-react-jsx', { pragma: 'h' }], 25 | process.env.NODE_ENV !== 'dev' && 'babel-plugin-inline-package-json', 26 | ].filter(Boolean), 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /bin/build-bundle.mjs: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | import fs from 'node:fs/promises' 4 | import path from 'node:path' 5 | import chalk from 'chalk' 6 | 7 | import esbuild from 'esbuild' 8 | import babel from 'esbuild-plugin-babel' 9 | 10 | const ImgProcessor_ROOT = new URL('../', import.meta.url) 11 | const PACKAGES_ROOT = new URL('./packages/', ImgProcessor_ROOT) 12 | 13 | function buildBundle (srcFile, bundleFile, { minify = true, standalone = '', plugins, target, format } = {}) { 14 | return esbuild.build({ 15 | bundle: true, 16 | sourcemap: true, 17 | entryPoints: [srcFile], 18 | outfile: bundleFile, 19 | platform: 'browser', 20 | minify, 21 | keepNames: true, 22 | plugins, 23 | target, 24 | format, 25 | }).then(() => { 26 | if (minify) { 27 | console.info(chalk.green(`✓ Built Minified Bundle [${standalone}]:`), chalk.magenta(bundleFile)) 28 | } else { 29 | console.info(chalk.green(`✓ Built Bundle [${standalone}]:`), chalk.magenta(bundleFile)) 30 | } 31 | }) 32 | } 33 | 34 | await fs.mkdir(new URL('./ImgProcessor/dist', PACKAGES_ROOT), { recursive: true }) 35 | await fs.mkdir(new URL('./@ImgProcessor/locales/dist', PACKAGES_ROOT), { recursive: true }) 36 | 37 | const methods = [ 38 | buildBundle( 39 | './packages/ImgProcessor/index.mjs', 40 | './packages/ImgProcessor/dist/ImgProcessor.min.mjs', 41 | { standalone: 'ImgProcessor (ESM)', format: 'esm' }, 42 | ), 43 | buildBundle( 44 | './packages/ImgProcessor/bundle.mjs', 45 | './packages/ImgProcessor/dist/ImgProcessor.min.js', 46 | { standalone: 'ImgProcessor', format: 'iife' }, 47 | ), 48 | buildBundle( 49 | './packages/ImgProcessor/bundle-legacy.mjs', 50 | './packages/ImgProcessor/dist/ImgProcessor.legacy.min.js', 51 | { 52 | standalone: 'ImgProcessor (with polyfills)', 53 | target: 'es5', 54 | plugins:[babel({ 55 | config:{ 56 | compact: true, 57 | highlightCode: true, 58 | inputSourceMap: true, 59 | 60 | browserslistEnv: 'legacy', 61 | presets: [['@babel/preset-env', { 62 | loose: false, 63 | targets: { ie:11 }, 64 | useBuiltIns: 'entry', 65 | corejs: { version: '3.24', proposals: true }, 66 | }]], 67 | }, 68 | })], 69 | }, 70 | ), 71 | ] 72 | 73 | // Build mini versions of all the locales 74 | const localesModules = await fs.opendir(new URL('./@ImgProcessor/locales/src/', PACKAGES_ROOT)) 75 | for await (const dirent of localesModules) { 76 | if (!dirent.isDirectory() && dirent.name.endsWith('.js')) { 77 | const localeName = path.basename(dirent.name, '.js') 78 | methods.push( 79 | buildBundle( 80 | `./packages/@ImgProcessor/locales/src/${localeName}.js`, 81 | `./packages/@ImgProcessor/locales/dist/${localeName}.min.js`, 82 | { minify: true }, 83 | ), 84 | ) 85 | } 86 | } 87 | 88 | // Add BUNDLE-README.MD 89 | methods.push( 90 | fs.copyFile( 91 | new URL('./BUNDLE-README.md', ImgProcessor_ROOT), 92 | new URL('./ImgProcessor/dist/README.md', PACKAGES_ROOT), 93 | ), 94 | ) 95 | 96 | await Promise.all(methods).then(() => { 97 | console.info(chalk.yellow('✓ JS bundles 🎉')) 98 | }, (err) => { 99 | console.error(chalk.red('✗ Error:'), chalk.red(err.message)) 100 | }) 101 | -------------------------------------------------------------------------------- /bin/build-css.js: -------------------------------------------------------------------------------- 1 | const sass = require('sass'); 2 | const postcss = require('postcss'); 3 | const autoprefixer = require('autoprefixer'); 4 | const postcssLogical = require('postcss-logical'); 5 | const postcssDirPseudoClass = require('postcss-dir-pseudo-class'); 6 | const cssnano = require('cssnano'); 7 | const { promisify } = require('node:util'); 8 | const fs = require('node:fs'); 9 | const path = require('node:path'); 10 | const resolve = require('resolve'); 11 | const glob = promisify(require('glob')); 12 | 13 | const renderScss = promisify(sass.render); 14 | const { mkdir, writeFile } = fs.promises; 15 | 16 | const cwd = process.cwd(); 17 | let chalk; 18 | 19 | function handleErr(err) { 20 | console.error(chalk.red('✗ Error:'), chalk.red(err.message)); 21 | } 22 | 23 | async function compileCSS() { 24 | ({ default: chalk } = await import('chalk')); 25 | const files = await glob('packages/{,@ImgProcessor/}*/src/style.scss'); 26 | 27 | // PostCSS plugins initialization 28 | const plugins = [ 29 | autoprefixer, 30 | postcssLogical(), 31 | postcssDirPseudoClass(), 32 | ]; 33 | 34 | for (const file of files) { 35 | const importedFiles = new Set(); 36 | const scssResult = await renderScss({ 37 | file, 38 | importer: createImporter(importedFiles), 39 | }); 40 | 41 | const postcssResult = await processCSS(scssResult.css, file, plugins); 42 | const outputDir = path.join(path.dirname(file), '../dist'); 43 | const outfile = determineOutfile(outputDir, 'style.css', 'ImgProcessor.css'); 44 | await saveAndLogCSS(outfile, postcssResult.css); 45 | 46 | const minifiedResult = await minifyCSS(outfile, postcssResult.css); 47 | await saveAndLogCSS(outfile.replace(/\.css$/, '.min.css'), minifiedResult.css); 48 | } 49 | } 50 | 51 | // Modularize the importer function 52 | function createImporter(importedFiles) { 53 | return (url, from, done) => { 54 | resolve(url, { 55 | basedir: path.dirname(from), 56 | filename: from, 57 | extensions: ['.scss'], 58 | }, (err, resolved) => { 59 | if (err) { 60 | done(err); 61 | return; 62 | } 63 | 64 | const realpath = fs.realpathSync(resolved); 65 | if (importedFiles.has(realpath)) { 66 | done({ contents: '' }); 67 | return; 68 | } 69 | importedFiles.add(realpath); 70 | done({ file: realpath }); 71 | }); 72 | }; 73 | } 74 | 75 | // Process CSS with PostCSS 76 | async function processCSS(css, file, plugins) { 77 | const result = await postcss(plugins).process(css, { from: file }); 78 | result.warnings().forEach(warn => console.warn(warn.toString())); 79 | return result; 80 | } 81 | 82 | // Minify CSS 83 | async function minifyCSS(outfile, css) { 84 | const result = await postcss([cssnano({ safe: true })]).process(css, { from: outfile }); 85 | result.warnings().forEach(warn => console.warn(warn.toString())); 86 | return result; 87 | } 88 | 89 | // Save and Log CSS 90 | async function saveAndLogCSS(outfile, css) { 91 | await mkdir(path.dirname(outfile), { recursive: true }); 92 | await writeFile(outfile, css); 93 | console.info(chalk.green('✓ CSS Processed:'), chalk.magenta(path.relative(cwd, outfile))); 94 | } 95 | 96 | // Determine the output file name 97 | function determineOutfile(outputDir, defaultName, packageName) { 98 | return outputDir.includes(path.normalize('packages/ImgProcessor/')) ? 99 | path.join(outputDir, packageName) : 100 | path.join(outputDir, defaultName); 101 | } 102 | 103 | compileCSS().then(() => { 104 | console.info(chalk.yellow('CSS Bundles OK')); 105 | }, handleErr); 106 | -------------------------------------------------------------------------------- /bin/build-lib.js: -------------------------------------------------------------------------------- 1 | const babel = require('@babel/core') 2 | const t = require('@babel/types') 3 | const { promisify } = require('node:util') 4 | const glob = promisify(require('glob')) 5 | const fs = require('node:fs') 6 | const path = require('node:path') 7 | 8 | const { mkdir, stat, writeFile } = fs.promises 9 | 10 | const PACKAGE_JSON_IMPORT = /^\..*\/package.json$/ 11 | const SOURCE = 'packages/{*,@ImgProcessor/*}/src/**/*.{js,ts}?(x)' 12 | const IGNORE = /\.test\.[jt]s$|__mocks__|svelte|angular|companion\// 13 | const META_FILES = [ 14 | 'babel.config.js', 15 | 'package.json', 16 | 'package-lock.json', 17 | 'yarn.lock', 18 | 'bin/build-lib.js', 19 | ] 20 | 21 | function lastModified (file, createParentDir = false) { 22 | return stat(file).then((s) => s.mtime, async (err) => { 23 | if (err.code === 'ENOENT') { 24 | if (createParentDir) { 25 | await mkdir(path.dirname(file), { recursive: true }) 26 | } 27 | return 0 28 | } 29 | throw err 30 | }) 31 | } 32 | 33 | const versionCache = new Map() 34 | 35 | async function preparePack (file) { 36 | const packageFolder = file.slice(0, file.indexOf('/src/')) 37 | if (versionCache.has(packageFolder)) return 38 | 39 | // eslint-disable-next-line import/no-dynamic-require, global-require 40 | const { version } = require(path.join(__dirname, '..', packageFolder, 'package.json')) 41 | if (process.env.FRESH) { 42 | // in case it hasn't been done before. 43 | await mkdir(path.join(packageFolder, 'lib'), { recursive: true }) 44 | } 45 | versionCache.set(packageFolder, version) 46 | } 47 | 48 | const nonJSImport = /^\.\.?\/.+\.([jt]sx|ts)$/ 49 | // eslint-disable-next-line no-shadow 50 | function rewriteNonJSImportsToJS (path) { 51 | const match = nonJSImport.exec(path.node.source.value) 52 | if (match) { 53 | // eslint-disable-next-line no-param-reassign 54 | path.node.source.value = `${match[0].slice(0, -match[1].length)}js` 55 | } 56 | } 57 | 58 | async function buildLib () { 59 | const metaMtimes = await Promise.all(META_FILES.map((filename) => lastModified(path.join(__dirname, '..', filename)))) 60 | const metaMtime = Math.max(...metaMtimes) 61 | 62 | const files = await glob(SOURCE) 63 | /* eslint-disable no-continue */ 64 | for (const file of files) { 65 | if (IGNORE.test(file)) { 66 | continue 67 | } 68 | await preparePack(file) 69 | const libFile = file.replace('/src/', '/lib/').replace(/\.[jt]sx?$/, '.js') 70 | 71 | // on a fresh build, rebuild everything. 72 | if (!process.env.FRESH) { 73 | const [srcMtime, libMtime] = await Promise.all([ 74 | lastModified(file), 75 | lastModified(libFile, true), 76 | ]) 77 | if (srcMtime < libMtime && metaMtime < libMtime) { 78 | continue 79 | } 80 | } 81 | 82 | const plugins = [{ 83 | visitor: { 84 | // eslint-disable-next-line no-shadow 85 | ImportDeclaration (path) { 86 | rewriteNonJSImportsToJS(path) 87 | if (PACKAGE_JSON_IMPORT.test(path.node.source.value) 88 | && path.node.specifiers.length === 1 89 | && path.node.specifiers[0].type === 'ImportDefaultSpecifier') { 90 | const version = versionCache.get(file.slice(0, file.indexOf('/src/'))) 91 | if (version != null) { 92 | const [{ local }] = path.node.specifiers 93 | path.replaceWith( 94 | t.variableDeclaration('const', [t.variableDeclarator(local, 95 | t.objectExpression([ 96 | t.objectProperty(t.stringLiteral('version'), t.stringLiteral(version)), 97 | ]))]), 98 | ) 99 | } 100 | } 101 | }, 102 | 103 | ExportAllDeclaration: rewriteNonJSImportsToJS, 104 | }, 105 | }] 106 | const isTSX = file.endsWith('.tsx') 107 | if (isTSX || file.endsWith('.ts')) { plugins.push(['@babel/plugin-transform-typescript', { disallowAmbiguousJSXLike: true, isTSX, jsxPragma: 'h' }]) } 108 | 109 | const { code, map } = await babel.transformFileAsync(file, { sourceMaps: true, plugins }) 110 | const [{ default: chalk }] = await Promise.all([ 111 | import('chalk'), 112 | writeFile(libFile, code), 113 | writeFile(`${libFile}.map`, JSON.stringify(map)), 114 | ]) 115 | console.log(chalk.green('Compiled lib:'), chalk.magenta(libFile)) 116 | } 117 | /* eslint-enable no-continue */ 118 | } 119 | 120 | console.log('Using Babel version:', require('@babel/core/package.json').version) 121 | 122 | buildLib().catch((err) => { 123 | console.error(err) 124 | process.exit(1) 125 | }) 126 | -------------------------------------------------------------------------------- /bin/build-ts.mjs: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | import { spawn } from 'node:child_process' 4 | import { once } from 'node:events' 5 | import { existsSync } from 'node:fs' 6 | import path from 'node:path' 7 | import { stdin, env } from 'node:process' 8 | import { createInterface as readLines } from 'node:readline' 9 | import { fileURLToPath } from 'node:url' 10 | 11 | const fromYarn = 'npm_execpath' in env 12 | const exe = fromYarn ? env.npm_execpath : 'corepack' 13 | const argv0 = fromYarn ? [] : ['yarn'] 14 | 15 | const cwd = fileURLToPath(new URL('../', import.meta.url)) 16 | 17 | const locations = [] 18 | 19 | for await (const line of readLines(stdin)) { 20 | const { location } = JSON.parse(line) 21 | if (existsSync(path.join(cwd, location, 'tsconfig.json'))) { 22 | locations.unshift(location) 23 | } 24 | const tsConfigBuildPath = path.join(cwd, location, 'tsconfig.build.json') 25 | if (existsSync(tsConfigBuildPath)) { 26 | locations.push(tsConfigBuildPath) 27 | } 28 | } 29 | 30 | const cp = spawn(exe, [...argv0, 'tsc', '--build', ...locations], { 31 | stdio: 'inherit', 32 | cwd, 33 | }) 34 | await Promise.race([ 35 | once(cp, 'error').then(err => Promise.reject(err)), 36 | await once(cp, 'exit') 37 | .then(([code]) => (code && Promise.reject(new Error(`Non-zero exit code when building TS projects: ${code}`)))), 38 | ]) 39 | -------------------------------------------------------------------------------- /bin/companion.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Load local env vars. In CI, these are injected. 4 | if [ -f .env ]; then 5 | nodemon --watch packages/@ImgProcessor/companion/src --exec node -r dotenv/config ./packages/@ImgProcessor/companion/src/standalone/start-server.js 6 | else 7 | env \ 8 | COMPANION_DATADIR="./output" \ 9 | COMPANION_DOMAIN="localhost:3020" \ 10 | COMPANION_PROTOCOL="http" \ 11 | COMPANION_PORT=3020 \ 12 | COMPANION_CLIENT_ORIGINS="" \ 13 | COMPANION_SECRET="development" \ 14 | COMPANION_PREAUTH_SECRET="development2" \ 15 | COMPANION_ALLOW_LOCAL_URLS="true" \ 16 | nodemon --watch packages/@ImgProcessor/companion/src --exec node ./packages/@ImgProcessor/companion/src/standalone/start-server.js 17 | fi 18 | 19 | -------------------------------------------------------------------------------- /bin/to-gif-hd.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # Convert a video file to a gif. 3 | # `to-gif /path/to/input.mp4 /path/to/output.gif` 4 | palette="/tmp/to-gif-palette.png" 5 | filters="fps=15" 6 | ffmpeg -v warning -i $1 -vf "$filters,palettegen" -y $palette 7 | ffmpeg -v warning -i $1 -i $palette -lavfi "$filters [x]; [x][1:v] paletteuse" -y $2 8 | 9 | # resize after 10 | # gifsicle --resize-fit-width 1000 -i animation.gif > animation-1000px.gif 11 | -------------------------------------------------------------------------------- /bin/to-gif-hq.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # Convert a video file to a gif. 3 | # `to-gif /path/to/input.mp4 /path/to/output.gif` 4 | palette="/tmp/to-gif-palette.png" 5 | filters="fps=15" 6 | ffmpeg -v warning -i $1 -vf "$filters,palettegen" -y $palette 7 | ffmpeg -v warning -i $1 -i $palette -lavfi "$filters [x]; [x][1:v] paletteuse" -y $2 8 | 9 | # gifsicle --resize-fit-width 1000 -i animation.gif > animation-1000px.gif 10 | -------------------------------------------------------------------------------- /bin/to-gif.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -o pipefail 3 | set -o errexit 4 | set -o nounset 5 | 6 | # Set magic variables for current file & dir 7 | __dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" 8 | __file="${__dir}/$(basename "${BASH_SOURCE[0]}")" 9 | __base="$(basename ${__file} .sh)" 10 | __root="$(cd "$(dirname "${__dir}")" && pwd)" 11 | 12 | width=600 13 | speed=0.7 14 | input="${__root}/assets/ImgProcessor-demo-oct-2018.mov" 15 | base="$(basename "${input}")" 16 | output="${__root}/assets/${base}.gif" 17 | 18 | ffmpeg \ 19 | -y \ 20 | -i "${input}" \ 21 | -vf fps=10,scale=${width}:-1:flags=lanczos,palettegen "${__root}/assets/${base}-palette.png" 22 | 23 | ffmpeg \ 24 | -y \ 25 | -i "${input}" \ 26 | -i "${__root}/assets/${base}-palette.png" \ 27 | -filter_complex "setpts=${speed}*PTS,fps=10,scale=${width}:-1:flags=lanczos[x];[x][1:v]paletteuse" \ 28 | "${output}" 29 | 30 | du -hs "${output}" 31 | open -a 'Google Chrome' "${output}" 32 | -------------------------------------------------------------------------------- /bin/update-yarn.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # This script is meant to be run on a dev's machine to update the version on 4 | # Yarn used by the monorepo. Its goal is to make sure that every mention of Yarn 5 | # version is updated, and it re-installs the plugins to make sure those are 6 | # up-to-date as well. 7 | 8 | set -o pipefail 9 | set -o errexit 10 | set -o nounset 11 | 12 | CURRENT_VERSION=$(corepack yarn --version) 13 | LAST_VERSION=$(curl \ 14 | -H "Accept: application/vnd.github.v3+json" \ 15 | https://api.github.com/repos/yarnpkg/berry/releases?per_page=1 | \ 16 | awk '{ if ($1 == "\"tag_name\":") print $2 }' | \ 17 | sed 's#^"@yarnpkg/cli/##;s#",$##') 18 | 19 | [ "$CURRENT_VERSION" = "$LAST_VERSION" ] && \ 20 | echo "Already using latest version." && \ 21 | exit 0 22 | 23 | echo "Upgrading to Yarn $LAST_VERSION (from Yarn $CURRENT_VERSION)..." 24 | 25 | PLUGINS=$(awk '{ if ($1 == "spec:") print $2 }' .yarnrc.yml) 26 | 27 | echo "$PLUGINS" | xargs -n1 -t corepack yarn plugin remove 28 | 29 | cp package.json .yarn/cache/tmp.package.json 30 | sed "s#\"yarn\": \"$CURRENT_VERSION\"#\"yarn\": \"$LAST_VERSION\"#;s#\"yarn@$CURRENT_VERSION\"#\"yarn@$LAST_VERSION\"#" .yarn/cache/tmp.package.json > package.json 31 | rm .yarn/cache/tmp.package.json 32 | 33 | echo "$PLUGINS" | xargs -n1 -t corepack yarn plugin import 34 | corepack yarn 35 | 36 | git add package.json yarn.lock 37 | git add .yarn/plugins 38 | -------------------------------------------------------------------------------- /docker-compose-dev.yml: -------------------------------------------------------------------------------- 1 | version: '3.9' 2 | 3 | services: 4 | ImgProcessor: 5 | image: transloadit/companion 6 | build: 7 | context: . 8 | dockerfile: Dockerfile 9 | environment: 10 | - NODE_ENV=development 11 | volumes: 12 | - ./:/app 13 | - /app/node_modules 14 | - /mnt/ImgProcessor-server-data:/mnt/ImgProcessor-server-data 15 | ports: 16 | - '3020:3020' 17 | command: '/app/src/standalone/start-server.js --config nodemon.json' 18 | env_file: 19 | - .env 20 | -------------------------------------------------------------------------------- /docker-compose-test.yml: -------------------------------------------------------------------------------- 1 | version: '3.9' 2 | 3 | services: 4 | uppy: 5 | image: companion 6 | build: 7 | context: . 8 | dockerfile: Dockerfile.test 9 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.9' 2 | 3 | services: 4 | ImgProcessor: 5 | image: transloadit/companion 6 | build: 7 | context: . 8 | dockerfile: Dockerfile 9 | volumes: 10 | - /app/node_modules 11 | - /mnt/ImgProcessor-server-data:/mnt/ImgProcessor-server-data 12 | ports: 13 | - '3020:3020' 14 | env_file: 15 | - .env 16 | -------------------------------------------------------------------------------- /e2e/.parcelrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@parcel/config-default", 3 | "transformers": { 4 | "*.{js,mjs,jsx,cjs,ts,tsx}": [ 5 | "@parcel/transformer-js", 6 | "@parcel/transformer-react-refresh-wrap" 7 | ] 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /e2e/clients/dashboard-aws-multipart/app.js: -------------------------------------------------------------------------------- 1 | import { ImgProcessor } from '@ImgProcessor/core' 2 | import Dashboard from '@ImgProcessor/dashboard' 3 | import AwsS3Multipart from '@ImgProcessor/aws-s3-multipart' 4 | 5 | import '@ImgProcessor/core/dist/style.css' 6 | import '@ImgProcessor/dashboard/dist/style.css' 7 | 8 | const ImgProcessor = new ImgProcessor() 9 | .use(Dashboard, { target: '#app', inline: true }) 10 | .use(AwsS3Multipart, { 11 | limit: 2, 12 | companionUrl: process.env.VITE_COMPANION_URL, 13 | // This way we can test that the user provided API still works 14 | // as expected in the flow. We call the default internal function for this, 15 | // otherwise we would have to run another server to pre-sign requests 16 | // and we don't care about that, just that the flow works. 17 | async prepareUploadParts (file, { uploadId, key, parts, signal }) { 18 | const { number: partNumber, chunk: body } = parts[0] 19 | const plugin = ImgProcessor.getPlugin('AwsS3Multipart') 20 | const { url } = await plugin.signPart(file, { uploadId, key, partNumber, body, signal }) 21 | return { presignedUrls: { [partNumber]: url } } 22 | }, 23 | }) 24 | 25 | // Keep this here to access ImgProcessor in tests 26 | window.ImgProcessor = ImgProcessor 27 | -------------------------------------------------------------------------------- /e2e/clients/dashboard-aws-multipart/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | dashboard-aws-multipart 6 | 7 | 8 | 9 |
    10 | 11 | 12 | -------------------------------------------------------------------------------- /e2e/clients/dashboard-aws/app.js: -------------------------------------------------------------------------------- 1 | import { ImgProcessor } from '@ImgProcessor/core' 2 | import Dashboard from '@ImgProcessor/dashboard' 3 | import AwsS3 from '@ImgProcessor/aws-s3' 4 | 5 | import '@ImgProcessor/core/dist/style.css' 6 | import '@ImgProcessor/dashboard/dist/style.css' 7 | 8 | const ImgProcessor = new ImgProcessor() 9 | .use(Dashboard, { target: '#app', inline: true }) 10 | .use(AwsS3, { 11 | limit: 2, 12 | companionUrl: process.env.VITE_COMPANION_URL, 13 | }) 14 | 15 | // Keep this here to access ImgProcessor in tests 16 | window.ImgProcessor = ImgProcessor 17 | -------------------------------------------------------------------------------- /e2e/clients/dashboard-aws/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | dashboard-aws 6 | 7 | 8 | 9 |
    10 | 11 | 12 | -------------------------------------------------------------------------------- /e2e/clients/dashboard-compressor/app.js: -------------------------------------------------------------------------------- 1 | import ImgProcessor from '@ImgProcessor/core' 2 | import Dashboard from '@ImgProcessor/dashboard' 3 | import Compressor from '@ImgProcessor/compressor' 4 | 5 | import '@ImgProcessor/core/dist/style.css' 6 | import '@ImgProcessor/dashboard/dist/style.css' 7 | 8 | const ImgProcessor = new ImgProcessor() 9 | .use(Dashboard, { 10 | target: document.body, 11 | inline: true, 12 | }) 13 | .use(Compressor, { 14 | mimeType: 'image/webp', 15 | }) 16 | 17 | // Keep this here to access ImgProcessor in tests 18 | window.ImgProcessor = ImgProcessor 19 | -------------------------------------------------------------------------------- /e2e/clients/dashboard-compressor/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | dashboard-compressor 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /e2e/clients/dashboard-transloadit/app.js: -------------------------------------------------------------------------------- 1 | import { ImgProcessor } from '@ImgProcessor/core' 2 | import Dashboard from '@ImgProcessor/dashboard' 3 | import Transloadit from '@ImgProcessor/transloadit' 4 | 5 | import generateSignatureIfSecret from './generateSignatureIfSecret.js' 6 | 7 | import '@ImgProcessor/core/dist/style.css' 8 | import '@ImgProcessor/dashboard/dist/style.css' 9 | 10 | // Environment variables: 11 | // https://en.parceljs.org/env.html 12 | const ImgProcessor = new ImgProcessor() 13 | .use(Dashboard, { target: '#app', inline: true }) 14 | .use(Transloadit, { 15 | service: process.env.VITE_TRANSLOADIT_SERVICE_URL, 16 | waitForEncoding: true, 17 | getAssemblyOptions: () => generateSignatureIfSecret(process.env.VITE_TRANSLOADIT_SECRET, { 18 | auth: { key: process.env.VITE_TRANSLOADIT_KEY }, 19 | template_id: process.env.VITE_TRANSLOADIT_TEMPLATE, 20 | }), 21 | }) 22 | 23 | // Keep this here to access ImgProcessor in tests 24 | window.ImgProcessor = ImgProcessor 25 | -------------------------------------------------------------------------------- /e2e/clients/dashboard-transloadit/generateSignatureIfSecret.js: -------------------------------------------------------------------------------- 1 | const enc = new TextEncoder('utf-8') 2 | async function sign (secret, body) { 3 | const algorithm = { name: 'HMAC', hash: 'SHA-384' } 4 | 5 | const key = await crypto.subtle.importKey('raw', enc.encode(secret), algorithm, false, ['sign', 'verify']) 6 | const signature = await crypto.subtle.sign(algorithm.name, key, enc.encode(body)) 7 | return `sha384:${Array.from(new Uint8Array(signature), x => x.toString(16).padStart(2, '0')).join('')}` 8 | } 9 | function getExpiration (future) { 10 | return new Date(Date.now() + future) 11 | .toISOString() 12 | .replace('T', ' ') 13 | .replace(/\.\d+Z$/, '+00:00') 14 | } 15 | /** 16 | * Adds an expiration date and signs the params object if a secret is passed to 17 | * it. If no secret is given, it returns the same object. 18 | * 19 | * @param {string | undefined} secret 20 | * @param {object} params 21 | * @returns {{ params: string, signature?: string }} 22 | */ 23 | export default async function generateSignatureIfSecret (secret, params) { 24 | let signature 25 | if (secret) { 26 | // eslint-disable-next-line no-param-reassign 27 | params.auth.expires = getExpiration(5 * 60 * 1000) 28 | // eslint-disable-next-line no-param-reassign 29 | params = JSON.stringify(params) 30 | signature = await sign(secret, params) 31 | } 32 | 33 | return { params, signature } 34 | } 35 | -------------------------------------------------------------------------------- /e2e/clients/dashboard-transloadit/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | dashboard-transloadit 6 | 7 | 8 | 9 |
    10 | 11 | 12 | -------------------------------------------------------------------------------- /e2e/clients/dashboard-tus/app.js: -------------------------------------------------------------------------------- 1 | import { ImgProcessor } from '@ImgProcessor/core' 2 | import Dashboard from '@ImgProcessor/dashboard' 3 | import Tus from '@ImgProcessor/tus' 4 | import Unsplash from '@ImgProcessor/unsplash' 5 | import Url from '@ImgProcessor/url' 6 | 7 | import '@ImgProcessor/core/dist/style.css' 8 | import '@ImgProcessor/dashboard/dist/style.css' 9 | 10 | function onShouldRetry (err, retryAttempt, options, next) { 11 | if (err?.originalResponse?.getStatus() === 418) { 12 | return true 13 | } 14 | return next(err) 15 | } 16 | 17 | const companionUrl = 'http://localhost:3020' 18 | const ImgProcessor = new ImgProcessor() 19 | .use(Dashboard, { target: '#app', inline: true }) 20 | .use(Tus, { endpoint: 'https://tusd.tusdemo.net/files', onShouldRetry }) 21 | .use(Url, { target: Dashboard, companionUrl }) 22 | .use(Unsplash, { target: Dashboard, companionUrl }) 23 | 24 | // Keep this here to access ImgProcessor in tests 25 | window.ImgProcessor = ImgProcessor 26 | -------------------------------------------------------------------------------- /e2e/clients/dashboard-tus/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | dashboard-tus 6 | 7 | 8 | 9 |
    10 | 11 | 12 | -------------------------------------------------------------------------------- /e2e/clients/dashboard-ui/app.js: -------------------------------------------------------------------------------- 1 | import ImgProcessor from '@ImgProcessor/core' 2 | import Dashboard from '@ImgProcessor/dashboard' 3 | import RemoteSources from '@ImgProcessor/remote-sources' 4 | import Webcam from '@ImgProcessor/webcam' 5 | import ScreenCapture from '@ImgProcessor/screen-capture' 6 | import GoldenRetriever from '@ImgProcessor/golden-retriever' 7 | import ImageEditor from '@ImgProcessor/image-editor' 8 | import DropTarget from '@ImgProcessor/drop-target' 9 | import Audio from '@ImgProcessor/audio' 10 | import Compressor from '@ImgProcessor/compressor' 11 | 12 | import '@ImgProcessor/core/dist/style.css' 13 | import '@ImgProcessor/dashboard/dist/style.css' 14 | 15 | const COMPANION_URL = 'http://companion.ImgProcessor.io' 16 | 17 | const ImgProcessor = new ImgProcessor() 18 | .use(Dashboard, { target: '#app', inline: true }) 19 | .use(RemoteSources, { companionUrl: COMPANION_URL }) 20 | .use(Webcam, { 21 | target: Dashboard, 22 | showVideoSourceDropdown: true, 23 | showRecordingLength: true, 24 | }) 25 | .use(Audio, { 26 | target: Dashboard, 27 | showRecordingLength: true, 28 | }) 29 | .use(ScreenCapture, { target: Dashboard }) 30 | .use(ImageEditor, { target: Dashboard }) 31 | .use(DropTarget, { target: document.body }) 32 | .use(Compressor) 33 | .use(GoldenRetriever, { serviceWorker: true }) 34 | 35 | // Keep this here to access ImgProcessor in tests 36 | window.ImgProcessor = ImgProcessor 37 | -------------------------------------------------------------------------------- /e2e/clients/dashboard-ui/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | dashboard-ui 6 | 7 | 8 | 9 |
    10 | 11 | 12 | -------------------------------------------------------------------------------- /e2e/clients/dashboard-vue/App.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /e2e/clients/dashboard-vue/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | dashboard-vue 6 | 7 | 8 | 9 |
    10 | 11 | 12 | -------------------------------------------------------------------------------- /e2e/clients/dashboard-vue/index.js: -------------------------------------------------------------------------------- 1 | import { createApp } from 'vue' 2 | import App from './App.vue' 3 | 4 | createApp(App).mount('#app') 5 | -------------------------------------------------------------------------------- /e2e/clients/dashboard-xhr/app.js: -------------------------------------------------------------------------------- 1 | import { ImgProcessor } from '@ImgProcessor/core' 2 | import Dashboard from '@ImgProcessor/dashboard' 3 | import XHRUpload from '@ImgProcessor/xhr-upload' 4 | import Unsplash from '@ImgProcessor/unsplash' 5 | import Url from '@ImgProcessor/url' 6 | 7 | import '@ImgProcessor/core/dist/style.css' 8 | import '@ImgProcessor/dashboard/dist/style.css' 9 | 10 | const companionUrl = 'http://localhost:3020' 11 | const ImgProcessor = new ImgProcessor() 12 | .use(Dashboard, { target: '#app', inline: true }) 13 | .use(XHRUpload, { endpoint: 'https://xhr-server.herokuapp.com/upload', limit: 6 }) 14 | .use(Url, { target: Dashboard, companionUrl }) 15 | .use(Unsplash, { target: Dashboard, companionUrl }) 16 | 17 | // Keep this here to access ImgProcessor in tests 18 | window.ImgProcessor = ImgProcessor 19 | -------------------------------------------------------------------------------- /e2e/clients/dashboard-xhr/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | dashboard-xhr 6 | 7 | 8 | 9 |
    10 | 11 | 12 | -------------------------------------------------------------------------------- /e2e/clients/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | End-to-End test suite 6 | 7 | 8 |

    Test apps

    9 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /e2e/clients/react/App.jsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable react/react-in-jsx-scope */ 2 | import ImgProcessor from '@ImgProcessor/core' 3 | /* eslint-disable-next-line no-unused-vars */ 4 | import React, { useState } from 'react' 5 | import { Dashboard, DashboardModal, DragDrop } from '@ImgProcessor/react' 6 | import ThumbnailGenerator from '@ImgProcessor/thumbnail-generator' 7 | import RemoteSources from '@ImgProcessor/remote-sources' 8 | 9 | import '@ImgProcessor/core/dist/style.css' 10 | import '@ImgProcessor/dashboard/dist/style.css' 11 | import '@ImgProcessor/drag-drop/dist/style.css' 12 | 13 | export default function App () { 14 | const RemoteSourcesOptions = { 15 | companionUrl: 'http://companion.ImgProcessor.io', 16 | sources: ['GoogleDrive', 'OneDrive', 'Unsplash', 'Zoom', 'Url'], 17 | } 18 | const ImgProcessorDashboard = new ImgProcessor({ id: 'dashboard' }).use(RemoteSources, { ...RemoteSourcesOptions }) 19 | const ImgProcessorModal = new ImgProcessor({ id: 'modal' }) 20 | const ImgProcessorDragDrop = new ImgProcessor({ id: 'drag-drop' }).use(ThumbnailGenerator) 21 | const [open, setOpen] = useState(false) 22 | 23 | // drag-drop has no visual output so we test it via the ImgProcessor instance 24 | window.ImgProcessor = ImgProcessorDragDrop 25 | 26 | return ( 27 |
    28 | 31 | 32 | 33 | 34 | 35 |
    36 | ) 37 | } 38 | -------------------------------------------------------------------------------- /e2e/clients/react/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | dashboard-react 6 | 7 | 8 | 9 |
    10 | 11 | 12 | -------------------------------------------------------------------------------- /e2e/clients/react/index.jsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable react/react-in-jsx-scope */ 2 | import { createRoot } from 'react-dom/client' 3 | import App from './App.jsx' 4 | 5 | const container = document.getElementById('app') 6 | const root = createRoot(container) 7 | 8 | root.render() 9 | -------------------------------------------------------------------------------- /e2e/cypress.config.mjs: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'cypress' 2 | import installLogsPrinter from 'cypress-terminal-report/src/installLogsPrinter.js' 3 | import startMockServer from './mock-server.mjs' 4 | 5 | export default defineConfig({ 6 | defaultCommandTimeout: 16_000, 7 | requestTimeout: 16_000, 8 | 9 | e2e: { 10 | baseUrl: 'http://localhost:1234', 11 | specPattern: 'cypress/integration/*.spec.ts', 12 | 13 | setupNodeEvents (on) { 14 | // implement node event listeners here 15 | installLogsPrinter(on) 16 | 17 | startMockServer('localhost', 4678) 18 | }, 19 | }, 20 | }) 21 | -------------------------------------------------------------------------------- /e2e/cypress/fixtures/DeepFrozenStore.mjs: -------------------------------------------------------------------------------- 1 | import deepFreeze from 'deep-freeze' 2 | 3 | class DeepFrozenStore { 4 | state = {}; 5 | callbacks = []; 6 | 7 | getState = () => this.state; 8 | 9 | setState = (patch) => { 10 | const nextState = deepFreeze({ ...this.state, ...patch }); 11 | this._publish(this.state, nextState, patch); 12 | this.state = nextState; 13 | }; 14 | 15 | subscribe = (listener) => { 16 | this.callbacks.push(listener); 17 | return () => { 18 | this.callbacks = this.callbacks.filter(cb => cb !== listener); 19 | }; 20 | }; 21 | 22 | _publish = (prevState, nextState, patch) => { 23 | this.callbacks.forEach(listener => listener(prevState, nextState, patch)); 24 | }; 25 | } 26 | 27 | export default function defaultStore() { 28 | return new DeepFrozenStore(); 29 | } 30 | -------------------------------------------------------------------------------- /e2e/cypress/fixtures/images/carToCheck.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CoderDonna/ImgProcessor/65c2a84b2335af172f2bcb8edc9ea6561db555fe/e2e/cypress/fixtures/images/carToCheck.jpg -------------------------------------------------------------------------------- /e2e/cypress/fixtures/images/cat-symbolic-link: -------------------------------------------------------------------------------- 1 | ./cat.jpg 2 | -------------------------------------------------------------------------------- /e2e/cypress/fixtures/images/cat-symbolic.jpg: -------------------------------------------------------------------------------- 1 | ./cat.jpg 2 | -------------------------------------------------------------------------------- /e2e/cypress/fixtures/images/image.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CoderDonna/ImgProcessor/65c2a84b2335af172f2bcb8edc9ea6561db555fe/e2e/cypress/fixtures/images/image.jpg -------------------------------------------------------------------------------- /e2e/cypress/fixtures/images/kit.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CoderDonna/ImgProcessor/65c2a84b2335af172f2bcb8edc9ea6561db555fe/e2e/cypress/fixtures/images/kit.jpg -------------------------------------------------------------------------------- /e2e/cypress/fixtures/images/monkey.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CoderDonna/ImgProcessor/65c2a84b2335af172f2bcb8edc9ea6561db555fe/e2e/cypress/fixtures/images/monkey.png -------------------------------------------------------------------------------- /e2e/cypress/fixtures/images/papagai.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CoderDonna/ImgProcessor/65c2a84b2335af172f2bcb8edc9ea6561db555fe/e2e/cypress/fixtures/images/papagai.jpg -------------------------------------------------------------------------------- /e2e/cypress/fixtures/images/papagai.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CoderDonna/ImgProcessor/65c2a84b2335af172f2bcb8edc9ea6561db555fe/e2e/cypress/fixtures/images/papagai.png -------------------------------------------------------------------------------- /e2e/cypress/fixtures/images/traffic.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CoderDonna/ImgProcessor/65c2a84b2335af172f2bcb8edc9ea6561db555fe/e2e/cypress/fixtures/images/traffic.jpg -------------------------------------------------------------------------------- /e2e/cypress/integration/dashboard-aws.spec.ts: -------------------------------------------------------------------------------- 1 | describe('Dashboard with @ImgProcessor/aws-s3', () => { 2 | beforeEach(() => { 3 | cy.visit('/dashboard-aws') 4 | cy.get('.ImgProcessor-Dashboard-input:first').as('file-input') 5 | cy.intercept({ method: 'GET', pathname: '/s3/params' }).as('get') 6 | cy.intercept({ method: 'POST' }).as('post') 7 | }) 8 | 9 | it('should upload cat image successfully', () => { 10 | cy.get('@file-input').selectFile('cypress/fixtures/images/kit.jpg', { 11 | force: true, 12 | }) 13 | 14 | cy.get('.ImgProcessor-StatusBar-actionBtn--upload').click() 15 | cy.wait(['@post', '@get']) 16 | cy.get('.ImgProcessor-StatusBar-statusPrimary').should('contain', 'Complete') 17 | }) 18 | }) 19 | -------------------------------------------------------------------------------- /e2e/cypress/integration/dashboard-compressor.spec.ts: -------------------------------------------------------------------------------- 1 | function uglierBytes(text) { 2 | const KB = 2 ** 10 3 | const MB = KB * KB 4 | 5 | if (text.endsWith(' KB')) { 6 | return Number(text.slice(0, -3)) * KB 7 | } 8 | 9 | if (text.endsWith(' MB')) { 10 | return Number(text.slice(0, -3)) * MB 11 | } 12 | 13 | if (text.endsWith(' B')) { 14 | return Number(text.slice(0, -2)) 15 | } 16 | 17 | throw new Error( 18 | `Not what the computer thinks a human-readable size string look like: ${text}`, 19 | ) 20 | } 21 | 22 | describe('dashboard-compressor', () => { 23 | beforeEach(() => { 24 | cy.visit('/dashboard-compressor') 25 | cy.get('.ImgProcessor-Dashboard-input:first').as('file-input') 26 | }) 27 | 28 | it('should compress images', () => { 29 | const sizeBeforeCompression = [] 30 | 31 | cy.get('@file-input').selectFile( 32 | [ 33 | 'cypress/fixtures/images/kit.jpg', 34 | 'cypress/fixtures/images/traffic.jpg', 35 | ], 36 | { force: true }, 37 | ) 38 | 39 | cy.get('.ImgProcessor-Dashboard-Item-statusSize').each((element) => { 40 | const text = element.text() 41 | sizeBeforeCompression.push(uglierBytes(text)) 42 | }) 43 | 44 | cy.window().then(({ ImgProcessor }) => { 45 | ImgProcessor.on('preprocess-complete', (file) => { 46 | expect(file.extension).to.equal('webp') 47 | expect(file.type).to.equal('image/webp') 48 | 49 | cy.get('.ImgProcessor-Dashboard-Item-statusSize').should((elements) => { 50 | expect(elements).to.have.length(sizeBeforeCompression.length) 51 | 52 | for (let i = 0; i < elements.length; i++) { 53 | expect(sizeBeforeCompression[i]).to.be.greaterThan( 54 | uglierBytes(elements[i].textContent), 55 | ) 56 | } 57 | }) 58 | }) 59 | 60 | cy.get('.ImgProcessor-StatusBar-actionBtn--upload').click() 61 | }) 62 | }) 63 | }) 64 | -------------------------------------------------------------------------------- /e2e/cypress/integration/dashboard-tus.spec.ts: -------------------------------------------------------------------------------- 1 | import { 2 | runRemoteUrlImageUploadTest, 3 | runRemoteUnsplashUploadTest, 4 | } from './reusable-tests' 5 | 6 | // NOTE: we have to use different files to upload per test 7 | // because we are uploading to https://tusd.tusdemo.net, 8 | // constantly uploading the same images gives a different cached result (or something). 9 | describe('Dashboard with Tus', () => { 10 | beforeEach(() => { 11 | cy.visit('/dashboard-tus') 12 | cy.get('.ImgProcessor-Dashboard-input:first').as('file-input') 13 | cy.intercept('/files/*').as('tus') 14 | cy.intercept({ method: 'POST', pathname: '/files' }).as('post') 15 | cy.intercept({ method: 'PATCH', pathname: '/files/*' }).as('patch') 16 | }) 17 | 18 | it('should upload cat image successfully', () => { 19 | cy.get('@file-input').selectFile('cypress/fixtures/images/kit.jpg', { 20 | force: true, 21 | }) 22 | 23 | cy.get('.ImgProcessor-StatusBar-actionBtn--upload').click() 24 | cy.wait(['@post', '@patch']).then(() => { 25 | cy.get('.ImgProcessor-StatusBar-statusPrimary').should('contain', 'Complete') 26 | }) 27 | }) 28 | 29 | it('should start exponential backoff when receiving HTTP 429', () => { 30 | cy.get('@file-input').selectFile('cypress/fixtures/images/monkey.png', { 31 | force: true, 32 | }) 33 | 34 | cy.intercept( 35 | { method: 'PATCH', pathname: '/files/*', times: 2 }, 36 | { statusCode: 429, body: {} }, 37 | ).as('patch') 38 | 39 | cy.get('.ImgProcessor-StatusBar-actionBtn--upload').click() 40 | cy.wait('@tus').then(() => { 41 | cy.get('.ImgProcessor-StatusBar-statusPrimary').should('contain', 'Complete') 42 | }) 43 | }) 44 | 45 | it('should upload remote image with URL plugin', () => { 46 | runRemoteUrlImageUploadTest() 47 | }) 48 | 49 | it('should upload remote image with Unsplash plugin', () => { 50 | runRemoteUnsplashUploadTest() 51 | }) 52 | }) 53 | -------------------------------------------------------------------------------- /e2e/cypress/integration/dashboard-ui.spec.ts: -------------------------------------------------------------------------------- 1 | describe('dashboard-ui', () => { 2 | beforeEach(() => { 3 | cy.visit('/dashboard-ui') 4 | cy.get('.ImgProcessor-Dashboard-input:first').as('file-input') 5 | cy.get('.ImgProcessor-Dashboard-AddFiles').as('drop-target') 6 | }) 7 | 8 | it('should not throw when calling ImgProcessor.close()', () => { 9 | cy.get('@file-input').selectFile( 10 | [ 11 | 'cypress/fixtures/images/kit.jpg', 12 | 'cypress/fixtures/images/traffic.jpg', 13 | ], 14 | { force: true }, 15 | ) 16 | 17 | cy.window().then(({ ImgProcessor }) => { 18 | expect(ImgProcessor.close()).to.not.throw 19 | }) 20 | }) 21 | 22 | it('should render thumbnails', () => { 23 | cy.get('@file-input').selectFile( 24 | [ 25 | 'cypress/fixtures/images/kit.jpg', 26 | 'cypress/fixtures/images/traffic.jpg', 27 | ], 28 | { force: true }, 29 | ) 30 | cy.get('.ImgProcessor-Dashboard-Item-previewImg') 31 | .should('have.length', 2) 32 | .each((element) => expect(element).attr('src').to.include('blob:')) 33 | }) 34 | 35 | it('should support drag&drop', () => { 36 | cy.get('@drop-target').selectFile( 37 | [ 38 | 'cypress/fixtures/images/kit.jpg', 39 | 'cypress/fixtures/images/cat-symbolic-link', 40 | 'cypress/fixtures/images/cat-symbolic.jpg', 41 | 'cypress/fixtures/images/traffic.jpg', 42 | ], 43 | { action: 'drag-drop' }, 44 | ) 45 | 46 | cy.get('.ImgProcessor-Dashboard-Item').should('have.length', 4) 47 | cy.get('.ImgProcessor-Dashboard-Item-previewImg') 48 | .should('have.length', 3) 49 | .each((element) => expect(element).attr('src').to.include('blob:')) 50 | cy.window().then(({ ImgProcessor }) => { 51 | expect( 52 | JSON.stringify(ImgProcessor.getFiles().map((file) => file.meta.relativePath)), 53 | ).to.be.equal('[null,null,null,null]') 54 | }) 55 | }) 56 | }) 57 | -------------------------------------------------------------------------------- /e2e/cypress/integration/dashboard-vue.spec.ts: -------------------------------------------------------------------------------- 1 | describe('dashboard-vue', () => { 2 | beforeEach(() => { 3 | cy.visit('/dashboard-vue') 4 | }) 5 | 6 | // Only Vue 3 works in Parcel if you use SFC's but Vue 3 is broken in ImgProcessor: 7 | // https://github.com/transloadit/ImgProcessor/issues/2877 8 | xit('should render in Vue 3 and show thumbnails', () => { 9 | cy.get('@file-input').selectFile( 10 | [ 11 | 'cypress/fixtures/images/kit.jpg', 12 | 'cypress/fixtures/images/traffic.jpg', 13 | ], 14 | { force: true }, 15 | ) 16 | cy.get('.ImgProcessor-Dashboard-Item-previewImg') 17 | .should('have.length', 2) 18 | .each((element) => expect(element).attr('src').to.include('blob:')) 19 | }) 20 | }) 21 | -------------------------------------------------------------------------------- /e2e/cypress/integration/dashboard-xhr.spec.ts: -------------------------------------------------------------------------------- 1 | import { 2 | interceptCompanionUrlMetaRequest, 3 | runRemoteUrlImageUploadTest, 4 | runRemoteUnsplashUploadTest, 5 | } from './reusable-tests' 6 | 7 | describe('Dashboard with XHR', () => { 8 | beforeEach(() => { 9 | cy.visit('/dashboard-xhr') 10 | }) 11 | 12 | it('should upload remote image with URL plugin', () => { 13 | runRemoteUrlImageUploadTest() 14 | }) 15 | 16 | it('should return correct file name with URL plugin from remote image with Content-Disposition', () => { 17 | const fileName = `DALL·E IMG_9078 - 学中文 🤑` 18 | cy.get('[data-cy="Url"]').click() 19 | cy.get('.ImgProcessor-Url-input').type( 20 | 'http://localhost:4678/file-with-content-disposition', 21 | ) 22 | interceptCompanionUrlMetaRequest() 23 | cy.get('.ImgProcessor-Url-importButton').click() 24 | cy.wait('@url-meta').then(() => { 25 | cy.get('.ImgProcessor-Dashboard-Item-name').should('contain', fileName) 26 | cy.get('.ImgProcessor-Dashboard-Item-status').should('contain', '84 KB') 27 | }) 28 | }) 29 | 30 | it('should return correct file name with URL plugin from remote image without Content-Disposition', () => { 31 | cy.get('[data-cy="Url"]').click() 32 | cy.get('.ImgProcessor-Url-input').type('http://localhost:4678/file-no-headers') 33 | interceptCompanionUrlMetaRequest() 34 | cy.get('.ImgProcessor-Url-importButton').click() 35 | cy.wait('@url-meta').then(() => { 36 | cy.get('.ImgProcessor-Dashboard-Item-name').should('contain', 'file-no') 37 | cy.get('.ImgProcessor-Dashboard-Item-status').should('contain', '0') 38 | }) 39 | }) 40 | 41 | it('should return correct file name even when Companion doesnt supply it', () => { 42 | cy.intercept('POST', 'http://localhost:3020/url/meta', { 43 | statusCode: 200, 44 | headers: {}, 45 | body: JSON.stringify({ size: 123, type: 'image/jpeg' }), 46 | }).as('url') 47 | 48 | cy.get('[data-cy="Url"]').click() 49 | cy.get('.ImgProcessor-Url-input').type( 50 | 'http://localhost:4678/file-with-content-disposition', 51 | ) 52 | interceptCompanionUrlMetaRequest() 53 | cy.get('.ImgProcessor-Url-importButton').click() 54 | cy.wait('@url-meta').then(() => { 55 | cy.get('.ImgProcessor-Dashboard-Item-name').should('contain', 'file-with') 56 | cy.get('.ImgProcessor-Dashboard-Item-status').should('contain', '123 B') 57 | }) 58 | }) 59 | 60 | it('should upload remote image with Unsplash plugin', () => { 61 | runRemoteUnsplashUploadTest() 62 | }) 63 | }) 64 | -------------------------------------------------------------------------------- /e2e/cypress/integration/react.spec.ts: -------------------------------------------------------------------------------- 1 | describe('@ImgProcessor/react', () => { 2 | beforeEach(() => { 3 | cy.visit('/react') 4 | cy.get('#dashboard .ImgProcessor-Dashboard-input:first').as('dashboard-input') 5 | cy.get('#modal .ImgProcessor-Dashboard-input:first').as('modal-input') 6 | cy.get('#drag-drop .ImgProcessor-DragDrop-input').as('dragdrop-input') 7 | }) 8 | 9 | it('should render Dashboard in React and show thumbnails', () => { 10 | cy.get('@dashboard-input').selectFile( 11 | [ 12 | 'cypress/fixtures/images/kit.jpg', 13 | 'cypress/fixtures/images/traffic.jpg', 14 | ], 15 | { force: true }, 16 | ) 17 | cy.get('#dashboard .ImgProcessor-Dashboard-Item-previewImg') 18 | .should('have.length', 2) 19 | .each((element) => expect(element).attr('src').to.include('blob:')) 20 | }) 21 | 22 | it('should render Dashboard with Remote Sources plugin pack', () => { 23 | const sources = [ 24 | 'My Device', 25 | 'Google Drive', 26 | 'OneDrive', 27 | 'Unsplash', 28 | 'Zoom', 29 | 'Link', 30 | ] 31 | cy.get('#dashboard .ImgProcessor-DashboardTab-name').each((item, index, list) => { 32 | expect(list).to.have.length(6) 33 | // Returns the current element from the loop 34 | expect(Cypress.$(item).text()).to.eq(sources[index]) 35 | }) 36 | }) 37 | 38 | it('should render Modal in React and show thumbnails', () => { 39 | cy.get('#open').click() 40 | cy.get('@modal-input').selectFile( 41 | [ 42 | 'cypress/fixtures/images/kit.jpg', 43 | 'cypress/fixtures/images/traffic.jpg', 44 | ], 45 | { force: true }, 46 | ) 47 | cy.get('#modal .ImgProcessor-Dashboard-Item-previewImg') 48 | .should('have.length', 2) 49 | .each((element) => expect(element).attr('src').to.include('blob:')) 50 | }) 51 | 52 | it('should render Drag & Drop in React and create a thumbail with @ImgProcessor/thumbnail-generator', () => { 53 | const spy = cy.spy() 54 | 55 | // eslint-disable-next-line 56 | // @ts-ignore fix me 57 | cy.window().then(({ ImgProcessor }) => ImgProcessor.on('thumbnail:generated', spy)) 58 | cy.get('@dragdrop-input').selectFile( 59 | [ 60 | 'cypress/fixtures/images/kit.jpg', 61 | 'cypress/fixtures/images/traffic.jpg', 62 | ], 63 | { force: true }, 64 | ) 65 | // not sure how I can accurately wait for the thumbnail 66 | // eslint-disable-next-line cypress/no-unnecessary-waiting 67 | cy.wait(1000).then(() => expect(spy).to.be.called) 68 | }) 69 | }) 70 | -------------------------------------------------------------------------------- /e2e/cypress/integration/reusable-tests.ts: -------------------------------------------------------------------------------- 1 | /* global cy */ 2 | 3 | const interceptCompanionUrlRequest = () => 4 | cy 5 | .intercept({ method: 'POST', url: 'http://localhost:3020/url/get' }) 6 | .as('url') 7 | export const interceptCompanionUrlMetaRequest = () => 8 | cy 9 | .intercept({ method: 'POST', url: 'http://localhost:3020/url/meta' }) 10 | .as('url-meta') 11 | 12 | export function runRemoteUrlImageUploadTest() { 13 | cy.get('[data-cy="Url"]').click() 14 | cy.get('.ImgProcessor-Url-input').type( 15 | 'https://raw.githubusercontent.com/transloadit/ImgProcessor/main/e2e/cypress/fixtures/images/cat.jpg', 16 | ) 17 | cy.get('.ImgProcessor-Url-importButton').click() 18 | interceptCompanionUrlRequest() 19 | cy.get('.ImgProcessor-StatusBar-actionBtn--upload').click() 20 | cy.wait('@url').then(() => { 21 | cy.get('.ImgProcessor-StatusBar-statusPrimary').should('contain', 'Complete') 22 | }) 23 | } 24 | 25 | export function runRemoteUnsplashUploadTest() { 26 | cy.get('[data-cy="Unsplash"]').click() 27 | cy.get('.ImgProcessor-SearchProvider-input').type('book') 28 | cy.intercept({ 29 | method: 'GET', 30 | url: 'http://localhost:3020/search/unsplash/list?q=book', 31 | }).as('unsplash-list') 32 | cy.get('.ImgProcessor-SearchProvider-searchButton').click() 33 | cy.wait('@unsplash-list') 34 | // Test that the author link is visible 35 | cy.get('.ImgProcessor-ProviderBrowserItem') 36 | .first() 37 | .within(() => { 38 | cy.root().click() 39 | // We have hover states that show the author 40 | // but we don't have hover in e2e, so we focus after the click 41 | // to get the same effect. Also tests keyboard users this way. 42 | cy.get('input[type="checkbox"]').focus() 43 | cy.get('a').should('have.css', 'display', 'block') 44 | }) 45 | cy.get('.ImgProcessor-c-btn-primary').click() 46 | cy.intercept({ 47 | method: 'POST', 48 | url: 'http://localhost:3020/search/unsplash/get/*', 49 | }).as('unsplash-get') 50 | cy.get('.ImgProcessor-StatusBar-actionBtn--upload').click() 51 | cy.wait('@unsplash-get').then(() => { 52 | cy.get('.ImgProcessor-StatusBar-statusPrimary').should('contain', 'Complete') 53 | }) 54 | } 55 | -------------------------------------------------------------------------------- /e2e/cypress/support/commands.ts: -------------------------------------------------------------------------------- 1 | // *********************************************** 2 | // This example commands.js shows you how to 3 | // create various custom commands and overwrite 4 | // existing commands. 5 | // 6 | // For more comprehensive examples of custom 7 | // commands please read more here: 8 | // https://on.cypress.io/custom-commands 9 | // *********************************************** 10 | // 11 | // 12 | // -- This is a parent command -- 13 | // Cypress.Commands.add('login', (email, password) => { ... }) 14 | // 15 | // 16 | // -- This is a child command -- 17 | // Cypress.Commands.add('drag', { prevSubject: 'element'}, (subject, options) => { ... }) 18 | // 19 | // 20 | // -- This is a dual command -- 21 | // Cypress.Commands.add('dismiss', { prevSubject: 'optional'}, (subject, options) => { ... }) 22 | // 23 | // 24 | // -- This will overwrite an existing command -- 25 | // Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... }) 26 | // 27 | 28 | import { createFakeFile } from './createFakeFile' 29 | 30 | Cypress.Commands.add('createFakeFile', createFakeFile) 31 | -------------------------------------------------------------------------------- /e2e/cypress/support/createFakeFile.ts: -------------------------------------------------------------------------------- 1 | declare global { 2 | namespace Cypress { 3 | interface Chainable { 4 | // eslint-disable-next-line no-use-before-define 5 | createFakeFile: typeof createFakeFile 6 | } 7 | } 8 | } 9 | 10 | interface File { 11 | source: string 12 | name: string 13 | type: string 14 | data: Blob 15 | } 16 | 17 | export function createFakeFile( 18 | name?: string, 19 | type?: string, 20 | b64?: string, 21 | ): File { 22 | if (!b64) { 23 | // eslint-disable-next-line no-param-reassign 24 | b64 = 25 | 'PHN2ZyB2aWV3Qm94PSIwIDAgMTIwIDEyMCI+CiAgPGNpcmNsZSBjeD0iNjAiIGN5PSI2MCIgcj0iNTAiLz4KPC9zdmc+Cg==' 26 | } 27 | // eslint-disable-next-line no-param-reassign 28 | if (!type) type = 'image/svg+xml' 29 | 30 | // https://stackoverflow.com/questions/16245767/creating-a-blob-from-a-base64-string-in-javascript 31 | function base64toBlob(base64Data: string, contentType = '') { 32 | const sliceSize = 1024 33 | const byteCharacters = atob(base64Data) 34 | const bytesLength = byteCharacters.length 35 | const slicesCount = Math.ceil(bytesLength / sliceSize) 36 | const byteArrays = new Array(slicesCount) 37 | 38 | for (let sliceIndex = 0; sliceIndex < slicesCount; ++sliceIndex) { 39 | const begin = sliceIndex * sliceSize 40 | const end = Math.min(begin + sliceSize, bytesLength) 41 | 42 | const bytes = new Array(end - begin) 43 | for (let offset = begin, i = 0; offset < end; ++i, ++offset) { 44 | bytes[i] = byteCharacters[offset].charCodeAt(0) 45 | } 46 | byteArrays[sliceIndex] = new Uint8Array(bytes) 47 | } 48 | return new Blob(byteArrays, { type: contentType }) 49 | } 50 | 51 | const blob = base64toBlob(b64, type) 52 | 53 | return { 54 | source: 'test', 55 | name: name || 'test-file', 56 | type: blob.type, 57 | data: blob, 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /e2e/cypress/support/e2e.ts: -------------------------------------------------------------------------------- 1 | // *********************************************************** 2 | // This example support/e2e.ts is processed and 3 | // loaded automatically before your test files. 4 | // 5 | // This is a great place to put global configuration and 6 | // behavior that modifies Cypress. 7 | // 8 | // You can change the location of this file or turn off 9 | // automatically serving support files with the 10 | // 'supportFile' configuration option. 11 | // 12 | // You can read more here: 13 | // https://on.cypress.io/configuration 14 | // *********************************************************** 15 | 16 | // Import commands.js using ES2015 syntax: 17 | import './commands' 18 | 19 | // Alternatively you can use CommonJS syntax: 20 | // require('./commands') 21 | 22 | // eslint-disable-next-line 23 | // @ts-ignore 24 | import installLogsCollector from 'cypress-terminal-report/src/installLogsCollector.js' 25 | 26 | installLogsCollector() 27 | -------------------------------------------------------------------------------- /e2e/cypress/support/index.ts: -------------------------------------------------------------------------------- 1 | // *********************************************************** 2 | // This example support/index.js is processed and 3 | // loaded automatically before your test files. 4 | // 5 | // This is a great place to put global configuration and 6 | // behavior that modifies Cypress. 7 | // 8 | // You can change the location of this file or turn off 9 | // automatically serving support files with the 10 | // 'supportFile' configuration option. 11 | // 12 | // You can read more here: 13 | // https://on.cypress.io/configuration 14 | // *********************************************************** 15 | 16 | import './commands.ts' 17 | 18 | import type { ImgProcessor } from '@ImgProcessor/core' 19 | 20 | declare global { 21 | interface Window { 22 | ImgProcessor: ImgProcessor 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /e2e/generate-test.mjs: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import prompts from 'prompts' 3 | import fs from 'node:fs/promises' 4 | 5 | /** 6 | * Utility function that strips indentation from multi-line strings. 7 | * Inspired from https://github.com/dmnd/dedent. 8 | */ 9 | function dedent (strings, ...parts) { 10 | const nonSpacingChar = /\S/m.exec(strings[0]) 11 | if (nonSpacingChar == null) return '' 12 | 13 | const indent = nonSpacingChar.index - strings[0].lastIndexOf('\n', nonSpacingChar.index) - 1 14 | const dedentEachLine = str => str.split('\n').map((line, i) => line.slice(i && indent)).join('\n') 15 | let returnLines = dedentEachLine(strings[0].slice(nonSpacingChar.index), indent) 16 | for (let i = 1; i < strings.length; i++) { 17 | returnLines += String(parts[i - 1]) + dedentEachLine(strings[i], indent) 18 | } 19 | return returnLines 20 | } 21 | 22 | const packageNames = await fs.readdir(new URL('../packages/@ImgProcessor', import.meta.url)) 23 | const unwantedPackages = ['core', 'companion', 'redux-dev-tools', 'utils'] 24 | 25 | const { name } = await prompts({ 26 | type: 'text', 27 | name: 'name', 28 | message: 'What should the name of the test be (e.g `dashboard-tus`)?', 29 | validate: (value) => /^[a-z|-]+$/i.test(value), 30 | }) 31 | 32 | const { packages } = await prompts({ 33 | type: 'multiselect', 34 | name: 'packages', 35 | message: 'What packages do you want to test?', 36 | hint: '@ImgProcessor/core is automatically included', 37 | choices: packageNames 38 | .filter((pkg) => !unwantedPackages.includes(pkg)) 39 | .map((pkg) => ({ title: pkg, value: pkg })), 40 | }) 41 | 42 | const camelcase = (str) => str 43 | .toLowerCase() 44 | .replace(/([-][a-z])/g, (group) => group.toUpperCase().replace('-', '')) 45 | 46 | const html = dedent` 47 | 48 | 49 | 50 | 51 | ${name} 52 | 53 | 54 | 55 |
    56 | 57 | 58 | ` 59 | const testUrl = new URL(`cypress/integration/${name}.spec.ts`, import.meta.url) 60 | const test = dedent` 61 | describe('${name}', () => { 62 | beforeEach(() => { 63 | cy.visit('/${name}') 64 | }) 65 | }) 66 | ` 67 | const htmlUrl = new URL(`clients/${name}/index.html`, import.meta.url) 68 | 69 | 70 | const appUrl = new URL(`clients/${name}/app.js`, import.meta.url) 71 | const app = dedent` 72 | import ImgProcessor from '@ImgProcessor/core' 73 | ${packages.map((pgk) => `import ${camelcase(pgk)} from '@ImgProcessor/${pgk}'`).join('\n')} 74 | 75 | const ImgProcessor = new ImgProcessor() 76 | ${packages.map((pkg) => `.use(${camelcase(pkg)})`).join('\n\t')} 77 | 78 | // Keep this here to access ImgProcessor in tests 79 | window.ImgProcessor = ImgProcessor 80 | ` 81 | 82 | await fs.writeFile(testUrl, test) 83 | await fs.mkdir(new URL(`clients/${name}`, import.meta.url)) 84 | await fs.writeFile(htmlUrl, html) 85 | await fs.writeFile(appUrl, app) 86 | 87 | const homeUrl = new URL('clients/index.html', import.meta.url) 88 | const home = await fs.readFile(homeUrl, 'utf8') 89 | const newHome = home.replace( 90 | '', 91 | `
  1. ${name}
  2. \n `, 92 | ) 93 | await fs.writeFile(homeUrl, newHome) 94 | 95 | const prettyPath = (url) => url.toString().split('ImgProcessor', 2)[1] 96 | 97 | console.log(`Generated ${prettyPath(testUrl)}`) 98 | console.log(`Generated ${prettyPath(htmlUrl)}`) 99 | console.log(`Generated ${prettyPath(appUrl)}`) 100 | console.log(`Updated ${prettyPath(homeUrl)}`) 101 | -------------------------------------------------------------------------------- /e2e/mock-server.mjs: -------------------------------------------------------------------------------- 1 | import http from 'node:http' 2 | 3 | const requestListener = (req, res) => { 4 | const endpoint = req.url 5 | 6 | switch (endpoint) { 7 | case '/file-with-content-disposition': { 8 | const fileName = `DALL·E IMG_9078 - 学中文 🤑` 9 | res.setHeader('Content-Disposition', `attachment; filename="ASCII-name.zip"; filename*=UTF-8''${encodeURIComponent(fileName)}`) 10 | res.setHeader('Content-Type', 'image/jpeg') 11 | res.setHeader('Content-Length', '86500') 12 | break 13 | } 14 | case '/file-no-headers': 15 | break 16 | default: 17 | res.writeHead(404).end('Unhandled request') 18 | } 19 | 20 | res.end() 21 | } 22 | 23 | export default function startMockServer (host, port) { 24 | const server = http.createServer(requestListener) 25 | server.listen(port, host, () => { 26 | console.log(`Server is running on http://${host}:${port}`) 27 | }) 28 | } 29 | 30 | // startMockServer('localhost', 4678) 31 | -------------------------------------------------------------------------------- /e2e/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "e2e", 3 | "private": true, 4 | "author": "Merlijn Vos ", 5 | "description": "E2E test suite for ImgProcessor", 6 | "scripts": { 7 | "client:start": "parcel --no-autoinstall clients/index.html", 8 | "cypress:open": "cypress open", 9 | "cypress:headless": "cypress run", 10 | "generate-test": "yarn node generate-test.mjs" 11 | }, 12 | "dependencies": { 13 | "@ImgProcessor/audio": "workspace:^", 14 | "@ImgProcessor/aws-s3": "workspace:^", 15 | "@ImgProcessor/aws-s3-multipart": "workspace:^", 16 | "@ImgProcessor/box": "workspace:^", 17 | "@ImgProcessor/companion-client": "workspace:^", 18 | "@ImgProcessor/core": "workspace:^", 19 | "@ImgProcessor/dashboard": "workspace:^", 20 | "@ImgProcessor/drag-drop": "workspace:^", 21 | "@ImgProcessor/drop-target": "workspace:^", 22 | "@ImgProcessor/dropbox": "workspace:^", 23 | "@ImgProcessor/golden-retriever": "workspace:^", 24 | "@ImgProcessor/google-drive": "workspace:^", 25 | "@ImgProcessor/facebook": "workspace:^", 26 | "@ImgProcessor/file-input": "workspace:^", 27 | "@ImgProcessor/form": "workspace:^", 28 | "@ImgProcessor/image-editor": "workspace:^", 29 | "@ImgProcessor/informer": "workspace:^", 30 | "@ImgProcessor/instagram": "workspace:^", 31 | "@ImgProcessor/onedrive": "workspace:^", 32 | "@ImgProcessor/progress-bar": "workspace:^", 33 | "@ImgProcessor/provider-views": "workspace:^", 34 | "@ImgProcessor/screen-capture": "workspace:^", 35 | "@ImgProcessor/status-bar": "workspace:^", 36 | "@ImgProcessor/store-default": "workspace:^", 37 | "@ImgProcessor/store-redux": "workspace:^", 38 | "@ImgProcessor/thumbnail-generator": "workspace:^", 39 | "@ImgProcessor/transloadit": "workspace:^", 40 | "@ImgProcessor/tus": "workspace:^", 41 | "@ImgProcessor/unsplash": "workspace:^", 42 | "@ImgProcessor/url": "workspace:^", 43 | "@ImgProcessor/webcam": "workspace:^", 44 | "@ImgProcessor/xhr-upload": "workspace:^", 45 | "@ImgProcessor/zoom": "workspace:^" 46 | }, 47 | "devDependencies": { 48 | "@parcel/transformer-vue": "^2.9.3", 49 | "cypress": "^13.0.0", 50 | "cypress-terminal-report": "^5.0.0", 51 | "deep-freeze": "^0.0.1", 52 | "parcel": "^2.9.3", 53 | "process": "^0.11.10", 54 | "prompts": "^2.4.2", 55 | "react": "^18.1.0", 56 | "react-dom": "^18.1.0", 57 | "typescript": "~5.1", 58 | "vue": "^3.2.33" 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /e2e/start-companion-with-load-balancer.mjs: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | import { spawn } from 'node:child_process' 4 | import http from 'node:http' 5 | import httpProxy from 'http-proxy' 6 | import process from 'node:process' 7 | 8 | const numInstances = 3 9 | const lbPort = 3020 10 | const companionStartPort = 3021 11 | 12 | function createLoadBalancer (baseUrls) { 13 | const proxy = httpProxy.createProxyServer({ ws: true }) 14 | 15 | let i = 0 16 | 17 | function getTarget () { 18 | return baseUrls[i % baseUrls.length] 19 | } 20 | 21 | const server = http.createServer((req, res) => { 22 | const target = getTarget() 23 | proxy.web(req, res, { target }, (err) => { 24 | console.error('Load balancer failed to proxy request', err.message) 25 | res.statusCode = 500 26 | res.end() 27 | }) 28 | i++ 29 | }) 30 | 31 | server.on('upgrade', (req, socket, head) => { 32 | const target = getTarget() 33 | proxy.ws(req, socket, head, { target }, (err) => { 34 | console.error('Load balancer failed to proxy websocket', err.message) 35 | console.error(err) 36 | socket.destroy() 37 | }) 38 | i++ 39 | }) 40 | 41 | server.listen(lbPort) 42 | console.log('Load balancer listening', lbPort) 43 | return server 44 | } 45 | 46 | const isWindows = process.platform === 'win32' 47 | const isOSX = process.platform === 'darwin' 48 | 49 | const startCompanion = ({ name, port }) => { 50 | const cp = spawn(process.execPath, [ 51 | '-r', 'dotenv/config', 52 | ...(isWindows || isOSX ? ['--watch-path', 'packages/@ImgProcessor/companion/src', '--watch'] : []), 53 | './packages/@ImgProcessor/companion/src/standalone/start-server.js', 54 | ], { 55 | cwd: new URL('../', import.meta.url), 56 | stdio: 'inherit', 57 | env: { 58 | ...process.env, 59 | COMPANION_PORT: port, 60 | COMPANION_SECRET: 'development', 61 | COMPANION_PREAUTH_SECRET: 'development', 62 | COMPANION_ALLOW_LOCAL_URLS: 'true', 63 | COMPANION_LOGGER_PROCESS_NAME: name, 64 | }, 65 | }) 66 | return Object.defineProperty(cp, 'then', { 67 | __proto__: null, 68 | writable: true, 69 | configurable: true, 70 | value: Promise.prototype.then.bind(new Promise((resolve, reject) => { 71 | cp.on('exit', (code) => { 72 | if (code === 0) resolve(cp) 73 | else reject(new Error(`Non-zero exit code: ${code}`)) 74 | }) 75 | cp.on('error', reject) 76 | })), 77 | }) 78 | } 79 | 80 | const hosts = Array.from({ length: numInstances }, (_, index) => { 81 | const port = companionStartPort + index; 82 | return { index, port } 83 | }) 84 | 85 | console.log('Starting companion instances on ports', hosts.map(({ port }) => port)) 86 | 87 | const companions = hosts.map(({ index, port }) => startCompanion({ name: `companion${index}`, port })) 88 | 89 | let loadBalancer 90 | try { 91 | loadBalancer = createLoadBalancer(hosts.map(({ port }) => `http://localhost:${port}`)) 92 | await Promise.all(companions) 93 | } finally { 94 | loadBalancer?.close() 95 | companions.forEach((companion) => companion.kill()) 96 | } 97 | -------------------------------------------------------------------------------- /e2e/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "moduleResolution": "NodeNext", 4 | "noEmit": true, 5 | "target": "es2020", 6 | "lib": ["es2020", "dom"], 7 | "types": ["cypress"] 8 | }, 9 | "include": ["cypress/**/*.ts"] 10 | } 11 | -------------------------------------------------------------------------------- /examples/aws-nodejs/README.md: -------------------------------------------------------------------------------- 1 | # ImgProcessor + AWS S3 with Node.JS 2 | 3 | A simple and fully working example of ImgProcessor and AWS S3 storage with Node.js (and 4 | Express.js). It uses presigned URL at the backend level. 5 | 6 | ## AWS Configuration 7 | 8 | It's assumed that you are familiar with AWS, at least, with the storage service 9 | (S3) and users & policies (IAM). 10 | 11 | These instructions are **not fit for production** but tightening the security is 12 | out of the scope here. 13 | 14 | ### S3 Setup 15 | 16 | - Create new S3 bucket in AWS (e.g. `aws-nodejs`). 17 | - Add a bucket policy. 18 | 19 | ```json 20 | { 21 | "Version": "2012-10-17", 22 | "Statement": [ 23 | { 24 | "Sid": "PublicAccess", 25 | "Effect": "Allow", 26 | "Principal": "*", 27 | "Action": "s3:GetObject", 28 | "Resource": "arn:aws:s3:::aws-nodejs/*" 29 | } 30 | ] 31 | } 32 | ``` 33 | 34 | - Make the S3 bucket public. 35 | - Add CORS configuration. 36 | 37 | ```json 38 | [ 39 | { 40 | "AllowedHeaders": ["*"], 41 | "AllowedMethods": ["GET", "PUT", "HEAD", "POST", "DELETE"], 42 | "AllowedOrigins": ["*"], 43 | "ExposeHeaders": [] 44 | } 45 | ] 46 | ``` 47 | 48 | ### AWS Credentials 49 | 50 | You may use existing AWS credentials or create a new user in the IAM page. 51 | 52 | - Make sure you setup the AWS credentials properly and write down the Access Key 53 | ID and Secret Access Key. 54 | - You may configure AWS S3 credentials using 55 | [environment variables](https://docs.aws.amazon.com/sdk-for-javascript/v3/developer-guide/loading-node-credentials-environment.html) 56 | or a 57 | [credentials file in `~/.aws/credentials`](https://docs.aws.amazon.com/sdk-for-javascript/v3/developer-guide/setting-credentials-node.html). 58 | - You will need at least `PutObject` and `PutObjectAcl` permissions. 59 | 60 | ```json 61 | { 62 | "Version": "2012-10-17", 63 | "Statement": [ 64 | { 65 | "Sid": "VisualEditor0", 66 | "Effect": "Allow", 67 | "Action": ["s3:PutObject", "s3:PutObjectAcl"], 68 | "Resource": "arn:aws:s3:::aws-nodejs/*" 69 | } 70 | ] 71 | } 72 | ``` 73 | 74 | ## Prerequisites 75 | 76 | Download this code or clone repository into a folder and install dependencies: 77 | 78 | ```sh 79 | CYPRESS_INSTALL_BINARY=0 corepack yarn install 80 | ``` 81 | 82 | Add a `.env` file to the root directory and define the S3 bucket name and port 83 | variables like the example below: 84 | 85 | ``` 86 | COMPANION_AWS_BUCKET=aws-nodejs 87 | COMPANION_AWS_REGION=… 88 | COMPANION_AWS_KEY=… 89 | COMPANION_AWS_SECRET=… 90 | PORT=8080 91 | ``` 92 | 93 | N.B.: This example uses `COMPANION_AWS_` environnement variables to facilitate 94 | integrations with other examples in this repository, but this example does _not_ 95 | uses Companion at all. 96 | 97 | ## Enjoy it 98 | 99 | Start the application: 100 | 101 | ```sh 102 | corepack yarn workspace @ImgProcessor-example/aws-nodejs start 103 | ``` 104 | 105 | Dashboard demo should now be available at http://localhost:8080. 106 | 107 | You have also a Drag & Drop demo on http://localhost:8080/drag. 108 | 109 | _Feel free to check how the demo works and feel free to open an issue._ 110 | -------------------------------------------------------------------------------- /examples/aws-nodejs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@ImgProcessor-example/aws-nodejs", 3 | "version": "1.0.0", 4 | "description": "ImgProcessor for AWS S3 with a custom Node.js backend for signing URLs", 5 | "main": "index.js", 6 | "scripts": { 7 | "dev": "node --watch index.js", 8 | "start": "node index.js" 9 | }, 10 | "private": true, 11 | "license": "MIT", 12 | "dependencies": { 13 | "@aws-sdk/client-s3": "^3.338.0", 14 | "@aws-sdk/client-sts": "^3.338.0", 15 | "@aws-sdk/s3-request-presigner": "^3.338.0", 16 | "body-parser": "^1.20.0", 17 | "dotenv": "^16.0.0", 18 | "express": "^4.18.1" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /examples/aws-nodejs/public/drag.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | ImgProcessor 6 | 10 | 11 | 12 |
    13 |
    14 |
    15 |
    16 |
    Uploaded files:
    17 |
      18 |
      19 | 102 |
      103 | 104 | 105 | -------------------------------------------------------------------------------- /examples/aws-php/.gitignore: -------------------------------------------------------------------------------- 1 | vendor 2 | -------------------------------------------------------------------------------- /examples/aws-php/composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "transloadit/ImgProcessor-aws-demo", 3 | "type": "project", 4 | "require": { 5 | "aws/aws-sdk-php": "^3.31" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /examples/aws-php/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | ImgProcessor AWS Presigned URL Example 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /examples/aws-php/main.js: -------------------------------------------------------------------------------- 1 | import ImgProcessor from '@ImgProcessor/core' 2 | import Dashboard from '@ImgProcessor/dashboard' 3 | import AwsS3 from '@ImgProcessor/aws-s3' 4 | 5 | const ImgProcessor = new ImgProcessor({ 6 | debug: true, 7 | }) 8 | 9 | ImgProcessor.use(Dashboard, { 10 | inline: true, 11 | target: 'body', 12 | }) 13 | ImgProcessor.use(AwsS3, { 14 | shouldUseMultipart: false, // The PHP backend only supports non-multipart uploads 15 | 16 | getUploadParameters (file) { 17 | // Send a request to our PHP signing endpoint. 18 | return fetch('/s3-sign.php', { 19 | method: 'post', 20 | // Send and receive JSON. 21 | headers: { 22 | accept: 'application/json', 23 | 'content-type': 'application/json', 24 | }, 25 | body: JSON.stringify({ 26 | filename: file.name, 27 | contentType: file.type, 28 | }), 29 | }).then((response) => { 30 | // Parse the JSON response. 31 | return response.json() 32 | }).then((data) => { 33 | // Return an object in the correct shape. 34 | return { 35 | method: data.method, 36 | url: data.url, 37 | fields: data.fields, 38 | headers: data.headers, 39 | } 40 | }) 41 | }, 42 | }) 43 | -------------------------------------------------------------------------------- /examples/aws-php/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@ImgProcessor-example/aws-php", 3 | "version": "0.0.0", 4 | "dependencies": { 5 | "@ImgProcessor/aws-s3": "workspace:*", 6 | "@ImgProcessor/core": "workspace:*", 7 | "@ImgProcessor/dashboard": "workspace:*", 8 | "ImgProcessor": "workspace:*" 9 | }, 10 | "devDependencies": { 11 | "esbuild": "^0.17.1" 12 | }, 13 | "private": true, 14 | "type": "module", 15 | "scripts": { 16 | "start": "php -S localhost:8080 serve.php", 17 | "outputBundle": "esbuild --format=esm --sourcemap=inline --bundle ./main.js" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /examples/aws-php/readme.md: -------------------------------------------------------------------------------- 1 | # ImgProcessor + AWS S3 Example 2 | 3 | This example uses a server-side PHP endpoint to sign uploads to S3. 4 | 5 | ## Running It 6 | 7 | To run this example, make sure you've correctly installed the **repository root**: 8 | 9 | ```bash 10 | yarn || corepack yarn install 11 | yarn build || corepack yarn build 12 | ``` 13 | 14 | That will also install the npm dependencies for this example. 15 | 16 | This example also uses the AWS PHP SDK. 17 | To install it, [get composer](https://getcomposer.org) and run `composer update` in this folder. 18 | 19 | ```bash 20 | corepack yarn workspace @ImgProcessor-example/aws-php exec "composer update" 21 | ``` 22 | 23 | Configure AWS S3 credentials using [environment variables](https://docs.aws.amazon.com/aws-sdk-php/v3/guide/guide/credentials.html#environment-credentials) or a [credentials file in `~/.aws/credentials`](https://docs.aws.amazon.com/aws-sdk-php/v3/guide/guide/credentials.html#credential-profiles). 24 | Configure a bucket name and region in the `s3-sign.php` file. 25 | 26 | Then, again in the **repository root**, start this example by doing: 27 | 28 | ```bash 29 | corepack yarn workspace @ImgProcessor-example/aws-php start 30 | ``` 31 | 32 | The demo should now be available at http://localhost:8080. 33 | 34 | You can use a different S3-compatible service like GCS by configuring that service in `~/.aws/config` and `~/.aws/credentials`, and then providing appropriate environment variables: 35 | 36 | ```bash 37 | AWS_PROFILE="gcs" \ 38 | COMPANION_AWS_ENDPOINT="https://storage.googleapis.com" \ 39 | COMPANION_AWS_BUCKET="test-bucket-name" \ 40 | corepack yarn run example aws-php 41 | ``` 42 | -------------------------------------------------------------------------------- /examples/aws-php/s3-sign.php: -------------------------------------------------------------------------------- 1 | 'latest', 17 | 'endpoint' => $awsEndpoint, 18 | 'region' => $awsRegion, 19 | ]); 20 | 21 | // Retrieve data about the file to be uploaded from the request body. 22 | $body = json_decode(file_get_contents('php://input')); 23 | $filename = $body->filename; 24 | $contentType = $body->contentType; 25 | 26 | // Create a PutObject command. 27 | $command = $s3->getCommand('putObject', [ 28 | 'Bucket' => $bucket, 29 | 'Key' => "{$directory}/{$filename}", 30 | 'ContentType' => $contentType, 31 | 'Body' => '', 32 | ]); 33 | 34 | $request = $s3->createPresignedRequest($command, '+5 minutes'); 35 | 36 | header('content-type: application/json'); 37 | echo json_encode([ 38 | 'method' => $request->getMethod(), 39 | 'url' => (string) $request->getUri(), 40 | 'fields' => [], 41 | // Also set the content-type header on the request, to make sure that it is the same as the one we used to generate the signature. 42 | // Else, the browser picks a content-type as it sees fit. 43 | 'headers' => [ 44 | 'content-type' => $contentType, 45 | ], 46 | ]); 47 | -------------------------------------------------------------------------------- /examples/aws-php/serve.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | ImgProcessor 7 | 8 | 9 | 21 |
      22 |

      ImgProcessor

      23 | 24 |
      25 | 26 | 27 |
      28 |
      29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /examples/bundled/index.js: -------------------------------------------------------------------------------- 1 | import ImgProcessor from '@ImgProcessor/core' 2 | import Dashboard from '@ImgProcessor/dashboard' 3 | import Instagram from '@ImgProcessor/instagram' 4 | import GoogleDrive from '@ImgProcessor/google-drive' 5 | import Url from '@ImgProcessor/url' 6 | import Webcam from '@ImgProcessor/webcam' 7 | import Tus from '@ImgProcessor/tus' 8 | 9 | import '@ImgProcessor/core/dist/style.css' 10 | import '@ImgProcessor/dashboard/dist/style.css' 11 | import '@ImgProcessor/url/dist/style.css' 12 | import '@ImgProcessor/webcam/dist/style.css' 13 | 14 | const TUS_ENDPOINT = 'https://tusd.tusdemo.net/files/' 15 | 16 | const ImgProcessor = new ImgProcessor({ 17 | debug: true, 18 | meta: { 19 | username: 'John', 20 | license: 'Creative Commons', 21 | }, 22 | }) 23 | .use(Dashboard, { 24 | trigger: '#pick-files', 25 | target: '#upload-form', 26 | inline: true, 27 | metaFields: [ 28 | { id: 'license', name: 'License', placeholder: 'specify license' }, 29 | { id: 'caption', name: 'Caption', placeholder: 'add caption' }, 30 | ], 31 | showProgressDetails: true, 32 | proudlyDisplayPoweredByImgProcessor: true, 33 | note: '2 files, images and video only', 34 | restrictions: { requiredMetaFields: ['caption'] }, 35 | }) 36 | .use(GoogleDrive, { target: Dashboard, companionUrl: 'http://localhost:3020' }) 37 | .use(Instagram, { target: Dashboard, companionUrl: 'http://localhost:3020' }) 38 | .use(Url, { target: Dashboard, companionUrl: 'http://localhost:3020' }) 39 | .use(Webcam, { target: Dashboard }) 40 | .use(Tus, { endpoint: TUS_ENDPOINT }) 41 | 42 | // You can optinally enable the Golden Retriever plugin — it will 43 | // restore files after a browser crash / accidental closed window 44 | // see more at https://ImgProcessor.io/docs/golden-retriever/ 45 | // 46 | // .use(GoldenRetriever, { serviceWorker: true }) 47 | 48 | ImgProcessor.on('complete', (result) => { 49 | if (result.failed.length === 0) { 50 | console.log('Upload successful 😀') 51 | } else { 52 | console.warn('Upload failed 😞') 53 | } 54 | console.log('successful files:', result.successful) 55 | console.log('failed files:', result.failed) 56 | }) 57 | 58 | // uncomment if you enable Golden Retriever 59 | // 60 | /* eslint-disable compat/compat */ 61 | // if ('serviceWorker' in navigator) { 62 | // navigator.serviceWorker 63 | // .register('/sw.js') 64 | // .then((registration) => { 65 | // console.log('ServiceWorker registration successful with scope: ', registration.scope) 66 | // }) 67 | // .catch((error) => { 68 | // console.log('Registration failed with ' + error) 69 | // }) 70 | // } 71 | /* eslint-enable */ 72 | -------------------------------------------------------------------------------- /examples/bundled/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@ImgProcessor-example/bundled", 3 | "version": "0.0.0", 4 | "dependencies": { 5 | "@ImgProcessor/core": "workspace:*", 6 | "@ImgProcessor/dashboard": "workspace:*", 7 | "@ImgProcessor/google-drive": "workspace:*", 8 | "@ImgProcessor/instagram": "workspace:*", 9 | "@ImgProcessor/tus": "workspace:*", 10 | "@ImgProcessor/url": "workspace:*", 11 | "@ImgProcessor/webcam": "workspace:*" 12 | }, 13 | "devDependencies": { 14 | "parcel": "^2.0.0" 15 | }, 16 | "private": true, 17 | "type": "module", 18 | "scripts": { 19 | "build": "parcel build index.html", 20 | "dev": "parcel index.html" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /examples/bundled/sw.js: -------------------------------------------------------------------------------- 1 | // This service worker is needed for Golden Retriever plugin, 2 | // only include if you’ve enabled it 3 | // https://ImgProcessor.io/docs/golden-retriever/ 4 | 5 | /* globals clients */ 6 | /* eslint-disable no-restricted-globals */ 7 | 8 | const fileCache = Object.create(null) 9 | 10 | function getCache (name) { 11 | if (!fileCache[name]) { 12 | fileCache[name] = Object.create(null) 13 | } 14 | return fileCache[name] 15 | } 16 | 17 | self.addEventListener('install', (event) => { 18 | console.log('Installing ImgProcessor Service Worker...') 19 | 20 | event.waitUntil(Promise.resolve() 21 | .then(() => self.skipWaiting())) 22 | }) 23 | 24 | self.addEventListener('activate', (event) => { 25 | event.waitUntil(self.clients.claim()) 26 | }) 27 | 28 | function sendMessageToAllClients (msg) { 29 | clients.matchAll().then((clients) => { 30 | clients.forEach((client) => { 31 | client.postMessage(msg) 32 | }) 33 | }) 34 | } 35 | 36 | function addFile (store, file) { 37 | getCache(store)[file.id] = file.data 38 | console.log('Added file blob to service worker cache:', file.data) 39 | } 40 | 41 | function removeFile (store, fileID) { 42 | delete getCache(store)[fileID] 43 | console.log('Removed file blob from service worker cache:', fileID) 44 | } 45 | 46 | function getFiles (store) { 47 | sendMessageToAllClients({ 48 | type: 'ImgProcessor/ALL_FILES', 49 | store, 50 | files: getCache(store), 51 | }) 52 | } 53 | 54 | self.addEventListener('message', (event) => { 55 | switch (event.data.type) { 56 | case 'ImgProcessor/ADD_FILE': 57 | addFile(event.data.store, event.data.file) 58 | break 59 | case 'ImgProcessor/REMOVE_FILE': 60 | removeFile(event.data.store, event.data.fileID) 61 | break 62 | case 'ImgProcessor/GET_FILES': 63 | getFiles(event.data.store) 64 | break 65 | 66 | default: throw new Error('unreachable') 67 | } 68 | }) 69 | -------------------------------------------------------------------------------- /examples/custom-provider/README.md: -------------------------------------------------------------------------------- 1 | # ImgProcessor + Companion + Custom Provider Example 2 | 3 | This example uses @ImgProcessor/companion with a dummy custom provider. 4 | This serves as an illustration on how integrating custom providers would work 5 | 6 | ## Run it 7 | 8 | **Note**: this example is using `fetch`, which is only available on Node.js 18+. 9 | 10 | First, you want to set up your environment variable. You can copy the content of 11 | `.env.example` and save it in a file named `.env`. You can modify in there all 12 | the information needed for the app to work that should not be committed 13 | (Google keys, Unsplash keys, etc.). 14 | 15 | ```sh 16 | [ -f .env ] || cp .env.example .env 17 | ``` 18 | 19 | To run the example, from the root directory of this repo, run the following commands: 20 | 21 | ```sh 22 | corepack yarn install 23 | corepack yarn build 24 | corepack yarn workspace @ImgProcessor-example/custom-provider start 25 | ``` 26 | -------------------------------------------------------------------------------- /examples/custom-provider/client/MyCustomProvider.jsx: -------------------------------------------------------------------------------- 1 | /** @jsx h */ 2 | 3 | import { UIPlugin } from '@ImgProcessor/core' 4 | import { Provider } from '@ImgProcessor/companion-client' 5 | import { ProviderViews } from '@ImgProcessor/provider-views' 6 | import { h } from 'preact' 7 | 8 | const defaultOptions = {} 9 | 10 | export default class MyCustomProvider extends UIPlugin { 11 | constructor (ImgProcessor, opts) { 12 | super(ImgProcessor, opts) 13 | this.type = 'acquirer' 14 | this.id = this.opts.id || 'MyCustomProvider' 15 | Provider.initPlugin(this, opts) 16 | 17 | this.icon = () => ( 18 | 19 | 20 | 21 | ) 22 | 23 | this.provider = new Provider(ImgProcessor, { 24 | companionUrl: this.opts.companionUrl, 25 | companionHeaders: this.opts.companionHeaders, 26 | provider: 'myunsplash', 27 | pluginId: this.id, 28 | }) 29 | 30 | this.defaultLocale = { 31 | strings: { 32 | pluginNameMyUnsplash: 'MyUnsplash', 33 | }, 34 | } 35 | 36 | // merge default options with the ones set by user 37 | this.opts = { ...defaultOptions, ...opts } 38 | 39 | this.i18nInit() 40 | this.title = this.i18n('pluginNameMyUnsplash') 41 | 42 | this.files = [] 43 | } 44 | 45 | install () { 46 | this.view = new ProviderViews(this, { 47 | provider: this.provider, 48 | }) 49 | 50 | const { target } = this.opts 51 | if (target) { 52 | this.mount(target, this) 53 | } 54 | } 55 | 56 | uninstall () { 57 | this.view.tearDown() 58 | this.unmount() 59 | } 60 | 61 | onFirstRender () { 62 | return this.view.getFolder() 63 | } 64 | 65 | render (state) { 66 | return this.view.render(state) 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /examples/custom-provider/client/main.js: -------------------------------------------------------------------------------- 1 | import ImgProcessor from '@ImgProcessor/core' 2 | import GoogleDrive from '@ImgProcessor/google-drive' 3 | import Tus from '@ImgProcessor/tus' 4 | import Dashboard from '@ImgProcessor/dashboard' 5 | import MyCustomProvider from './MyCustomProvider.jsx' 6 | 7 | import '@ImgProcessor/core/dist/style.css' 8 | import '@ImgProcessor/dashboard/dist/style.css' 9 | 10 | const ImgProcessor = new ImgProcessor({ 11 | debug: true, 12 | }) 13 | 14 | ImgProcessor.use(GoogleDrive, { 15 | companionUrl: 'http://localhost:3020', 16 | }) 17 | 18 | ImgProcessor.use(MyCustomProvider, { 19 | companionUrl: 'http://localhost:3020', 20 | }) 21 | 22 | ImgProcessor.use(Dashboard, { 23 | inline: true, 24 | target: 'body', 25 | plugins: ['GoogleDrive', 'MyCustomProvider'], 26 | }) 27 | 28 | ImgProcessor.use(Tus, { endpoint: 'https://tusd.tusdemo.net/files/' }) 29 | -------------------------------------------------------------------------------- /examples/custom-provider/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | ImgProcessor Custom provider Example 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /examples/custom-provider/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@ImgProcessor-example/custom-provider", 3 | "version": "0.0.0", 4 | "type": "module", 5 | "dependencies": { 6 | "@ImgProcessor/companion-client": "workspace:*", 7 | "@ImgProcessor/core": "workspace:*", 8 | "@ImgProcessor/dashboard": "workspace:*", 9 | "@ImgProcessor/google-drive": "workspace:*", 10 | "@ImgProcessor/provider-views": "workspace:*", 11 | "@ImgProcessor/tus": "workspace:*", 12 | "preact": "^10.5.13" 13 | }, 14 | "engines": { 15 | "node": ">=18.0.0" 16 | }, 17 | "devDependencies": { 18 | "@ImgProcessor/companion": "workspace:*", 19 | "body-parser": "^1.18.2", 20 | "dotenv": "^16.0.1", 21 | "express": "^4.16.2", 22 | "express-session": "^1.15.6", 23 | "npm-run-all": "^4.1.2", 24 | "vite": "^4.0.0" 25 | }, 26 | "private": true, 27 | "scripts": { 28 | "start": "npm-run-all --parallel start:server start:client", 29 | "start:client": "vite", 30 | "start:server": "node server/index.cjs" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /examples/custom-provider/server/CustomProvider.cjs: -------------------------------------------------------------------------------- 1 | const { Readable } = require('node:stream') 2 | 3 | const BASE_URL = 'https://api.unsplash.com' 4 | 5 | function adaptData (res) { 6 | const data = { 7 | username: null, 8 | items: [], 9 | nextPagePath: null, 10 | } 11 | 12 | const items = res 13 | items.forEach((item) => { 14 | const isFolder = !!item.published_at 15 | data.items.push({ 16 | isFolder, 17 | icon: isFolder ? item.cover_photo.urls.thumb : item.urls.thumb, 18 | name: item.title || item.description, 19 | mimeType: isFolder ? null : 'image/jpeg', 20 | id: item.id, 21 | thumbnail: isFolder ? item.cover_photo.urls.thumb : item.urls.thumb, 22 | requestPath: item.id, 23 | modifiedDate: item.updated_at, 24 | size: null, 25 | }) 26 | }) 27 | 28 | return data 29 | } 30 | 31 | /** 32 | * an example of a custom provider module. It implements @ImgProcessor/companion's Provider interface 33 | */ 34 | class MyCustomProvider { 35 | static version = 2 36 | 37 | static get authProvider () { 38 | return 'myunsplash' 39 | } 40 | 41 | // eslint-disable-next-line class-methods-use-this 42 | async list ({ token, directory }) { 43 | const path = directory ? `/${directory}/photos` : '' 44 | 45 | const resp = await fetch(`${BASE_URL}/collections${path}`, { 46 | headers:{ 47 | Authorization: `Bearer ${token}`, 48 | }, 49 | }) 50 | if (!resp.ok) { 51 | throw new Error(`Errornous HTTP response (${resp.status} ${resp.statusText})`) 52 | } 53 | return adaptData(await resp.json()) 54 | } 55 | 56 | // eslint-disable-next-line class-methods-use-this 57 | async download ({ id, token }) { 58 | const resp = await fetch(`${BASE_URL}/photos/${id}`, { 59 | headers: { 60 | Authorization: `Bearer ${token}`, 61 | }, 62 | }) 63 | 64 | if (!resp.ok) { 65 | throw new Error(`Errornous HTTP response (${resp.status} ${resp.statusText})`) 66 | } 67 | return { stream: Readable.fromWeb(resp.body) } 68 | } 69 | 70 | // eslint-disable-next-line class-methods-use-this 71 | async size ({ id, token }) { 72 | const resp = await fetch(`${BASE_URL}/photos/${id}`, { 73 | headers: { 74 | Authorization: `Bearer ${token}`, 75 | }, 76 | }) 77 | 78 | if (!resp.ok) { 79 | throw new Error(`Errornous HTTP response (${resp.status} ${resp.statusText})`) 80 | } 81 | 82 | const { size } = await resp.json() 83 | return size 84 | } 85 | } 86 | 87 | module.exports = MyCustomProvider 88 | -------------------------------------------------------------------------------- /examples/custom-provider/server/index.cjs: -------------------------------------------------------------------------------- 1 | const { mkdtempSync } = require('node:fs') 2 | const os = require('node:os') 3 | const path = require('node:path') 4 | 5 | require('dotenv').config({ path: path.join(__dirname, '..', '..', '..', '.env') }) 6 | const express = require('express') 7 | // the ../../../packages is just to use the local version 8 | // instead of the npm version—in a real app use `require('@ImgProcessor/companion')` 9 | const bodyParser = require('body-parser') 10 | const session = require('express-session') 11 | const ImgProcessor = require('@ImgProcessor/companion') 12 | const MyCustomProvider = require('./CustomProvider.cjs') 13 | 14 | const app = express() 15 | 16 | app.use(bodyParser.json()) 17 | app.use(session({ 18 | secret: 'some-secret', 19 | resave: true, 20 | saveUninitialized: true, 21 | })) 22 | 23 | // Routes 24 | app.get('/', (req, res) => { 25 | res.setHeader('Content-Type', 'text/plain') 26 | res.send('Welcome to my ImgProcessor companion service') 27 | }) 28 | 29 | // source https://unsplash.com/documentation#user-authentication 30 | const AUTHORIZE_URL = 'https://unsplash.com/oauth/authorize' 31 | const ACCESS_URL = 'https://unsplash.com/oauth/token' 32 | 33 | // initialize ImgProcessor 34 | const ImgProcessorOptions = { 35 | providerOptions: { 36 | drive: { 37 | key: process.env.COMPANION_GOOGLE_KEY, 38 | secret: process.env.COMPANION_GOOGLE_SECRET, 39 | }, 40 | }, 41 | customProviders: { 42 | myunsplash: { 43 | config: { 44 | // your oauth handlers 45 | authorize_url: AUTHORIZE_URL, 46 | access_url: ACCESS_URL, 47 | oauth: 2, 48 | key: process.env.COMPANION_UNSPLASH_KEY, 49 | secret: process.env.COMPANION_UNSPLASH_SECRET, 50 | }, 51 | // you provider class/module: 52 | module: MyCustomProvider, 53 | }, 54 | }, 55 | server: { 56 | host: 'localhost:3020', 57 | protocol: 'http', 58 | }, 59 | filePath: mkdtempSync(path.join(os.tmpdir(), 'companion-')), 60 | secret: 'some-secret', 61 | debug: true, 62 | } 63 | 64 | app.use(ImgProcessor.app(ImgProcessorOptions).app) 65 | 66 | // handle 404 67 | app.use((req, res) => { 68 | return res.status(404).json({ message: 'Not Found' }) 69 | }) 70 | 71 | // handle server errors 72 | app.use((err, req, res) => { 73 | console.error('\x1b[31m', err.stack, '\x1b[0m') 74 | res.status(err.status || 500).json({ message: err.message, error: err }) 75 | }) 76 | 77 | ImgProcessor.socket(app.listen(3020), ImgProcessorOptions) 78 | 79 | console.log('Welcome to Companion!') 80 | console.log(`Listening on http://0.0.0.0:${3020}`) 81 | -------------------------------------------------------------------------------- /examples/multiple-instances/README.md: -------------------------------------------------------------------------------- 1 | # Multiple Instances 2 | 3 | This example uses ImgProcessor with the `@ImgProcessor/golden-retriever` plugin. 4 | 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. 5 | 6 | ## Run it 7 | 8 | To run this example, make sure you've correctly installed the **repository root**: 9 | 10 | ```bash 11 | corepack yarn install 12 | corepack yarn build 13 | ``` 14 | 15 | That will also install the dependencies for this example. 16 | 17 | Then, again in the **repository root**, start this example by doing: 18 | 19 | ```bash 20 | corepack yarn workspace @ImgProcessor-example/multiple-instances start 21 | ``` 22 | -------------------------------------------------------------------------------- /examples/multiple-instances/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | ImgProcessor example: Multiple instances 7 | 17 | 18 | 19 |
      20 |
      21 |

      Instance A

      22 |
      23 |
      24 |
      25 |

      Instance B

      26 |
      27 |
      28 |
      29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /examples/multiple-instances/main.js: -------------------------------------------------------------------------------- 1 | import ImgProcessor from '@ImgProcessor/core' 2 | import Dashboard from '@ImgProcessor/dashboard' 3 | import GoldenRetriever from '@ImgProcessor/golden-retriever' 4 | 5 | import '@ImgProcessor/core/dist/style.css' 6 | import '@ImgProcessor/dashboard/dist/style.css' 7 | 8 | // Initialise two ImgProcessor instances with the GoldenRetriever plugin, 9 | // but with different `id`s. 10 | const a = new ImgProcessor({ 11 | id: 'a', 12 | debug: true, 13 | }) 14 | .use(Dashboard, { 15 | target: '#a', 16 | inline: true, 17 | width: 400, 18 | }) 19 | .use(GoldenRetriever, { serviceWorker: false }) 20 | 21 | const b = new ImgProcessor({ 22 | id: 'b', 23 | debug: true, 24 | }) 25 | .use(Dashboard, { 26 | target: '#b', 27 | inline: true, 28 | width: 400, 29 | }) 30 | .use(GoldenRetriever, { serviceWorker: false }) 31 | 32 | window.a = a 33 | window.b = b 34 | -------------------------------------------------------------------------------- /examples/multiple-instances/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@ImgProcessor-example/multiple-instances", 3 | "version": "0.0.0", 4 | "type": "module", 5 | "dependencies": { 6 | "@ImgProcessor/core": "workspace:*", 7 | "@ImgProcessor/dashboard": "workspace:*", 8 | "@ImgProcessor/golden-retriever": "workspace:*" 9 | }, 10 | "devDependencies": { 11 | "vite": "^4.0.0" 12 | }, 13 | "private": true, 14 | "scripts": { 15 | "start": "vite" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /examples/node-xhr/.gitignore: -------------------------------------------------------------------------------- 1 | uploads/ 2 | -------------------------------------------------------------------------------- /examples/node-xhr/README.md: -------------------------------------------------------------------------------- 1 | # ImgProcessor + Node Example 2 | 3 | This example uses Node server and `@ImgProcessor/xhr-upload` to upload files to the local file system. 4 | 5 | ## Run it 6 | 7 | To run this example, make sure you've correctly installed the **repository root**: 8 | 9 | ```sh 10 | corepack yarn install 11 | corepack yarn build 12 | ``` 13 | 14 | That will also install the dependencies for this example. 15 | 16 | Then, again in the **repository root**, start this example by doing: 17 | 18 | ```sh 19 | corepack yarn workspace @ImgProcessor-example/node-xhr start 20 | ``` 21 | -------------------------------------------------------------------------------- /examples/node-xhr/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Node.js + ImgProcessor Example 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /examples/node-xhr/main.js: -------------------------------------------------------------------------------- 1 | import ImgProcessor from '@ImgProcessor/core' 2 | import Dashboard from '@ImgProcessor/dashboard' 3 | import XHRUpload from '@ImgProcessor/xhr-upload' 4 | import Webcam from '@ImgProcessor/webcam' 5 | 6 | import '@ImgProcessor/core/dist/style.css' 7 | import '@ImgProcessor/dashboard/dist/style.css' 8 | import '@ImgProcessor/webcam/dist/style.css' 9 | 10 | const ImgProcessor = new ImgProcessor({ 11 | debug: true, 12 | autoProceed: false, 13 | }) 14 | 15 | ImgProcessor.use(Webcam) 16 | ImgProcessor.use(Dashboard, { 17 | inline: true, 18 | target: 'body', 19 | plugins: ['Webcam'], 20 | }) 21 | ImgProcessor.use(XHRUpload, { 22 | endpoint: 'http://localhost:3020/upload', 23 | }) 24 | -------------------------------------------------------------------------------- /examples/node-xhr/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@ImgProcessor-example/node-xhr", 3 | "version": "0.0.0", 4 | "type": "module", 5 | "dependencies": { 6 | "@ImgProcessor/core": "workspace:*", 7 | "@ImgProcessor/dashboard": "workspace:*", 8 | "@ImgProcessor/webcam": "workspace:*", 9 | "@ImgProcessor/xhr-upload": "workspace:*", 10 | "formidable": "^3.2.4" 11 | }, 12 | "devDependencies": { 13 | "npm-run-all": "^4.1.3", 14 | "vite": "^4.0.0" 15 | }, 16 | "private": true, 17 | "scripts": { 18 | "start": "npm-run-all --parallel start:server start:client", 19 | "start:client": "vite", 20 | "start:server": "node server.js" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /examples/node-xhr/server.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /* eslint-disable no-console */ 4 | 5 | import http from 'node:http' 6 | import { fileURLToPath } from 'node:url' 7 | import { mkdir } from 'node:fs/promises' 8 | 9 | import formidable from 'formidable' 10 | 11 | const UPLOAD_DIR = new URL('./uploads/', import.meta.url) 12 | 13 | await mkdir(UPLOAD_DIR, { recursive: true }) 14 | 15 | http.createServer((req, res) => { 16 | const headers = { 17 | 'Content-Type': 'application/json', 18 | 'Access-Control-Allow-Origin': '*', 19 | 'Access-Control-Allow-Methods': 'OPTIONS, POST, GET', 20 | 'Access-Control-Max-Age': 2592000, // 30 days 21 | /** add other headers as per requirement */ 22 | } 23 | 24 | if (req.method === 'OPTIONS') { 25 | res.writeHead(204, headers) 26 | res.end() 27 | return 28 | } 29 | if (req.url === '/upload' && req.method.toLowerCase() === 'post') { 30 | // parse a file upload 31 | const form = formidable({ 32 | keepExtensions: true, 33 | uploadDir: fileURLToPath(UPLOAD_DIR), 34 | }) 35 | 36 | form.parse(req, (err, fields, files) => { 37 | if (err) { 38 | console.log('some error', err) 39 | res.writeHead(200, headers) 40 | res.write(JSON.stringify(err)) 41 | return res.end() 42 | } 43 | const { file:[{ filepath, originalFilename, mimetype, size }] } = files 44 | console.log('saved file', { filepath, originalFilename, mimetype, size }) 45 | res.writeHead(200, headers) 46 | res.write(JSON.stringify({ fields, files })) 47 | return res.end() 48 | }) 49 | } 50 | }).listen(3020, () => { 51 | console.log('server started') 52 | }) 53 | -------------------------------------------------------------------------------- /examples/php-xhr/.gitignore: -------------------------------------------------------------------------------- 1 | uploads/ 2 | -------------------------------------------------------------------------------- /examples/php-xhr/README.md: -------------------------------------------------------------------------------- 1 | # ImgProcessor + PHP Example 2 | 3 | This example uses PHP server and `@ImgProcessor/xhr-upload` to upload files to the local file system. 4 | 5 | ## Run it 6 | 7 | To run this example, make sure you've correctly installed the **repository root**: 8 | 9 | ```sh 10 | corepack yarn install 11 | corepack yarn build 12 | ``` 13 | 14 | That will also install the dependencies for this example. 15 | 16 | Then, again in the **repository root**, start this example by doing: 17 | 18 | ```sh 19 | corepack yarn workspace @ImgProcessor-example/php-xhr start 20 | ``` 21 | -------------------------------------------------------------------------------- /examples/php-xhr/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | PHP + ImgProcessor Example 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /examples/php-xhr/main.js: -------------------------------------------------------------------------------- 1 | import ImgProcessor from '@ImgProcessor/core' 2 | import Webcam from '@ImgProcessor/webcam' 3 | import Dashboard from '@ImgProcessor/dashboard' 4 | import XHRUpload from '@ImgProcessor/xhr-upload' 5 | 6 | import '@ImgProcessor/core/dist/style.css' 7 | import '@ImgProcessor/dashboard/dist/style.css' 8 | import '@ImgProcessor/webcam/dist/style.css' 9 | 10 | const ImgProcessor = new ImgProcessor({ 11 | debug: true, 12 | autoProceed: false, 13 | }) 14 | 15 | ImgProcessor.use(Webcam) 16 | ImgProcessor.use(Dashboard, { 17 | inline: true, 18 | target: 'body', 19 | plugins: ['Webcam'], 20 | }) 21 | ImgProcessor.use(XHRUpload, { 22 | endpoint: 'http://localhost:3020/upload.php', 23 | }) 24 | -------------------------------------------------------------------------------- /examples/php-xhr/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@ImgProcessor-example/php-xhr", 3 | "version": "0.0.0", 4 | "type": "module", 5 | "dependencies": { 6 | "@ImgProcessor/core": "workspace:*", 7 | "@ImgProcessor/dashboard": "workspace:*", 8 | "@ImgProcessor/webcam": "workspace:*", 9 | "@ImgProcessor/xhr-upload": "workspace:*" 10 | }, 11 | "devDependencies": { 12 | "npm-run-all": "^4.1.3", 13 | "vite": "^4.0.0" 14 | }, 15 | "private": true, 16 | "scripts": { 17 | "start": "npm-run-all --parallel start:server start:client", 18 | "start:client": "vite", 19 | "start:server": "mkdir -p uploads && php -S 0.0.0.0:3020 server.php" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /examples/php-xhr/server.php: -------------------------------------------------------------------------------- 1 | $max_size) { 32 | header('Access-Control-Allow-Origin: *'); 33 | header('Content-type: application/json'); 34 | $data = ['message' => 'File size exceeds the maximum allowed size of ' . $max_size . '.']; 35 | http_response_code(400); 36 | echo json_encode($data); 37 | exit; 38 | } 39 | 40 | // Sanitize file name to prevent directory traversal attacks 41 | $file_name = preg_replace('/[^a-zA-Z0-9._-]/', '', $file_name); 42 | $target_file = $target_dir . DIRECTORY_SEPARATOR . $file_name; 43 | 44 | try { 45 | if (move_uploaded_file($_FILES['file']['tmp_name'], $target_file)) { 46 | header('Access-Control-Allow-Origin: *'); 47 | header('Content-type: application/json'); 48 | $data = ['url' => $target_file, 'message' => 'The file ' . $file_name . ' has been uploaded.']; 49 | http_response_code(201); 50 | echo json_encode($data); 51 | } else { 52 | throw new Exception('Unable to move the uploaded file to its final location:' . $target_file); 53 | } 54 | 55 | } catch (\Throwable $th) { 56 | header('Access-Control-Allow-Origin: *'); 57 | header('Content-type: application/json'); 58 | $data = ['message' => 'Sorry, there was an error uploading your file.', 'error' => $th->getMessage()]; 59 | http_response_code(400); 60 | echo json_encode($data); 61 | } 62 | } else { 63 | header('Access-Control-Allow-Origin: *'); 64 | header('Content-type: application/json'); 65 | $data = ['message' => 'Please upload a file.']; 66 | http_response_code(400); 67 | echo json_encode($data); 68 | } 69 | -------------------------------------------------------------------------------- /examples/python-xhr/.gitignore: -------------------------------------------------------------------------------- 1 | uploads/ 2 | -------------------------------------------------------------------------------- /examples/python-xhr/README.md: -------------------------------------------------------------------------------- 1 | # ImgProcessor + Python Example 2 | 3 | This example uses a Python Flask server and `@ImgProcessor/xhr-upload` to upload files to the local file system. 4 | 5 | ## Run it 6 | 7 | To run this example, make sure you've correctly installed the **repository root**: 8 | 9 | ```sh 10 | corepack yarn install 11 | corepack yarn build 12 | ``` 13 | 14 | That will also install the npm dependencies for this example. 15 | 16 | Additionally, this example uses python dependencies. Move into this directory, and install them using pip: 17 | 18 | ```sh 19 | corepack yarn workspace @ImgProcessor-example/python-xhr installPythonDeps 20 | ``` 21 | 22 | Then, again in the **repository root**, start this example by doing: 23 | 24 | ```sh 25 | corepack yarn workspace @ImgProcessor-example/python-xhr start 26 | ``` 27 | -------------------------------------------------------------------------------- /examples/python-xhr/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Python + ImgProcessor Example 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /examples/python-xhr/main.js: -------------------------------------------------------------------------------- 1 | import ImgProcessor from '@ImgProcessor/core' 2 | import Webcam from '@ImgProcessor/webcam' 3 | import Dashboard from '@ImgProcessor/dashboard' 4 | import XHRUpload from '@ImgProcessor/xhr-upload' 5 | 6 | import '@ImgProcessor/core/dist/style.css' 7 | import '@ImgProcessor/webcam/dist/style.css' 8 | import '@ImgProcessor/dashboard/dist/style.css' 9 | 10 | const ImgProcessor = new ImgProcessor({ 11 | debug: true, 12 | autoProceed: false, 13 | }) 14 | 15 | ImgProcessor.use(Webcam) 16 | ImgProcessor.use(Dashboard, { 17 | inline: true, 18 | target: 'body', 19 | plugins: ['Webcam'], 20 | }) 21 | ImgProcessor.use(XHRUpload, { 22 | endpoint: 'http://localhost:3020/upload', 23 | }) 24 | -------------------------------------------------------------------------------- /examples/python-xhr/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@ImgProcessor-example/python-xhr", 3 | "version": "0.0.0", 4 | "type": "module", 5 | "dependencies": { 6 | "@ImgProcessor/core": "workspace:*", 7 | "@ImgProcessor/dashboard": "workspace:*", 8 | "@ImgProcessor/webcam": "workspace:*", 9 | "@ImgProcessor/xhr-upload": "workspace:*" 10 | }, 11 | "devDependencies": { 12 | "npm-run-all": "^4.1.3", 13 | "vite": "^4.0.0" 14 | }, 15 | "private": true, 16 | "scripts": { 17 | "installPythonDeps": "python3 -m pip install -r requirements.txt", 18 | "start": "npm-run-all --parallel start:server start:client", 19 | "start:client": "vite", 20 | "start:server": "mkdir -p uploads && python3 server.py" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /examples/python-xhr/requirements.txt: -------------------------------------------------------------------------------- 1 | flask 2 | werkzeug 3 | flask-cors 4 | -------------------------------------------------------------------------------- /examples/python-xhr/server.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import os 3 | from flask import Flask, request, jsonify 4 | from werkzeug.utils import secure_filename 5 | from flask_cors import CORS 6 | 7 | UPLOAD_FOLDER = 'uploads' 8 | ALLOWED_EXTENSIONS = {'txt', 'pdf', 'png', 'jpg', 'jpeg', 'gif'} 9 | 10 | app = Flask(__name__) 11 | app.config['UPLOAD_FOLDER'] = os.path.join(os.path.dirname(__file__), UPLOAD_FOLDER) 12 | CORS(app) 13 | 14 | def allowed_file(filename): 15 | return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS 16 | 17 | @app.route('/upload', methods=['POST']) 18 | def upload_file(): 19 | if request.method == 'POST': 20 | uploaded_files = request.files.getlist('file') 21 | if not uploaded_files: 22 | return jsonify(error="No files in the request"), 400 23 | 24 | uploaded_filenames = [] 25 | for file in uploaded_files: 26 | if file and allowed_file(file.filename): 27 | filename = secure_filename(file.filename) 28 | file.save(os.path.join(app.config['UPLOAD_FOLDER'], filename)) 29 | uploaded_filenames.append(filename) 30 | 31 | if uploaded_filenames: 32 | return jsonify(message="Files uploaded successfully", uploaded_files=uploaded_filenames), 201 33 | else: 34 | return jsonify(error="No valid files uploaded"), 400 35 | 36 | if __name__ == '__main__': 37 | app.run(port=3020) 38 | -------------------------------------------------------------------------------- /examples/react-native-expo/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | "react/react-in-jsx-scope": "off", 4 | "no-use-before-define": "off" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /examples/react-native-expo/.expo-shared/assets.json: -------------------------------------------------------------------------------- 1 | { 2 | "12bb71dsgc6255bbf50437ec8f4441c083f47cdb74bd89160c15e4f43e52sdf4": true, 3 | "40b842e832070c58233c6aa9e08fa459302ee3f9da492c7gfsd93d2fbf4a56fd": true 4 | } 5 | -------------------------------------------------------------------------------- /examples/react-native-expo/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | .expo/ 3 | dist/ 4 | npm-debug.* 5 | *.jks 6 | *.p8 7 | *.p12 8 | *.key 9 | *.mobileprovision 10 | *.orig.* 11 | web-build/ 12 | 13 | # macOS 14 | .DS_Store 15 | -------------------------------------------------------------------------------- /examples/react-native-expo/FileList.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { StyleSheet, View, FlatList, Text, Image } from 'react-native' 3 | 4 | import getFileTypeIcon from '@ImgProcessor/dashboard/lib/utils/getFileTypeIcon.js' 5 | import renderStringFromJSX from 'preact-render-to-string' 6 | 7 | const fileIcon = require('./assets/file-icon.png') 8 | 9 | const truncateString = (str) => { 10 | const maxChars = 20 11 | if (str.length > maxChars) { 12 | return `${str.substring(0, 25)}...` 13 | } 14 | 15 | return str 16 | } 17 | 18 | function FileIcon () { 19 | return ( 20 | 21 | 25 | 26 | ) 27 | } 28 | 29 | function ImgProcessorDashboardFileIcon ({ type }) { 30 | const icon = renderStringFromJSX(getFileTypeIcon(type).icon) 31 | if (!icon) { 32 | return 33 | } 34 | const { color } = getFileTypeIcon(type) 35 | return ( 36 | 42 | logo 43 | 44 | ) 45 | } 46 | 47 | export default function FileList ({ ImgProcessor }) { 48 | const ImgProcessorFiles = ImgProcessor.store.state.files 49 | const ImgProcessorFilesArray = Object.keys(ImgProcessorFiles).map((id) => ImgProcessorFiles[id]) 50 | 51 | return ( 52 | 53 | item.id} 56 | numColumns={2} 57 | renderItem={({ item }) => { 58 | return ( 59 | 60 | {item.type === 'image' ? ( 61 | 65 | ) : ( 66 | 67 | )} 68 | {truncateString(item.name, 20)} 69 | {item.type} 70 | 71 | ) 72 | }} 73 | /> 74 | 75 | ) 76 | } 77 | 78 | const styles = StyleSheet.create({ 79 | container: { 80 | marginTop: 20, 81 | marginBottom: 20, 82 | flex: 1, 83 | justifyContent: 'center', 84 | alignItems:'center', 85 | marginRight: -25, 86 | }, 87 | item: { 88 | width: 100, 89 | marginTop: 5, 90 | marginBottom: 15, 91 | marginRight: 25, 92 | }, 93 | itemImage: { 94 | width: 100, 95 | height: 100, 96 | borderRadius: 5, 97 | marginBottom: 5, 98 | }, 99 | itemIconContainer: { 100 | width: 100, 101 | height: 100, 102 | borderRadius: 5, 103 | marginBottom: 5, 104 | backgroundColor: '#cfd3d6', 105 | alignItems: 'center', 106 | justifyContent: 'center', 107 | }, 108 | itemIcon: { 109 | width: 42, 110 | height: 56, 111 | }, 112 | itemIconSVG: { 113 | width: 50, 114 | height: 50, 115 | }, 116 | itemName: { 117 | fontSize: 13, 118 | color: '#2c3e50', 119 | fontWeight: '600', 120 | }, 121 | itemType: { 122 | fontWeight: '600', 123 | fontSize: 12, 124 | color: '#95a5a6', 125 | }, 126 | }) 127 | -------------------------------------------------------------------------------- /examples/react-native-expo/PauseResumeButton.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { StyleSheet, Text, TouchableHighlight } from 'react-native' 3 | 4 | export default function PauseResumeButton ({ uploadStarted, uploadComplete, isPaused, onPress }) { 5 | if (!uploadStarted || uploadComplete) { 6 | return null 7 | } 8 | 9 | return ( 10 | 14 | 17 | {isPaused ? 'Resume' : 'Pause'} 18 | 19 | 20 | ) 21 | } 22 | 23 | const styles = StyleSheet.create({ 24 | button: { 25 | backgroundColor: '#cc0077', 26 | padding: 10, 27 | }, 28 | text: { 29 | color: '#fff', 30 | textAlign: 'center', 31 | fontSize: 17, 32 | }, 33 | }) 34 | -------------------------------------------------------------------------------- /examples/react-native-expo/ProgressBar.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { View, Text, StyleSheet } from 'react-native' 3 | 4 | const colorGreen = '#0b8600' 5 | const colorBlue = '#006bb7' 6 | 7 | export default function ProgressBar ({ progress }) { 8 | return ( 9 | 10 | 13 | 18 | 19 | {progress ? `${progress}%` : null} 20 | 21 | ) 22 | } 23 | 24 | const styles = StyleSheet.create({ 25 | root: { 26 | marginTop: 15, 27 | marginBottom: 15, 28 | }, 29 | wrapper:{ 30 | height: 5, 31 | overflow: 'hidden', 32 | backgroundColor: '#dee1e3', 33 | }, 34 | bar: { 35 | height: 5, 36 | }, 37 | }) 38 | -------------------------------------------------------------------------------- /examples/react-native-expo/SelectFilesButton.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Text, TouchableHighlight, StyleSheet } from 'react-native' 3 | 4 | export default function SelectFiles ({ showFilePicker }) { 5 | return ( 6 | 10 | Select files 11 | 12 | ) 13 | } 14 | 15 | const styles = StyleSheet.create({ 16 | button: { 17 | backgroundColor: '#cc0077', 18 | padding: 15, 19 | }, 20 | text: { 21 | color: '#fff', 22 | textAlign: 'center', 23 | fontSize: 17, 24 | }, 25 | }) 26 | -------------------------------------------------------------------------------- /examples/react-native-expo/app.json: -------------------------------------------------------------------------------- 1 | { 2 | "expo": { 3 | "name": "react-native-expo", 4 | "slug": "react-native-expo", 5 | "version": "1.0.0", 6 | "orientation": "portrait", 7 | "icon": "./assets/icon.png", 8 | "splash": { 9 | "image": "./assets/splash.png", 10 | "resizeMode": "contain", 11 | "backgroundColor": "#ffffff" 12 | }, 13 | "updates": { 14 | "fallbackToCacheTimeout": 0 15 | }, 16 | "assetBundlePatterns": ["**/*"], 17 | "ios": { 18 | "supportsTablet": true 19 | }, 20 | "android": { 21 | "adaptiveIcon": { 22 | "foregroundImage": "./assets/adaptive-icon.png", 23 | "backgroundColor": "#FFFFFF" 24 | } 25 | }, 26 | "web": { 27 | "favicon": "./assets/favicon.png" 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /examples/react-native-expo/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = (api) => { 2 | api.cache(true) 3 | return { 4 | presets: ['babel-preset-expo'], 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /examples/react-native-expo/index.js: -------------------------------------------------------------------------------- 1 | import { registerRootComponent } from 'expo' 2 | 3 | import App from './App' 4 | 5 | // registerRootComponent calls AppRegistry.registerComponent('main', () => App); 6 | // It also ensures that whether you load the app in Expo Go or in a native build, 7 | // the environment is set up appropriately 8 | registerRootComponent(App) 9 | -------------------------------------------------------------------------------- /examples/react-native-expo/metro.config.js: -------------------------------------------------------------------------------- 1 | // Learn more https://docs.expo.dev/guides/monorepos 2 | const { getDefaultConfig } = require('expo/metro-config') 3 | const path = require('node:path') 4 | 5 | // Find the project and workspace directories 6 | const projectRoot = __dirname 7 | // This can be replaced with `find-yarn-workspace-root` 8 | const workspaceRoot = path.resolve(projectRoot, '../../') 9 | 10 | const config = getDefaultConfig(projectRoot) 11 | 12 | // 1. Watch all files within the monorepo 13 | config.watchFolders = [workspaceRoot] 14 | // 2. Let Metro know where to resolve packages and in what order 15 | config.resolver.nodeModulesPaths = [ 16 | path.resolve(projectRoot, 'node_modules'), 17 | path.resolve(workspaceRoot, 'node_modules'), 18 | ] 19 | // 3. Force Metro to resolve (sub)dependencies only from the `nodeModulesPaths` 20 | config.resolver.disableHierarchicalLookup = true 21 | 22 | module.exports = config 23 | -------------------------------------------------------------------------------- /examples/react-native-expo/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@ImgProcessor-example/react-native-expo", 3 | "version": "1.0.0", 4 | "main": "index.js", 5 | "scripts": { 6 | "start": "expo start", 7 | "android": "expo start --android", 8 | "ios": "expo start --ios", 9 | "web": "expo start --web", 10 | "eject": "expo eject" 11 | }, 12 | "dependencies": { 13 | "@react-native-async-storage/async-storage": "~1.15.0", 14 | "@ImgProcessor/core": "workspace:*", 15 | "@ImgProcessor/dashboard": "workspace:*", 16 | "@ImgProcessor/instagram": "workspace:*", 17 | "@ImgProcessor/react": "workspace:*", 18 | "@ImgProcessor/react-native": "workspace:*", 19 | "@ImgProcessor/tus": "workspace:*", 20 | "@ImgProcessor/url": "workspace:*", 21 | "@ImgProcessor/xhr-upload": "workspace:*", 22 | "base64-js": "^1.3.0", 23 | "expo": "~43.0.2", 24 | "expo-file-system": "~13.0.3", 25 | "expo-status-bar": "~1.1.0", 26 | "preact-render-to-string": "^5.1.0", 27 | "react": "17.0.1", 28 | "react-dom": "17.0.1", 29 | "react-native": "0.64.3", 30 | "react-native-web": "0.17.1" 31 | }, 32 | "devDependencies": { 33 | "@babel/core": "^7.12.9" 34 | }, 35 | "private": true 36 | } 37 | -------------------------------------------------------------------------------- /examples/react-native-expo/readme.md: -------------------------------------------------------------------------------- 1 | # ImgProcessor in React Native Expo (Beta) 2 | 3 | ⚠️ In Beta 4 | 5 | `@ImgProcessor/react-native` is a basic ImgProcessor 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 ImgProcessor Companion. 6 | 7 | ## Run it 8 | 9 | To run this example, make sure you've correctly installed the **repository root**: 10 | 11 | ```bash 12 | yarn install 13 | yarn run build 14 | ``` 15 | 16 | That will also install the dependencies for this example. 17 | 18 | Then, start this example by doing: 19 | 20 | ```bash 21 | cd examples/react-native-expo 22 | yarn start 23 | ``` 24 | 25 | Then you'll see a menu within your terminal where you can chose where to open the app (Android, iOS, device etc.) 26 | -------------------------------------------------------------------------------- /examples/react-native-expo/tusFileReader.js: -------------------------------------------------------------------------------- 1 | import * as FileSystem from 'expo-file-system' 2 | import base64 from 'base64-js' 3 | 4 | export default function getTusFileReader (file, chunkSize, cb) { 5 | FileSystem.getInfoAsync(file.uri, { size: true }).then((info) => { 6 | cb(null, new TusFileReader(file, info.size)) 7 | }).catch(cb) 8 | } 9 | 10 | class TusFileReader { 11 | constructor (file, size) { 12 | this.file = file 13 | this.size = size 14 | } 15 | 16 | slice (start, end, cb) { 17 | const options = { 18 | encoding: FileSystem.EncodingType.Base64, 19 | length: Math.min(end, this.size) - start, 20 | position: start, 21 | } 22 | FileSystem.readAsStringAsync(this.file.uri, options).then((data) => { 23 | cb(null, base64.toByteArray(data)) 24 | }).catch(cb) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /examples/redux/README.md: -------------------------------------------------------------------------------- 1 | # Redux 2 | 3 | This example uses ImgProcessor with a Redux store. 4 | The same Redux store is also used for other parts of the application, namely the counter example. 5 | Each action is logged to the console using [redux-logger](https://github.com/theaqua/redux-logger). 6 | 7 | This example supports the [Redux Devtools extension](https://github.com/zalmoxisus/redux-devtools-extension), including time travel. 8 | 9 | ## Run it 10 | 11 | To run this example, make sure you've correctly installed the **repository root**: 12 | 13 | ```sh 14 | corepack yarn install 15 | corepack yarn build 16 | ``` 17 | 18 | That will also install the dependencies for this example. 19 | 20 | Then, again in the **repository root**, start this example by doing: 21 | 22 | ```sh 23 | corepack yarn workspace @ImgProcessor-example/redux start 24 | ``` 25 | -------------------------------------------------------------------------------- /examples/redux/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | ImgProcessor example: Redux 7 | 8 | 9 |
      10 |

      A counter

      11 |
      12 |

      13 | Clicked: 0 times 14 | 15 | 16 | 17 | 18 |

      19 |
      20 |

      An ImgProcessor

      21 |
      22 |
      23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /examples/redux/main.js: -------------------------------------------------------------------------------- 1 | import { compose, combineReducers, applyMiddleware } from 'redux' 2 | import { configureStore } from '@reduxjs/toolkit' 3 | import logger from 'redux-logger' 4 | import ImgProcessor from '@ImgProcessor/core' 5 | import ReduxStore from '@ImgProcessor/store-redux' 6 | import * as ImgProcessorReduxStore from '@ImgProcessor/store-redux' 7 | import Dashboard from '@ImgProcessor/dashboard' 8 | import Tus from '@ImgProcessor/tus' 9 | 10 | import '@ImgProcessor/core/dist/style.css' 11 | import '@ImgProcessor/dashboard/dist/style.css' 12 | 13 | function counter (state = 0, action) { 14 | switch (action.type) { 15 | case 'INCREMENT': 16 | return state + 1 17 | case 'DECREMENT': 18 | return state - 1 19 | default: 20 | return state 21 | } 22 | } 23 | 24 | const reducer = combineReducers({ 25 | counter, 26 | // You don't have to use the `ImgProcessor` key. But if you don't, 27 | // you need to provide a custom `selector` to the `ImgProcessorReduxStore` call below. 28 | ImgProcessor: ImgProcessorReduxStore.reducer, 29 | }) 30 | 31 | let enhancer = applyMiddleware( 32 | ImgProcessorReduxStore.middleware(), 33 | logger, 34 | ) 35 | if (typeof __REDUX_DEVTOOLS_EXTENSION__ !== 'undefined') { 36 | // eslint-disable-next-line no-undef 37 | enhancer = compose(enhancer, __REDUX_DEVTOOLS_EXTENSION__()) 38 | } 39 | 40 | const store = configureStore({ 41 | reducer, 42 | enhancers: [enhancer], 43 | middleware: (getDefaultMiddleware) => getDefaultMiddleware({ 44 | serializableCheck: { 45 | ignoredActions: [ImgProcessorReduxStore.STATE_UPDATE], 46 | ignoreState: true, 47 | }, 48 | }), 49 | }) 50 | 51 | // Counter example from https://github.com/reactjs/redux/blob/master/examples/counter-vanilla/index.html 52 | const valueEl = document.querySelector('#value') 53 | 54 | function getCounter () { return store.getState().counter } 55 | function render () { 56 | valueEl.innerHTML = getCounter().toString() 57 | } 58 | render() 59 | store.subscribe(render) 60 | 61 | document.querySelector('#increment').onclick = () => { 62 | store.dispatch({ type: 'INCREMENT' }) 63 | } 64 | document.querySelector('#decrement').onclick = () => { 65 | store.dispatch({ type: 'DECREMENT' }) 66 | } 67 | document.querySelector('#incrementIfOdd').onclick = () => { 68 | if (getCounter() % 2 !== 0) { 69 | store.dispatch({ type: 'INCREMENT' }) 70 | } 71 | } 72 | document.querySelector('#incrementAsync').onclick = () => { 73 | setTimeout(() => store.dispatch({ type: 'INCREMENT' }), 1000) 74 | } 75 | 76 | // ImgProcessor using the same store 77 | const ImgProcessor = new ImgProcessor({ 78 | id: 'redux', 79 | store: new ReduxStore({ store }), 80 | // If we had placed our `reducer` elsewhere in Redux, eg. under an `ImgProcessor` key in the state for a profile page, 81 | // we'd do something like: 82 | // 83 | // store: new ReduxStore({ 84 | // store: store, 85 | // id: 'avatar', 86 | // selector: state => state.pages.profile.ImgProcessor 87 | // }), 88 | debug: true, 89 | }) 90 | ImgProcessor.use(Dashboard, { 91 | target: '#app', 92 | inline: true, 93 | width: 400, 94 | }) 95 | ImgProcessor.use(Tus, { endpoint: 'https://tusd.tusdemo.net/files/' }) 96 | -------------------------------------------------------------------------------- /examples/redux/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@ImgProcessor-example/redux", 3 | "version": "0.0.0", 4 | "type": "module", 5 | "dependencies": { 6 | "@reduxjs/toolkit": "^1.9.3", 7 | "@ImgProcessor/core": "workspace:*", 8 | "@ImgProcessor/dashboard": "workspace:*", 9 | "@ImgProcessor/store-redux": "workspace:*", 10 | "@ImgProcessor/tus": "workspace:*", 11 | "redux": "^4.2.1", 12 | "redux-logger": "^3.0.6" 13 | }, 14 | "devDependencies": { 15 | "vite": "^4.0.0" 16 | }, 17 | "private": true, 18 | "scripts": { 19 | "start": "vite" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /examples/transloadit/.gitignore: -------------------------------------------------------------------------------- 1 | ImgProcessor.min.css 2 | bundle.js 3 | bundle.js.map 4 | -------------------------------------------------------------------------------- /examples/transloadit/README.md: -------------------------------------------------------------------------------- 1 | # Transloadit example 2 | 3 | This example shows how to make advantage of ImgProcessor API to upload files to 4 | [Transloadit](https://transloadit.com/). 5 | 6 | ## Run it 7 | 8 | To run this example, make sure you've correctly installed the **repository root**: 9 | 10 | ```sh 11 | corepack yarn install 12 | corepack yarn build 13 | ``` 14 | 15 | That will also install the dependencies for this example. 16 | 17 | Then, again in the **repository root**, start this example by doing: 18 | 19 | ```sh 20 | corepack yarn workspace @ImgProcessor-example/transloadit start 21 | ``` 22 | -------------------------------------------------------------------------------- /examples/transloadit/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Transloadit Example 7 | 8 | 9 | 36 |
      37 |

      38 | ImgProcessor Transloadit 39 | 44 | playground 45 |

      46 |

      47 | This page contains small examples for different ways you can use ImgProcessor 48 | with Transloadit. Please see the 49 | Github repository 53 | for the source code. 54 |

      55 |
      56 |

      Form

      57 | 58 |

      59 | The form API allows you to easily send files through Transloadit’s 60 | encoding backend. When the user submits the form, any files are uploaded 61 | to Transloadit. The form data is then sent to your own backend, with 62 | additional data about the Transloadit Assemblies that were started. 63 |

      64 |
      65 |

      leave a message

      66 |

      67 | 71 |

      72 |

      73 | 77 |

      78 |

      79 | 85 | 86 |

      87 |

      88 | 89 |

      90 | 91 |
      92 | 93 |
      94 |

      Form with inline Dashboard

      95 | 96 |

      You can also use the Dashboard UI inside a plain old HTML form.

      97 |
      102 |

      leave a message

      103 |

      104 | 108 |

      109 |

      110 | 114 |

      115 |

      116 | 120 |

      121 |

      122 | 123 |

      124 | 125 |
      126 | 127 |
      128 |

      Inline Dashboard

      129 |

      130 | The robodog.dashboard API allows you to embed a Dashboard 131 | at any location. Users can continuously upload files through this UI, so 132 | please make sure this fits your use case! 133 |

      134 |
      135 | 136 |
      137 |

      Dashboard Modal

      138 | 139 |

      140 | This API is a one-shot upload UI using a modal overlay. Call the 141 | function and receive a listen to an event with upload results ✌️ 142 |

      143 | 144 | 145 |

      ImgProcessor.upload()

      146 |

      An <input type=file> backed by ImgProcessor.upload():

      147 |

      148 | 149 |

      150 |
      151 |
      152 | 153 |
      
      163 |         
      
      164 |       
      165 |
      166 | 167 | 168 | 169 | 170 | -------------------------------------------------------------------------------- /examples/transloadit/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@ImgProcessor-example/transloadit", 3 | "version": "0.0.0", 4 | "type": "module", 5 | "devDependencies": { 6 | "npm-run-all": "^4.1.5", 7 | "vite": "^4.0.0" 8 | }, 9 | "dependencies": { 10 | "@ImgProcessor/core": "workspace:*", 11 | "@ImgProcessor/dashboard": "workspace:*", 12 | "@ImgProcessor/drop-target": "workspace:*", 13 | "@ImgProcessor/form": "workspace:*", 14 | "@ImgProcessor/image-editor": "workspace:*", 15 | "@ImgProcessor/progress-bar": "workspace:*", 16 | "@ImgProcessor/remote-sources": "workspace:*", 17 | "@ImgProcessor/transloadit": "workspace:*", 18 | "@ImgProcessor/webcam": "workspace:*", 19 | "express": "^4.16.4", 20 | "he": "^1.2.0" 21 | }, 22 | "private": true, 23 | "scripts": { 24 | "start:server": "node server.js", 25 | "start:client": "vite", 26 | "start": "npm-run-all --parallel start:server start:client" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /examples/transloadit/server.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /* eslint-disable compat/compat */ 4 | import http from 'node:http' 5 | import qs from 'node:querystring' 6 | import he from 'he' 7 | 8 | const e = he.encode 9 | 10 | function Header () { 11 | return ` 12 | 13 | 14 | 15 | 25 | 26 | 27 |
      28 | ` 29 | } 30 | 31 | function Footer () { 32 | return ` 33 |
      34 | 35 | 36 | ` 37 | } 38 | 39 | function FormFields (fields) { 40 | function Field ([name, value]) { 41 | if (name === 'transloadit') return '' 42 | let isValueJSON = false 43 | if (value.startsWith('{') || value.startsWith('[')) { 44 | try { 45 | // eslint-disable-next-line no-param-reassign 46 | value = JSON.stringify( 47 | JSON.parse(value), 48 | null, 49 | 2, 50 | ) 51 | isValueJSON = true 52 | } catch { 53 | // Nothing 54 | } 55 | } 56 | 57 | const prettyValue = isValueJSON ? ` 58 |
      59 | 60 |
      ${e(value)}
      61 |
      62 |
      63 | ` : e(value) 64 | 65 | return ` 66 |
      ${e(name)}
      67 |
      68 | ${prettyValue} 69 |
      70 | ` 71 | } 72 | 73 | return ` 74 |

      Form Fields

      75 |
      76 | ${Object.entries(fields).map(Field).join('\n')} 77 |
      78 | ` 79 | } 80 | 81 | function UploadsList (uploads) { 82 | function Upload (upload) { 83 | return `
    1. ${e(upload.name)}
    2. ` 84 | } 85 | 86 | return ` 87 |
        88 | ${uploads.map(Upload).join('\n')} 89 |
      90 | ` 91 | } 92 | 93 | function ResultsList (results) { 94 | function Result (result) { 95 | return `
    3. ${e(result.name)} View
    4. ` 96 | } 97 | 98 | function ResultsSection (stepName) { 99 | return ` 100 |

      ${e(stepName)}

      101 |
        102 | ${results[stepName].map(Result).join('\n')} 103 |
      104 | ` 105 | } 106 | 107 | return Object.keys(results) 108 | .map(ResultsSection) 109 | .join('\n') 110 | } 111 | 112 | function AssemblyResult (assembly) { 113 | return ` 114 |

      ${e(assembly.assembly_id)} (${e(assembly.ok)})

      115 | ${UploadsList(assembly.uploads)} 116 | ${ResultsList(assembly.results)} 117 | ` 118 | } 119 | 120 | function onrequest (req, res) { 121 | if (req.url !== '/test') { 122 | res.writeHead(404, { 'content-type': 'text/html' }) 123 | res.end('404') 124 | return 125 | } 126 | 127 | function onbody (body) { 128 | const fields = qs.parse(body) 129 | const result = JSON.parse(fields.ImgProcessorResult) 130 | const assemblies = result[0].transloadit 131 | 132 | res.setHeader('content-type', 'text/html') 133 | res.write(Header()) 134 | res.write(FormFields(fields)) 135 | assemblies.forEach((assembly) => { 136 | res.write(AssemblyResult(assembly)) 137 | }) 138 | res.end(Footer()) 139 | } 140 | 141 | { 142 | let body = '' 143 | req.on('data', (chunk) => { body += chunk }) 144 | req.on('end', () => { 145 | onbody(body) 146 | }) 147 | } 148 | } 149 | 150 | /** 151 | * A very haxxor server that outputs some of the data it receives in a POST form parameter. 152 | */ 153 | 154 | const server = http.createServer(onrequest) 155 | server.listen(9967) 156 | -------------------------------------------------------------------------------- /examples/vue/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /dist 4 | /public 5 | 6 | 7 | # local env files 8 | .env.local 9 | .env.*.local 10 | 11 | # Log files 12 | npm-debug.log* 13 | yarn-debug.log* 14 | yarn-error.log* 15 | pnpm-debug.log* 16 | 17 | # Editor directories and files 18 | .idea 19 | .vscode 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /examples/vue/README.md: -------------------------------------------------------------------------------- 1 | # Vue 2 example 2 | 3 | You’re browsing the documentation for Vue v2.x and earlier. Check out 4 | [Vue 3 example](../vue3/) for new projects. 5 | 6 | To run the example, from the root directory of this repo, run the following commands: 7 | 8 | ```sh 9 | corepack yarn install 10 | corepack yarn build 11 | corepack yarn workspace @ImgProcessor-example/vue2 dev 12 | ``` 13 | -------------------------------------------------------------------------------- /examples/vue/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Vite App 8 | 9 | 10 |
      11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /examples/vue/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@ImgProcessor-example/vue2", 3 | "version": "0.0.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "vite", 7 | "build": "vite build", 8 | "preview": "vite preview --port 5050" 9 | }, 10 | "dependencies": { 11 | "@ImgProcessor/core": "workspace:*", 12 | "@ImgProcessor/dashboard": "workspace:*", 13 | "@ImgProcessor/drag-drop": "workspace:*", 14 | "@ImgProcessor/progress-bar": "workspace:*", 15 | "@ImgProcessor/transloadit": "workspace:*", 16 | "@ImgProcessor/vue": "workspace:*", 17 | "vue": "^2.6.14" 18 | }, 19 | "devDependencies": { 20 | "vite": "^4.0.0", 21 | "vite-plugin-vue2": "^2.0.1", 22 | "vue-template-compiler": "^2.6.14" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /examples/vue/src/App.vue: -------------------------------------------------------------------------------- 1 | 58 | 59 | 97 | 98 | 99 | 100 | 101 | 102 | 112 | -------------------------------------------------------------------------------- /examples/vue/src/main.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import App from './App.vue' 3 | 4 | Vue.config.productionTip = false 5 | 6 | new Vue({ 7 | render: h => h(App), 8 | }).$mount('#app') 9 | -------------------------------------------------------------------------------- /examples/vue/vite.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import { createVuePlugin } from 'vite-plugin-vue2' 3 | 4 | // https://vitejs.dev/config/ 5 | export default defineConfig({ 6 | plugins: [createVuePlugin()], 7 | }) 8 | -------------------------------------------------------------------------------- /examples/xhr-bundle/README.md: -------------------------------------------------------------------------------- 1 | # XHR Bundle Upload 2 | 3 | This example uses ImgProcessor with XHRUpload plugin in `bundle` mode. Bundle mode uploads all files to the endpoint in a single request, instead of firing off a new request for each file. This makes uploading a bit slower, but it may be easier to handle on the server side, depending on your setup. 4 | 5 | [`server.cjs`](./server.cjs) contains an example express.js server that receives a multipart form-data upload and responds with some information about the files that were received (name, size) as JSON. It uses [multer](https://npmjs.com/package/multer) to parse the upload stream. 6 | 7 | ## Run it 8 | 9 | To run this example, make sure you've correctly installed the **repository root**: 10 | 11 | ```sh 12 | corepack yarn install 13 | corepack yarn build 14 | ``` 15 | 16 | That will also install the dependencies for this example. 17 | 18 | Then, again in the **repository root**, start this example by doing: 19 | 20 | ```sh 21 | corepack yarn workspace @ImgProcessor-example/xhr-bundle start 22 | ``` 23 | -------------------------------------------------------------------------------- /examples/xhr-bundle/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | ImgProcessor example: XHRUpload to a single endpoint 7 | 17 | 18 | 19 |
      20 |
      21 |

      files[]

      22 |
      23 |
      24 |
      25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /examples/xhr-bundle/main.js: -------------------------------------------------------------------------------- 1 | import ImgProcessor from '@ImgProcessor/core' 2 | import Dashboard from '@ImgProcessor/dashboard' 3 | import XHRUpload from '@ImgProcessor/xhr-upload' 4 | 5 | import '@ImgProcessor/core/dist/style.css' 6 | import '@ImgProcessor/dashboard/dist/style.css' 7 | 8 | const ImgProcessor = new ImgProcessor({ 9 | debug: true, 10 | meta: { something: 'xyz' }, 11 | }) 12 | 13 | ImgProcessor.use(Dashboard, { 14 | target: '#app', 15 | inline: true, 16 | hideRetryButton: true, 17 | hideCancelButton: true, 18 | }) 19 | 20 | ImgProcessor.use(XHRUpload, { 21 | bundle: true, 22 | endpoint: 'http://localhost:9967/upload', 23 | allowedMetaFields: ['something'], 24 | fieldName: 'files', 25 | }) 26 | -------------------------------------------------------------------------------- /examples/xhr-bundle/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@ImgProcessor-example/xhr-bundle", 3 | "version": "0.0.0", 4 | "dependencies": { 5 | "@ImgProcessor/core": "workspace:*", 6 | "@ImgProcessor/dashboard": "workspace:*", 7 | "@ImgProcessor/xhr-upload": "workspace:*", 8 | "cors": "^2.8.5", 9 | "express": "^4.16.4", 10 | "multer": "^1.4.1" 11 | }, 12 | "devDependencies": { 13 | "npm-run-all": "^4.1.5", 14 | "vite": "^4.0.0" 15 | }, 16 | "private": true, 17 | "scripts": { 18 | "start": "run-p start:server start:client", 19 | "start:client": "vite", 20 | "start:server": "node server.cjs" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /examples/xhr-bundle/server.cjs: -------------------------------------------------------------------------------- 1 | const app = require('express')() 2 | const cors = require('cors') 3 | const multer = require('multer') 4 | 5 | const upload = multer({ 6 | storage: multer.memoryStorage(), 7 | }) 8 | 9 | function uploadRoute (req, res) { 10 | res.json({ 11 | files: req.files.map((file) => { 12 | // eslint-disable-next-line no-param-reassign 13 | delete file.buffer 14 | return file 15 | }), 16 | }) 17 | } 18 | 19 | app.use(cors()) 20 | app.post('/upload', upload.array('files'), uploadRoute) 21 | 22 | app.listen(9967) 23 | -------------------------------------------------------------------------------- /freshstart.md: -------------------------------------------------------------------------------- 1 | yoho! 2 | -------------------------------------------------------------------------------- /output/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CoderDonna/ImgProcessor/65c2a84b2335af172f2bcb8edc9ea6561db555fe/output/.keep -------------------------------------------------------------------------------- /packages/@ImgProcessor/angular/.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see https://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 2 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.ts] 12 | quote_type = single 13 | 14 | [*.md] 15 | max_line_length = off 16 | trim_trailing_whitespace = false 17 | -------------------------------------------------------------------------------- /packages/@ImgProcessor/angular/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "ignorePatterns": ["projects/**/*"], 3 | "overrides": [ 4 | { 5 | "files": ["*.ts"], 6 | "extends": [ 7 | "eslint:recommended", 8 | "plugin:@typescript-eslint/recommended", 9 | "plugin:@angular-eslint/recommended", 10 | "plugin:@angular-eslint/template/process-inline-templates" 11 | ], 12 | "rules": { 13 | // eslint-disable-line import/newline-after-import 14 | "@angular-eslint/directive-selector": [ 15 | "error", 16 | { 17 | "type": "attribute", 18 | "prefix": "app", 19 | "style": "camelCase" 20 | } 21 | ], 22 | "@typescript-eslint/semi": ["error", "never"], 23 | "import/no-unresolved": "off", 24 | "import/prefer-default-export": "off", 25 | "@angular-eslint/component-selector": [ 26 | "error", 27 | { 28 | "type": "element", 29 | "prefix": "app", 30 | "style": "kebab-case" 31 | } 32 | ], 33 | "semi": ["error", "never"] 34 | } 35 | }, 36 | { 37 | "files": ["*.html"], 38 | "extends": [ 39 | "plugin:@angular-eslint/template/recommended", 40 | "plugin:@angular-eslint/template/accessibility" 41 | ], 42 | "rules": {} 43 | } 44 | ] 45 | } 46 | -------------------------------------------------------------------------------- /packages/@ImgProcessor/angular/.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # Compiled output 4 | /dist 5 | /tmp 6 | /out-tsc 7 | /bazel-out 8 | 9 | # Node 10 | /node_modules 11 | npm-debug.log 12 | yarn-error.log 13 | 14 | # IDEs and editors 15 | .idea/ 16 | .project 17 | .classpath 18 | .c9/ 19 | *.launch 20 | .settings/ 21 | *.sublime-workspace 22 | 23 | # Visual Studio Code 24 | .vscode/* 25 | !.vscode/settings.json 26 | !.vscode/tasks.json 27 | !.vscode/launch.json 28 | !.vscode/extensions.json 29 | .history/* 30 | 31 | # Miscellaneous 32 | /.angular/cache 33 | .sass-cache/ 34 | /connect.lock 35 | /coverage 36 | /libpeerconnection.log 37 | testem.log 38 | /typings 39 | 40 | # System files 41 | .DS_Store 42 | Thumbs.db 43 | -------------------------------------------------------------------------------- /packages/@ImgProcessor/angular/.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=827846 3 | "recommendations": ["angular.ng-template"] 4 | } 5 | -------------------------------------------------------------------------------- /packages/@ImgProcessor/angular/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 3 | "version": "0.2.0", 4 | "configurations": [ 5 | { 6 | "name": "ng serve", 7 | "type": "chrome", 8 | "request": "launch", 9 | "preLaunchTask": "npm: start", 10 | "url": "http://localhost:4200/" 11 | }, 12 | { 13 | "name": "ng test", 14 | "type": "chrome", 15 | "request": "launch", 16 | "preLaunchTask": "npm: test", 17 | "url": "http://localhost:9876/debug.html" 18 | } 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /packages/@ImgProcessor/angular/.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | // For more information, visit: https://go.microsoft.com/fwlink/?LinkId=733558 3 | "version": "2.0.0", 4 | "tasks": [ 5 | { 6 | "type": "npm", 7 | "script": "start", 8 | "isBackground": true, 9 | "problemMatcher": { 10 | "owner": "typescript", 11 | "pattern": "$tsc", 12 | "background": { 13 | "activeOnStart": true, 14 | "beginsPattern": { 15 | "regexp": "(.*?)" 16 | }, 17 | "endsPattern": { 18 | "regexp": "bundle generation complete" 19 | } 20 | } 21 | } 22 | }, 23 | { 24 | "type": "npm", 25 | "script": "test", 26 | "isBackground": true, 27 | "problemMatcher": { 28 | "owner": "typescript", 29 | "pattern": "$tsc", 30 | "background": { 31 | "activeOnStart": true, 32 | "beginsPattern": { 33 | "regexp": "(.*?)" 34 | }, 35 | "endsPattern": { 36 | "regexp": "bundle generation complete" 37 | } 38 | } 39 | } 40 | } 41 | ] 42 | } 43 | -------------------------------------------------------------------------------- /packages/@ImgProcessor/angular/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # @uppy/angular 2 | 3 | ## 0.6.0 4 | 5 | Released: 2023-09-05 6 | Included in: Uppy v3.15.0 7 | 8 | - @uppy/angular: upgrade to Angular 16.x (Antoine du Hamel / #4642) 9 | 10 | ## 0.5.0 11 | 12 | Released: 2022-11-10 13 | Included in: Uppy v3.3.0 14 | 15 | - @uppy/angular,@uppy/utils: add `cause` support for `AbortError`s (Antoine du Hamel / #4198) 16 | 17 | ## 0.4.3 18 | 19 | Released: 2022-10-19 20 | Included in: Uppy v3.2.0 21 | 22 | - @uppy/angular: remove unnecessary `console.log` call (Antoine du Hamel / #4139) 23 | 24 | ## 0.4.2 25 | 26 | Released: 2022-09-25 27 | Included in: Uppy v3.1.0 28 | 29 | - @uppy/angular: Fix angular build error (Murderlon) 30 | 31 | ## 0.4.1 32 | 33 | Released: 2022-08-30 34 | Included in: Uppy v3.0.1 35 | 36 | - @uppy/angular: fix compiler warning (Antoine du Hamel / #4064) 37 | - @uppy/angular: fix peer dependencies (Antoine du Hamel / #4035) 38 | 39 | ## 0.4.0 40 | 41 | Released: 2022-08-22 42 | Included in: Uppy v3.0.0 43 | 44 | - @uppy/angular: upgrade to Angular 14 (Antoine du Hamel / #3997) 45 | 46 | ## 0.3.1 47 | 48 | Released: 2022-05-30 49 | Included in: Uppy v2.11.0 50 | 51 | - @uppy/angular,@uppy/audio,@uppy/aws-s3-multipart,@uppy/aws-s3,@uppy/box,@uppy/core,@uppy/dashboard,@uppy/drag-drop,@uppy/dropbox,@uppy/facebook,@uppy/file-input,@uppy/form,@uppy/golden-retriever,@uppy/google-drive,@uppy/image-editor,@uppy/informer,@uppy/instagram,@uppy/onedrive,@uppy/progress-bar,@uppy/react,@uppy/redux-dev-tools,@uppy/robodog,@uppy/screen-capture,@uppy/status-bar,@uppy/store-default,@uppy/store-redux,@uppy/thumbnail-generator,@uppy/transloadit,@uppy/tus,@uppy/unsplash,@uppy/url,@uppy/vue,@uppy/webcam,@uppy/xhr-upload,@uppy/zoom: doc: update bundler recommendation (Antoine du Hamel / #3763) 52 | 53 | ## 0.3.0 54 | 55 | Released: 2022-03-02 56 | Included in: Uppy v2.7.0 57 | 58 | - @uppy/angular: update ng version (Antoine du Hamel / #3503) 59 | 60 | ## 0.2.8 61 | 62 | Released: 2021-12-21 63 | Included in: Uppy v2.3.2 64 | 65 | - @uppy/angular,@uppy/companion,@uppy/svelte,@uppy/vue: add `.npmignore` files to ignore `.gitignore` when packing (Antoine du Hamel / #3380) 66 | - @uppy/angular: Fix module field in `package.json` (Merlijn Vos / #3365) 67 | 68 | ## 0.2.6 69 | 70 | Released: 2021-12-07 71 | Included in: Uppy v2.3.0 72 | 73 | - @uppy/angular: examples: update `angular-example` to Angular v13 (Antoine du Hamel / #3325) 74 | -------------------------------------------------------------------------------- /packages/@ImgProcessor/angular/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2020 Transloadit 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 | -------------------------------------------------------------------------------- /packages/@ImgProcessor/angular/README.md: -------------------------------------------------------------------------------- 1 | # Angular 2 | 3 | This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 16.2.0. 4 | 5 | ## Development server 6 | 7 | Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The application will automatically reload if you change any of the source files. 8 | 9 | ## Code scaffolding 10 | 11 | Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`. 12 | 13 | ## Build 14 | 15 | Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. 16 | 17 | ## Running unit tests 18 | 19 | Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io). 20 | 21 | ## Running end-to-end tests 22 | 23 | Run `ng e2e` to execute the end-to-end tests via a platform of your choice. To use this command, you need to first add a package that implements end-to-end testing capabilities. 24 | 25 | ## Further help 26 | 27 | To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI Overview and Command Reference](https://angular.io/cli) page. 28 | -------------------------------------------------------------------------------- /packages/@ImgProcessor/angular/angular.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./node_modules/@angular/cli/lib/config/schema.json", 3 | "version": 1, 4 | "newProjectRoot": "projects", 5 | "projects": { 6 | "@uppy/angular": { 7 | "projectType": "library", 8 | "root": "projects/uppy/angular", 9 | "sourceRoot": "projects/uppy/angular/src", 10 | "prefix": "lib", 11 | "architect": { 12 | "build": { 13 | "builder": "@angular-devkit/build-angular:ng-packagr", 14 | "options": { 15 | "project": "projects/uppy/angular/ng-package.json" 16 | }, 17 | "configurations": { 18 | "production": { 19 | "tsConfig": "projects/uppy/angular/tsconfig.lib.prod.json" 20 | }, 21 | "development": { 22 | "tsConfig": "projects/uppy/angular/tsconfig.lib.json" 23 | } 24 | }, 25 | "defaultConfiguration": "production" 26 | }, 27 | "test": { 28 | "builder": "@angular-devkit/build-angular:karma", 29 | "options": { 30 | "tsConfig": "projects/uppy/angular/tsconfig.spec.json", 31 | "polyfills": ["zone.js", "zone.js/testing"] 32 | } 33 | } 34 | } 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /packages/@ImgProcessor/angular/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular", 3 | "version": "0.0.0", 4 | "scripts": { 5 | "ng": "ng", 6 | "start": "ng serve", 7 | "build": "ng build", 8 | "watch": "ng build --watch --configuration development", 9 | "test": "ng test" 10 | }, 11 | "private": true, 12 | "dependencies": { 13 | "@angular/animations": "^16.2.0", 14 | "@angular/common": "^16.2.0", 15 | "@angular/compiler": "^16.2.0", 16 | "@angular/core": "^16.2.0", 17 | "@angular/forms": "^16.2.0", 18 | "@angular/platform-browser": "^16.2.0", 19 | "@angular/platform-browser-dynamic": "^16.2.0", 20 | "@angular/router": "^16.2.0", 21 | "rxjs": "~7.8.0", 22 | "tslib": "^2.3.0", 23 | "zone.js": "~0.13.0" 24 | }, 25 | "devDependencies": { 26 | "@angular-devkit/build-angular": "^16.2.0", 27 | "@angular-eslint/builder": "16.1.1", 28 | "@angular-eslint/eslint-plugin": "16.1.1", 29 | "@angular-eslint/eslint-plugin-template": "16.1.1", 30 | "@angular-eslint/schematics": "16.1.1", 31 | "@angular-eslint/template-parser": "16.1.1", 32 | "@angular/cli": "~16.2.0", 33 | "@angular/compiler-cli": "^16.2.0", 34 | "@types/jasmine": "~4.3.0", 35 | "@typescript-eslint/eslint-plugin": "5.62.0", 36 | "@typescript-eslint/parser": "5.62.0", 37 | "jasmine-core": "~4.6.0", 38 | "karma": "~6.4.0", 39 | "karma-chrome-launcher": "~3.2.0", 40 | "karma-coverage": "~2.2.0", 41 | "karma-jasmine": "~5.1.0", 42 | "karma-jasmine-html-reporter": "~2.1.0", 43 | "ng-packagr": "^16.2.0", 44 | "typescript": "~5.1" 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /packages/@ImgProcessor/angular/projects/uppy/angular/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../../.eslintrc.json", 3 | "ignorePatterns": ["!**/*"], 4 | "overrides": [ 5 | { 6 | "files": ["*.ts"], 7 | "parserOptions": { 8 | "project": [ 9 | "packages/@uppy/angular/projects/angular/tsconfig.lib.json", 10 | "packages/@uppy/angular/projects/angular/tsconfig.spec.json" 11 | ], 12 | "createDefaultProgram": true 13 | }, 14 | "rules": { 15 | "@angular-eslint/component-selector": [ 16 | "error", 17 | { 18 | "type": "element", 19 | "prefix": "uppy", 20 | "style": "kebab-case" 21 | } 22 | ], 23 | "@angular-eslint/directive-selector": [ 24 | "error", 25 | { 26 | "type": "attribute", 27 | "prefix": "uppy", 28 | "style": "camelCase" 29 | } 30 | ], 31 | "dot-notation": "error", 32 | "indent": "error", 33 | "no-empty-function": "off", 34 | "no-shadow": "error", 35 | "no-unused-expressions": "error", 36 | "no-use-before-define": "off", 37 | "quotes": "error", 38 | "semi": "error" 39 | } 40 | }, 41 | { 42 | "files": ["*.html"], 43 | "rules": {} 44 | } 45 | ] 46 | } 47 | -------------------------------------------------------------------------------- /packages/@ImgProcessor/angular/projects/uppy/angular/README.md: -------------------------------------------------------------------------------- 1 | # @uppy/angular 2 | 3 | Uppy logo: a smiling puppy above a pink upwards arrow 4 | 5 | Build Status 6 | 7 | Angular component wrappers around Uppy’s officially maintained UI plugins. 8 | 9 | Uppy is being developed by the folks at [Transloadit](https://transloadit.com), a versatile file encoding service. 10 | 11 | ## Example 12 | 13 | ```ts 14 | // TODO 15 | ``` 16 | 17 | ## Installation 18 | 19 | ```bash 20 | $ npm install @uppy/angular --save 21 | ``` 22 | 23 | Alternatively, you can also use this plugin in a pre-built bundle from Transloadit’s CDN: Edgly. In that case `Uppy` will attach itself to the global `window.Uppy` object. See the [main Uppy documentation](https://uppy.io/docs/#Installation) for instructions. 24 | 25 | ## Documentation 26 | 27 | Documentation for this plugin can be found on the [Uppy website](https://uppy.io/docs/). 28 | 29 | ## License 30 | 31 | [The MIT License](./LICENSE). 32 | -------------------------------------------------------------------------------- /packages/@ImgProcessor/angular/projects/uppy/angular/ng-package.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "../../../node_modules/ng-packagr/ng-package.schema.json", 3 | "dest": "../../../dist/uppy/angular", 4 | "lib": { 5 | "entryFile": "src/public-api.ts" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/@ImgProcessor/angular/projects/uppy/angular/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@uppy/angular", 3 | "description": "Angular component wrappers around Uppy's official UI plugins.", 4 | "version": "0.6.1", 5 | "license": "MIT", 6 | "homepage": "https://uppy.io", 7 | "keywords": [ 8 | "file uploader", 9 | "uppy", 10 | "uppy-plugin", 11 | "angular", 12 | "angular-components" 13 | ], 14 | "bugs": { 15 | "url": "https://github.com/transloadit/uppy/issues" 16 | }, 17 | "repository": { 18 | "type": "git", 19 | "url": "git+https://github.com/transloadit/uppy.git" 20 | }, 21 | "scripts": { 22 | "prepublishOnly": "rm -fr * && cp -r ../../../dist/uppy/angular .." 23 | }, 24 | "dependencies": { 25 | "tslib": "^2.0.0" 26 | }, 27 | "peerDependencies": { 28 | "@angular/common": "^16.2.0", 29 | "@angular/core": "^16.2.0", 30 | "@uppy/core": "workspace:^", 31 | "@uppy/dashboard": "workspace:^", 32 | "@uppy/drag-drop": "workspace:^", 33 | "@uppy/progress-bar": "workspace:^", 34 | "@uppy/status-bar": "workspace:^", 35 | "@uppy/utils": "workspace:^" 36 | }, 37 | "sideEffects": false 38 | } 39 | -------------------------------------------------------------------------------- /packages/@ImgProcessor/angular/projects/uppy/angular/src/public-api.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Public API Surface of @uppy/angular 3 | */ 4 | 5 | export { UppyAngularDashboardModule } from './lib/components/dashboard/dashboard.module'; 6 | export { UppyAngularDashboardModalModule } from './lib/components/dashboard-modal/dashboard-modal.module'; 7 | export { UppyAngularProgressBarModule } from './lib/components/progress-bar/progress-bar.module'; 8 | export { UppyAngularStatusBarModule } from './lib/components/status-bar/status-bar.module'; 9 | export { UppyAngularDragDropModule } from './lib/components/drag-drop/drag-drop.module'; 10 | export { StatusBarComponent } from './lib/components/status-bar/status-bar.component'; 11 | export { ProgressBarComponent } from './lib/components/progress-bar/progress-bar.component'; 12 | export { DragDropComponent } from './lib/components/drag-drop/drag-drop.component'; 13 | export { DashboardComponent } from './lib/components/dashboard/dashboard.component'; 14 | export { DashboardModalComponent } from './lib/components/dashboard-modal/dashboard-modal.component'; 15 | -------------------------------------------------------------------------------- /packages/@ImgProcessor/angular/projects/uppy/angular/tsconfig.lib.json: -------------------------------------------------------------------------------- 1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */ 2 | { 3 | "extends": "../../../tsconfig.json", 4 | "compilerOptions": { 5 | "outDir": "../../../out-tsc/lib", 6 | "declaration": true, 7 | "declarationMap": true, 8 | "inlineSources": true, 9 | "types": [] 10 | }, 11 | "exclude": ["**/*.spec.ts"] 12 | } 13 | -------------------------------------------------------------------------------- /packages/@ImgProcessor/angular/projects/uppy/angular/tsconfig.lib.prod.json: -------------------------------------------------------------------------------- 1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */ 2 | { 3 | "extends": "./tsconfig.lib.json", 4 | "compilerOptions": { 5 | "declarationMap": false 6 | }, 7 | "angularCompilerOptions": { 8 | "compilationMode": "partial" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /packages/@ImgProcessor/angular/projects/uppy/angular/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */ 2 | { 3 | "extends": "../../../tsconfig.json", 4 | "compilerOptions": { 5 | "outDir": "../../../out-tsc/spec", 6 | "types": ["jasmine"] 7 | }, 8 | "include": ["**/*.spec.ts", "**/*.d.ts"] 9 | } 10 | -------------------------------------------------------------------------------- /packages/@ImgProcessor/angular/tsconfig.json: -------------------------------------------------------------------------------- 1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */ 2 | { 3 | "compileOnSave": false, 4 | "compilerOptions": { 5 | "paths": { 6 | "@uppy/angular": ["dist/uppy/angular"] 7 | }, 8 | "baseUrl": "./", 9 | "outDir": "./dist/out-tsc", 10 | "forceConsistentCasingInFileNames": true, 11 | "strict": true, 12 | "noImplicitOverride": true, 13 | "noPropertyAccessFromIndexSignature": true, 14 | "noImplicitReturns": true, 15 | "noFallthroughCasesInSwitch": true, 16 | "sourceMap": true, 17 | "declaration": false, 18 | "downlevelIteration": true, 19 | "experimentalDecorators": true, 20 | "moduleResolution": "node", 21 | "importHelpers": true, 22 | "target": "ES2022", 23 | "module": "ES2022", 24 | "useDefineForClassFields": false, 25 | "lib": ["ES2022", "dom"] 26 | }, 27 | "angularCompilerOptions": { 28 | "enableI18nLegacyMessageIdFormat": false, 29 | "strictInjectionParameters": true, 30 | "strictInputAccessModifiers": true, 31 | "strictTemplates": true 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /packages/@ImgProcessor/audio/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # @uppy/audio 2 | 3 | ## 1.0.4 4 | 5 | Released: 2023-02-13 6 | Included in: Uppy v3.5.0 7 | 8 | - @uppy/audio,@uppy/core,@uppy/dashboard,@uppy/screen-capture: Warn more instead of erroring (Artur Paikin / #4302) 9 | 10 | ## 1.0.3 11 | 12 | Released: 2023-01-26 13 | Included in: Uppy v3.4.0 14 | 15 | - @uppy/audio: @uppy/audio fix typo in readme (elliotsayes / #4240) 16 | 17 | ## 1.0.2 18 | 19 | Released: 2022-09-25 20 | Included in: Uppy v3.1.0 21 | 22 | - @uppy/audio,@uppy/aws-s3-multipart,@uppy/aws-s3,@uppy/box,@uppy/companion-client,@uppy/companion,@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/image-editor,@uppy/informer,@uppy/instagram,@uppy/locales,@uppy/onedrive,@uppy/progress-bar,@uppy/provider-views,@uppy/react,@uppy/redux-dev-tools,@uppy/remote-sources,@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: add missing entries to changelog for individual packages (Antoine du Hamel / #4092) 23 | 24 | ## 1.0.0 25 | 26 | Released: 2022-08-22 27 | Included in: Uppy v3.0.0 28 | 29 | - Switch to ESM 30 | 31 | ## 0.3.2 32 | 33 | Released: 2022-05-30 34 | Included in: Uppy v2.11.0 35 | 36 | - @uppy/angular,@uppy/audio,@uppy/aws-s3-multipart,@uppy/aws-s3,@uppy/box,@uppy/core,@uppy/dashboard,@uppy/drag-drop,@uppy/dropbox,@uppy/facebook,@uppy/file-input,@uppy/form,@uppy/golden-retriever,@uppy/google-drive,@uppy/image-editor,@uppy/informer,@uppy/instagram,@uppy/onedrive,@uppy/progress-bar,@uppy/react,@uppy/redux-dev-tools,@uppy/robodog,@uppy/screen-capture,@uppy/status-bar,@uppy/store-default,@uppy/store-redux,@uppy/thumbnail-generator,@uppy/transloadit,@uppy/tus,@uppy/unsplash,@uppy/url,@uppy/vue,@uppy/webcam,@uppy/xhr-upload,@uppy/zoom: doc: update bundler recommendation (Antoine du Hamel / #3763) 37 | 38 | ## 0.3.1 39 | 40 | Released: 2022-05-14 41 | Included in: Uppy v2.10.0 42 | 43 | - @uppy/audio: fix types (Merlijn Vos / #3689) 44 | 45 | ## 0.3.0 46 | 47 | Released: 2022-03-16 48 | Included in: Uppy v2.8.0 49 | 50 | - @uppy/audio: refactor to ESM (Antoine du Hamel / #3470) 51 | 52 | ## 0.2.1 53 | 54 | Released: 2021-12-09 55 | Included in: Uppy v2.3.1 56 | 57 | - @uppy/audio: showRecordingLength option was removed, always clearInterval (Artur Paikin / #3351) 58 | 59 | ## 0.2.0 60 | 61 | Released: 2021-12-07 62 | Included in: Uppy v2.3.0 63 | 64 | - @uppy/audio: new @uppy/audio plugin for recording with microphone (Artur Paikin / #2976) 65 | -------------------------------------------------------------------------------- /packages/@ImgProcessor/audio/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2021 Transloadit 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 | -------------------------------------------------------------------------------- /packages/@ImgProcessor/audio/README.md: -------------------------------------------------------------------------------- 1 | # @uppy/audio 2 | 3 | Uppy logo: a smiling puppy above a pink upwards arrow 4 | 5 | CI status for Uppy tests CI status for Companion tests CI status for browser tests 6 | 7 | The Audio plugin for Uppy lets you record audio using a built-in or external microphone, or any other audio device, on desktop and mobile. 8 | 9 | Uppy is being developed by the folks at [Transloadit](https://transloadit.com), a versatile file encoding service. 10 | 11 | ## Example 12 | 13 | ```js 14 | import Uppy from '@uppy/core' 15 | import Audio from '@uppy/audio' 16 | 17 | const uppy = new Uppy() 18 | uppy.use(Audio) 19 | ``` 20 | 21 | ## Installation 22 | 23 | ```bash 24 | $ npm install @uppy/audio 25 | ``` 26 | 27 | Alternatively, you can also use this plugin in a pre-built bundle from Transloadit’s CDN: Edgly. In that case `Uppy` will attach itself to the global `window.Uppy` object. See the [main Uppy documentation](https://uppy.io/docs/#Installation) for instructions. 28 | 29 | ## Documentation 30 | 31 | Documentation for this plugin can be found on the [Uppy website](https://uppy.io/docs/webcam). 32 | 33 | ## License 34 | 35 | [The MIT License](./LICENSE). 36 | -------------------------------------------------------------------------------- /packages/@ImgProcessor/audio/types/index.d.ts: -------------------------------------------------------------------------------- 1 | import type { PluginTarget, UIPlugin, UIPluginOptions } from '@uppy/core' 2 | import type AudioLocale from './generatedLocale' 3 | 4 | export interface AudioOptions extends UIPluginOptions { 5 | target?: PluginTarget 6 | showAudioSourceDropdown?: boolean 7 | locale?: AudioLocale 8 | } 9 | 10 | declare class Audio extends UIPlugin {} 11 | 12 | export default Audio 13 | -------------------------------------------------------------------------------- /packages/@ImgProcessor/url/types/index.d.ts: -------------------------------------------------------------------------------- 1 | import type { RequestClientOptions } from '@ImgProcessor/companion-client' 2 | import type { 3 | IndexedObject, 4 | PluginTarget, 5 | UIPlugin, 6 | UIPluginOptions, 7 | } from '@ImgProcessor/core' 8 | import UrlLocale from './generatedLocale' 9 | 10 | export interface UrlOptions extends UIPluginOptions, RequestClientOptions { 11 | target?: PluginTarget 12 | title?: string 13 | locale?: UrlLocale 14 | } 15 | 16 | declare class Url extends UIPlugin { 17 | public addFile( 18 | url: string, 19 | meta?: IndexedObject, 20 | ): undefined | string | never 21 | } 22 | 23 | export default Url 24 | -------------------------------------------------------------------------------- /packages/ImgProcessor/.npmignore: -------------------------------------------------------------------------------- 1 | # This file need to be there so .gitignored files are still uploaded to the npm registry. 2 | --------------------------------------------------------------------------------