├── README.md
├── SECURITY.md
├── output
└── .keep
├── freshstart.md
├── examples
├── aws-php
│ ├── .gitignore
│ ├── composer.json
│ ├── index.html
│ ├── package.json
│ ├── serve.php
│ ├── main.js
│ ├── readme.md
│ └── s3-sign.php
├── node-xhr
│ ├── .gitignore
│ ├── index.html
│ ├── README.md
│ ├── package.json
│ ├── main.js
│ └── server.js
├── php-xhr
│ ├── .gitignore
│ ├── index.html
│ ├── README.md
│ ├── package.json
│ ├── main.js
│ └── server.php
├── python-xhr
│ ├── .gitignore
│ ├── requirements.txt
│ ├── index.html
│ ├── main.js
│ ├── package.json
│ ├── README.md
│ └── server.py
├── transloadit
│ ├── .gitignore
│ ├── README.md
│ ├── package.json
│ ├── server.js
│ └── index.html
├── react-native-expo
│ ├── .eslintrc.json
│ ├── babel.config.js
│ ├── .expo-shared
│ │ └── assets.json
│ ├── .gitignore
│ ├── index.js
│ ├── SelectFilesButton.js
│ ├── app.json
│ ├── tusFileReader.js
│ ├── readme.md
│ ├── PauseResumeButton.js
│ ├── metro.config.js
│ ├── ProgressBar.js
│ ├── package.json
│ └── FileList.js
├── vue
│ ├── src
│ │ ├── main.js
│ │ └── App.vue
│ ├── vite.config.js
│ ├── .gitignore
│ ├── README.md
│ ├── index.html
│ └── package.json
├── custom-provider
│ ├── index.html
│ ├── client
│ │ ├── main.js
│ │ └── MyCustomProvider.jsx
│ ├── README.md
│ ├── package.json
│ └── server
│ │ ├── CustomProvider.cjs
│ │ └── index.cjs
├── multiple-instances
│ ├── package.json
│ ├── README.md
│ ├── index.html
│ └── main.js
├── xhr-bundle
│ ├── server.cjs
│ ├── package.json
│ ├── main.js
│ ├── index.html
│ └── README.md
├── redux
│ ├── package.json
│ ├── README.md
│ ├── index.html
│ └── main.js
├── aws-nodejs
│ ├── package.json
│ ├── README.md
│ └── public
│ │ └── drag.html
└── bundled
│ ├── package.json
│ ├── index.html
│ ├── sw.js
│ └── index.js
├── e2e
├── cypress
│ ├── fixtures
│ │ ├── images
│ │ │ ├── cat-symbolic-link
│ │ │ ├── cat-symbolic.jpg
│ │ │ ├── image.jpg
│ │ │ ├── kit.jpg
│ │ │ ├── monkey.png
│ │ │ ├── papagai.jpg
│ │ │ ├── papagai.png
│ │ │ ├── traffic.jpg
│ │ │ └── carToCheck.jpg
│ │ └── DeepFrozenStore.mjs
│ ├── integration
│ │ ├── dashboard-aws.spec.ts
│ │ ├── dashboard-vue.spec.ts
│ │ ├── dashboard-tus.spec.ts
│ │ ├── dashboard-compressor.spec.ts
│ │ ├── dashboard-ui.spec.ts
│ │ ├── reusable-tests.ts
│ │ ├── dashboard-xhr.spec.ts
│ │ └── react.spec.ts
│ └── support
│ │ ├── index.ts
│ │ ├── e2e.ts
│ │ ├── commands.ts
│ │ └── createFakeFile.ts
├── clients
│ ├── dashboard-vue
│ │ ├── index.js
│ │ ├── index.html
│ │ └── App.vue
│ ├── dashboard-compressor
│ │ ├── index.html
│ │ └── app.js
│ ├── react
│ │ ├── index.jsx
│ │ ├── index.html
│ │ └── App.jsx
│ ├── dashboard-ui
│ │ ├── index.html
│ │ └── app.js
│ ├── dashboard-aws
│ │ ├── index.html
│ │ └── app.js
│ ├── dashboard-tus
│ │ ├── index.html
│ │ └── app.js
│ ├── dashboard-xhr
│ │ ├── index.html
│ │ └── app.js
│ ├── dashboard-transloadit
│ │ ├── index.html
│ │ ├── app.js
│ │ └── generateSignatureIfSecret.js
│ ├── dashboard-aws-multipart
│ │ ├── index.html
│ │ └── app.js
│ └── index.html
├── .parcelrc
├── tsconfig.json
├── cypress.config.mjs
├── mock-server.mjs
├── package.json
├── start-companion-with-load-balancer.mjs
└── generate-test.mjs
├── .dockerignore
├── .prettierignore
├── packages
├── ImgProcessor
│ └── .npmignore
└── @ImgProcessor
│ ├── angular
│ ├── .vscode
│ │ ├── extensions.json
│ │ ├── launch.json
│ │ └── tasks.json
│ ├── projects
│ │ └── uppy
│ │ │ └── angular
│ │ │ ├── ng-package.json
│ │ │ ├── tsconfig.lib.prod.json
│ │ │ ├── tsconfig.spec.json
│ │ │ ├── tsconfig.lib.json
│ │ │ ├── src
│ │ │ └── public-api.ts
│ │ │ ├── package.json
│ │ │ ├── README.md
│ │ │ └── .eslintrc.json
│ ├── .editorconfig
│ ├── .gitignore
│ ├── tsconfig.json
│ ├── LICENSE
│ ├── README.md
│ ├── angular.json
│ ├── .eslintrc.json
│ ├── package.json
│ └── CHANGELOG.md
│ ├── audio
│ ├── types
│ │ └── index.d.ts
│ ├── LICENSE
│ ├── README.md
│ └── CHANGELOG.md
│ └── url
│ └── types
│ └── index.d.ts
├── .eslintignore
├── docker-compose-test.yml
├── .remarkignore
├── .browserslistrc
├── .prettierrc.js
├── .stylelintrc.json
├── .editorconfig
├── Dockerfile.test
├── docker-compose.yml
├── bin
├── to-gif-hq.sh
├── to-gif-hd.sh
├── companion.sh
├── to-gif.sh
├── build-ts.mjs
├── update-yarn.sh
├── build-bundle.mjs
├── build-css.js
└── build-lib.js
├── .yarnrc.yml
├── .yarn
└── patches
│ ├── p-queue-npm-7.4.1-e0cf0a6f17.patch
│ ├── uuid-npm-8.3.2-eca0baba53.patch
│ ├── stylelint-config-rational-order-npm-0.1.2-d8336e84ed.patch
│ ├── start-server-and-test-npm-1.14.0-841aa34fdf.patch
│ ├── pre-commit-npm-1.2.2-f30af83877.patch
│ └── preact-npm-10.10.0-dd04de05e8.patch
├── docker-compose-dev.yml
├── .vscode
├── uppy.code-workspace
└── uppy.code-workspace.bak
├── .gitignore
├── babel.config.js
├── LICENSE
├── Dockerfile
├── atest.ps1
├── Makefile
├── .env.example
└── BUNDLE-README.md
/README.md:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/SECURITY.md:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/output/.keep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/freshstart.md:
--------------------------------------------------------------------------------
1 | yoho!
2 |
--------------------------------------------------------------------------------
/examples/aws-php/.gitignore:
--------------------------------------------------------------------------------
1 | vendor
2 |
--------------------------------------------------------------------------------
/examples/node-xhr/.gitignore:
--------------------------------------------------------------------------------
1 | uploads/
2 |
--------------------------------------------------------------------------------
/examples/php-xhr/.gitignore:
--------------------------------------------------------------------------------
1 | uploads/
2 |
--------------------------------------------------------------------------------
/examples/python-xhr/.gitignore:
--------------------------------------------------------------------------------
1 | uploads/
2 |
--------------------------------------------------------------------------------
/e2e/cypress/fixtures/images/cat-symbolic-link:
--------------------------------------------------------------------------------
1 | ./cat.jpg
2 |
--------------------------------------------------------------------------------
/e2e/cypress/fixtures/images/cat-symbolic.jpg:
--------------------------------------------------------------------------------
1 | ./cat.jpg
2 |
--------------------------------------------------------------------------------
/examples/python-xhr/requirements.txt:
--------------------------------------------------------------------------------
1 | flask
2 | werkzeug
3 | flask-cors
4 |
--------------------------------------------------------------------------------
/examples/transloadit/.gitignore:
--------------------------------------------------------------------------------
1 | ImgProcessor.min.css
2 | bundle.js
3 | bundle.js.map
4 |
--------------------------------------------------------------------------------
/.dockerignore:
--------------------------------------------------------------------------------
1 | **/node_modules
2 | .git
3 | website
4 | assets
5 | private
6 | e2e
7 | .env
8 |
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | *.js
3 | *.jsx
4 | *.cjs
5 | *.mjs
6 | !private/js2ts/*
7 | *.md
8 | *.lock
9 |
--------------------------------------------------------------------------------
/packages/ImgProcessor/.npmignore:
--------------------------------------------------------------------------------
1 | # This file need to be there so .gitignored files are still uploaded to the npm registry.
2 |
--------------------------------------------------------------------------------
/e2e/cypress/fixtures/images/image.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CoderDonna/ImgProcessor/HEAD/e2e/cypress/fixtures/images/image.jpg
--------------------------------------------------------------------------------
/e2e/cypress/fixtures/images/kit.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CoderDonna/ImgProcessor/HEAD/e2e/cypress/fixtures/images/kit.jpg
--------------------------------------------------------------------------------
/e2e/cypress/fixtures/images/monkey.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CoderDonna/ImgProcessor/HEAD/e2e/cypress/fixtures/images/monkey.png
--------------------------------------------------------------------------------
/e2e/cypress/fixtures/images/papagai.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CoderDonna/ImgProcessor/HEAD/e2e/cypress/fixtures/images/papagai.jpg
--------------------------------------------------------------------------------
/e2e/cypress/fixtures/images/papagai.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CoderDonna/ImgProcessor/HEAD/e2e/cypress/fixtures/images/papagai.png
--------------------------------------------------------------------------------
/e2e/cypress/fixtures/images/traffic.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CoderDonna/ImgProcessor/HEAD/e2e/cypress/fixtures/images/traffic.jpg
--------------------------------------------------------------------------------
/e2e/cypress/fixtures/images/carToCheck.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CoderDonna/ImgProcessor/HEAD/e2e/cypress/fixtures/images/carToCheck.jpg
--------------------------------------------------------------------------------
/e2e/clients/dashboard-vue/index.js:
--------------------------------------------------------------------------------
1 | import { createApp } from 'vue'
2 | import App from './App.vue'
3 |
4 | createApp(App).mount('#app')
5 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/e2e/clients/dashboard-compressor/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | dashboard-compressor
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/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/clients/dashboard-ui/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | dashboard-ui
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/e2e/clients/react/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | dashboard-react
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/e2e/clients/dashboard-aws/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | dashboard-aws
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/e2e/clients/dashboard-tus/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | dashboard-tus
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/e2e/clients/dashboard-vue/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | dashboard-vue
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/e2e/clients/dashboard-xhr/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | dashboard-xhr
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/e2e/clients/dashboard-transloadit/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | dashboard-transloadit
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/e2e/clients/dashboard-aws-multipart/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | dashboard-aws-multipart
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/examples/php-xhr/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | PHP + ImgProcessor Example
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/examples/custom-provider/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | ImgProcessor Custom provider Example
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/python-xhr/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Python + ImgProcessor Example
7 |
8 |
9 | This app requires JavaScript.
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/examples/aws-php/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | ImgProcessor AWS Presigned URL Example
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/examples/vue/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Vite App
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/examples/node-xhr/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Node.js + ImgProcessor Example
7 |
8 |
9 | The app requires JavaScript to be enabled.
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/.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/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/.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/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/e2e/clients/dashboard-vue/App.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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/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/aws-php/serve.php:
--------------------------------------------------------------------------------
1 | {
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 |
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/examples/xhr-bundle/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | ImgProcessor example: XHRUpload to a single endpoint
7 |
17 |
18 |
19 |
20 |
24 |
25 |
26 | This app requires JavaScript.
27 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/examples/bundled/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | ImgProcessor
7 |
8 |
9 |
21 |
22 | ImgProcessor
23 |
24 |
28 |
29 |
30 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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/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/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/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/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 | Increment if odd
17 | Increment async
18 |
19 |
20 | An ImgProcessor
21 |
22 |
23 |
24 | This app requires JavaScript.
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/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 | This app requires JavaScript to be enabled.
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/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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-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 |
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/clients/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | End-to-End test suite
6 |
7 |
8 | Test apps
9 |
10 |
29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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/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/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/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 |
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/packages/@ImgProcessor/angular/projects/uppy/angular/README.md:
--------------------------------------------------------------------------------
1 | # @uppy/angular
2 |
3 |
4 |
5 |
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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/.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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/audio/README.md:
--------------------------------------------------------------------------------
1 | # @uppy/audio
2 |
3 |
4 |
5 |
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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/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 | setOpen(!open)}>
29 | Open Modal
30 |
31 |
32 |
33 |
34 |
35 |
36 | )
37 | }
38 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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-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 |
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/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/vue/src/App.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
Welcome to ImgProcessor Vue Demo!
4 |
Inline Dashboard
5 |
6 | {
11 | showInlineDashboard = event.target.checked
12 | }
13 | "
14 | />
15 | Show Dashboard
16 |
17 |
24 |
Modal Dashboard
25 |
26 | Show Dashboard
27 |
34 |
35 |
36 |
Drag Drop Area
37 |
48 |
49 |
Progress Bar
50 |
56 |
57 |
58 |
59 |
97 |
98 |
99 |
100 |
101 |
102 |
112 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 | Upload
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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 | ` ${name} \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 |
--------------------------------------------------------------------------------
/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 `${e(upload.name)} `
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 `${e(result.name)} View `
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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
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 | name:
106 |
107 |
108 |
109 |
110 | message:
112 |
113 |
114 |
115 |
116 |
117 | attachments:
118 |
119 |
120 |
121 |
122 | Upload
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 | Open
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 |
--------------------------------------------------------------------------------