├── .eslintignore ├── .gitignore ├── .eslintrc.json ├── .gitattributes ├── src ├── VImageInput │ ├── name.js │ ├── props │ │ ├── name.js │ │ ├── value.js │ │ ├── imageQuality.js │ │ ├── clearIconStyle.js │ │ ├── errorIconStyle.js │ │ ├── successIconStyle.js │ │ ├── uploadIconStyle.js │ │ ├── imageBackgroundColor.js │ │ ├── flipHorizontallyIconStyle.js │ │ ├── flipVerticallyIconStyle.js │ │ ├── rotateClockwiseIconStyle.js │ │ ├── rotateCounterClockwiseIconStyle.js │ │ ├── debounce.js │ │ ├── clearable.js │ │ ├── disabled.js │ │ ├── fullWidth.js │ │ ├── imageHeight.js │ │ ├── imageWidth.js │ │ ├── readonly.js │ │ ├── clearIcon.js │ │ ├── errorIcon.js │ │ ├── fullHeight.js │ │ ├── hideActions.js │ │ ├── imageFormat.js │ │ ├── imageMaxScaling.js │ │ ├── overlayPadding.js │ │ ├── imageMinScaling.js │ │ ├── overlayBorderColor.js │ │ ├── overlayBorderWidth.js │ │ ├── successIcon.js │ │ ├── uploadIcon.js │ │ ├── flipVerticallyIcon.js │ │ ├── overlayBackgroundColor.js │ │ ├── rotateClockwiseIcon.js │ │ ├── flipHorizontallyIcon.js │ │ ├── rotateCounterClockwiseIcon.js │ │ └── index.js │ ├── directives.js │ ├── methods │ │ ├── clear.js │ │ ├── scaleTo.js │ │ ├── onPan.js │ │ ├── flipVertically.js │ │ ├── flipHorizontally.js │ │ ├── rotateClockwise.js │ │ ├── rotateCounterClockwise.js │ │ ├── index.js │ │ └── load.js │ ├── watch │ │ ├── imageData.js │ │ ├── updateImageData.js │ │ ├── value.js │ │ └── index.js │ ├── components │ │ ├── ActionButton │ │ │ ├── index.js │ │ │ └── render.js │ │ ├── ClearButton │ │ │ ├── index.js │ │ │ └── render.js │ │ ├── FileUpload │ │ │ ├── index.js │ │ │ └── render.js │ │ ├── ScalingSlider │ │ │ ├── index.js │ │ │ └── render.js │ │ ├── FlipHorizontallyButton │ │ │ ├── index.js │ │ │ └── render.js │ │ ├── FlipVerticallyButton │ │ │ ├── index.js │ │ │ └── render.js │ │ ├── RotateClockwiseButton │ │ │ ├── index.js │ │ │ └── render.js │ │ ├── RotateCounterClockwiseButton │ │ │ ├── index.js │ │ │ └── render.js │ │ └── index.js │ ├── computed │ │ ├── computedMaxScaling.js │ │ ├── computedMaxCroppingLeft.js │ │ ├── computedMaxCroppingTop.js │ │ ├── computedMinCroppingLeft.js │ │ ├── computedMinCroppingTop.js │ │ ├── relativeRotatedInternalImageWidth.js │ │ ├── relativeRotatedInternalImageHeight.js │ │ ├── scaledRotatedInternalImageWidth.js │ │ ├── rotatedInternalImageHeight.js │ │ ├── rotatedInternalImageWidth.js │ │ ├── scaledRotatedInternalImageHeight.js │ │ ├── scaledRotatedInternalImageHeightDifference.js │ │ ├── scaledRotatedInternalImageWidthDifference.js │ │ ├── scaling.js │ │ ├── checkeredBackground.js │ │ ├── computedMinScaling.js │ │ ├── croppingLeft.js │ │ ├── croppingTop.js │ │ ├── updateImageData.js │ │ └── index.js │ ├── data.js │ ├── index.js │ ├── created.js │ └── render.js ├── core │ ├── Function │ │ ├── noop.js │ │ ├── isFunction.js │ │ ├── constant.js │ │ └── cast.js │ ├── Math │ │ └── clamp.js │ ├── Promise │ │ └── try.js │ └── Object │ │ └── mapValues.js ├── VueFileUpload │ ├── computed │ │ ├── index.js │ │ └── on.js │ ├── props │ │ ├── disabled.js │ │ └── index.js │ ├── methods │ │ ├── onDragOver.js │ │ ├── onDragEnter.js │ │ ├── onDragLeave.js │ │ ├── onChange.js │ │ ├── onDrop.js │ │ ├── onClick.js │ │ ├── index.js │ │ └── load.js │ ├── index.js │ ├── data.js │ └── render.js └── index.js ├── a-la-carte.js ├── rollup.config.js ├── package.json ├── LICENSE ├── demo └── script.js ├── README.md ├── index.html └── index.js /.eslintignore: -------------------------------------------------------------------------------- 1 | /index.js 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | {"extends": "seregpie"} 2 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | index.html linguist-documentation 2 | -------------------------------------------------------------------------------- /src/VImageInput/name.js: -------------------------------------------------------------------------------- 1 | export default 'VImageInput'; 2 | -------------------------------------------------------------------------------- /src/VImageInput/props/name.js: -------------------------------------------------------------------------------- 1 | export default String; 2 | -------------------------------------------------------------------------------- /src/VImageInput/props/value.js: -------------------------------------------------------------------------------- 1 | export default String; 2 | -------------------------------------------------------------------------------- /src/core/Function/noop.js: -------------------------------------------------------------------------------- 1 | export default function() {} 2 | -------------------------------------------------------------------------------- /src/VImageInput/props/imageQuality.js: -------------------------------------------------------------------------------- 1 | export default {}; 2 | -------------------------------------------------------------------------------- /src/VImageInput/props/clearIconStyle.js: -------------------------------------------------------------------------------- 1 | export default Object; 2 | -------------------------------------------------------------------------------- /src/VImageInput/props/errorIconStyle.js: -------------------------------------------------------------------------------- 1 | export default Object; 2 | -------------------------------------------------------------------------------- /src/VImageInput/props/successIconStyle.js: -------------------------------------------------------------------------------- 1 | export default Object; 2 | -------------------------------------------------------------------------------- /src/VImageInput/props/uploadIconStyle.js: -------------------------------------------------------------------------------- 1 | export default Object; 2 | -------------------------------------------------------------------------------- /src/VImageInput/props/imageBackgroundColor.js: -------------------------------------------------------------------------------- 1 | export default String; 2 | -------------------------------------------------------------------------------- /src/VImageInput/props/flipHorizontallyIconStyle.js: -------------------------------------------------------------------------------- 1 | export default Object; 2 | -------------------------------------------------------------------------------- /src/VImageInput/props/flipVerticallyIconStyle.js: -------------------------------------------------------------------------------- 1 | export default Object; 2 | -------------------------------------------------------------------------------- /src/VImageInput/props/rotateClockwiseIconStyle.js: -------------------------------------------------------------------------------- 1 | export default Object; 2 | -------------------------------------------------------------------------------- /src/VImageInput/props/rotateCounterClockwiseIconStyle.js: -------------------------------------------------------------------------------- 1 | export default Object; 2 | -------------------------------------------------------------------------------- /src/VImageInput/props/debounce.js: -------------------------------------------------------------------------------- 1 | export default { 2 | type: Number, 3 | default: 0, 4 | }; 5 | -------------------------------------------------------------------------------- /src/VImageInput/props/clearable.js: -------------------------------------------------------------------------------- 1 | export default { 2 | type: Boolean, 3 | default: false, 4 | }; 5 | -------------------------------------------------------------------------------- /src/VImageInput/props/disabled.js: -------------------------------------------------------------------------------- 1 | export default { 2 | type: Boolean, 3 | default: false, 4 | }; 5 | -------------------------------------------------------------------------------- /src/VImageInput/props/fullWidth.js: -------------------------------------------------------------------------------- 1 | export default { 2 | type: Boolean, 3 | default: false, 4 | }; 5 | -------------------------------------------------------------------------------- /src/VImageInput/props/imageHeight.js: -------------------------------------------------------------------------------- 1 | export default { 2 | type: Number, 3 | default: 256, 4 | }; 5 | -------------------------------------------------------------------------------- /src/VImageInput/props/imageWidth.js: -------------------------------------------------------------------------------- 1 | export default { 2 | type: Number, 3 | default: 256, 4 | }; 5 | -------------------------------------------------------------------------------- /src/VImageInput/props/readonly.js: -------------------------------------------------------------------------------- 1 | export default { 2 | type: Boolean, 3 | default: false, 4 | }; 5 | -------------------------------------------------------------------------------- /src/VImageInput/directives.js: -------------------------------------------------------------------------------- 1 | import Claw from 'vueclaw'; 2 | 3 | export default { 4 | Claw, 5 | }; 6 | -------------------------------------------------------------------------------- /src/VImageInput/methods/clear.js: -------------------------------------------------------------------------------- 1 | export default function() { 2 | this.internalImageData = null; 3 | } 4 | -------------------------------------------------------------------------------- /src/VImageInput/methods/scaleTo.js: -------------------------------------------------------------------------------- 1 | export default function(scaling) { 2 | this.scaling = scaling; 3 | } 4 | -------------------------------------------------------------------------------- /src/VImageInput/props/clearIcon.js: -------------------------------------------------------------------------------- 1 | export default { 2 | type: String, 3 | default: '$clear', 4 | }; 5 | -------------------------------------------------------------------------------- /src/VImageInput/props/errorIcon.js: -------------------------------------------------------------------------------- 1 | export default { 2 | type: String, 3 | default: '$error', 4 | }; 5 | -------------------------------------------------------------------------------- /src/VImageInput/props/fullHeight.js: -------------------------------------------------------------------------------- 1 | export default { 2 | type: Boolean, 3 | default: false, 4 | }; 5 | -------------------------------------------------------------------------------- /src/VImageInput/props/hideActions.js: -------------------------------------------------------------------------------- 1 | export default { 2 | type: Boolean, 3 | default: false, 4 | }; 5 | -------------------------------------------------------------------------------- /src/VImageInput/props/imageFormat.js: -------------------------------------------------------------------------------- 1 | export default { 2 | type: String, 3 | default: 'png', 4 | }; 5 | -------------------------------------------------------------------------------- /src/VImageInput/props/imageMaxScaling.js: -------------------------------------------------------------------------------- 1 | export default { 2 | type: Number, 3 | default: 1, 4 | }; 5 | -------------------------------------------------------------------------------- /src/VImageInput/props/overlayPadding.js: -------------------------------------------------------------------------------- 1 | export default { 2 | type: String, 3 | default: '50px', 4 | }; 5 | -------------------------------------------------------------------------------- /src/VueFileUpload/computed/index.js: -------------------------------------------------------------------------------- 1 | import on from './on'; 2 | 3 | export default { 4 | on, 5 | }; 6 | -------------------------------------------------------------------------------- /src/VueFileUpload/props/disabled.js: -------------------------------------------------------------------------------- 1 | export default { 2 | type: Boolean, 3 | default: false, 4 | }; 5 | -------------------------------------------------------------------------------- /src/VImageInput/props/imageMinScaling.js: -------------------------------------------------------------------------------- 1 | export default { 2 | type: String, 3 | default: 'cover', 4 | }; 5 | -------------------------------------------------------------------------------- /src/VImageInput/props/overlayBorderColor.js: -------------------------------------------------------------------------------- 1 | export default { 2 | type: String, 3 | default: '#fff', 4 | }; 5 | -------------------------------------------------------------------------------- /src/VImageInput/props/overlayBorderWidth.js: -------------------------------------------------------------------------------- 1 | export default { 2 | type: String, 3 | default: '4px', 4 | }; 5 | -------------------------------------------------------------------------------- /src/VImageInput/props/successIcon.js: -------------------------------------------------------------------------------- 1 | export default { 2 | type: String, 3 | default: '$success', 4 | }; 5 | -------------------------------------------------------------------------------- /src/VImageInput/props/uploadIcon.js: -------------------------------------------------------------------------------- 1 | export default { 2 | type: String, 3 | default: 'mdi-upload', 4 | }; 5 | -------------------------------------------------------------------------------- /src/VImageInput/watch/imageData.js: -------------------------------------------------------------------------------- 1 | export default function(value) { 2 | this.$emit('input', value); 3 | } 4 | -------------------------------------------------------------------------------- /src/VueFileUpload/methods/onDragOver.js: -------------------------------------------------------------------------------- 1 | export default function(event) { 2 | event.preventDefault(); 3 | } 4 | -------------------------------------------------------------------------------- /src/core/Function/isFunction.js: -------------------------------------------------------------------------------- 1 | export default function(value) { 2 | return typeof value === 'function'; 3 | } 4 | -------------------------------------------------------------------------------- /src/core/Math/clamp.js: -------------------------------------------------------------------------------- 1 | export default function(n, min, max) { 2 | return Math.min(Math.max(n, min), max); 3 | } 4 | -------------------------------------------------------------------------------- /src/VueFileUpload/props/index.js: -------------------------------------------------------------------------------- 1 | import disabled from './disabled'; 2 | 3 | export default { 4 | disabled, 5 | }; 6 | -------------------------------------------------------------------------------- /src/VImageInput/props/flipVerticallyIcon.js: -------------------------------------------------------------------------------- 1 | export default { 2 | type: String, 3 | default: 'mdi-flip-vertical', 4 | }; 5 | -------------------------------------------------------------------------------- /src/VImageInput/props/overlayBackgroundColor.js: -------------------------------------------------------------------------------- 1 | export default { 2 | type: String, 3 | default: 'rgba(0,0,0,0.5)', 4 | }; 5 | -------------------------------------------------------------------------------- /src/VImageInput/props/rotateClockwiseIcon.js: -------------------------------------------------------------------------------- 1 | export default { 2 | type: String, 3 | default: 'mdi-rotate-right', 4 | }; 5 | -------------------------------------------------------------------------------- /src/core/Function/constant.js: -------------------------------------------------------------------------------- 1 | export default function(value) { 2 | return function() { 3 | return value; 4 | }; 5 | } 6 | -------------------------------------------------------------------------------- /src/VImageInput/props/flipHorizontallyIcon.js: -------------------------------------------------------------------------------- 1 | export default { 2 | type: String, 3 | default: 'mdi-flip-horizontal', 4 | }; 5 | -------------------------------------------------------------------------------- /src/VImageInput/props/rotateCounterClockwiseIcon.js: -------------------------------------------------------------------------------- 1 | export default { 2 | type: String, 3 | default: 'mdi-rotate-left', 4 | }; 5 | -------------------------------------------------------------------------------- /src/VImageInput/watch/updateImageData.js: -------------------------------------------------------------------------------- 1 | export default function(newTimeoutId, oldTimeoutId) { 2 | clearTimeout(oldTimeoutId); 3 | } 4 | -------------------------------------------------------------------------------- /src/core/Promise/try.js: -------------------------------------------------------------------------------- 1 | export default function(func) { 2 | return new Promise(resolve => { 3 | resolve(func()); 4 | }); 5 | } 6 | -------------------------------------------------------------------------------- /src/VueFileUpload/methods/onDragEnter.js: -------------------------------------------------------------------------------- 1 | export default function(event) { 2 | event.preventDefault(); 3 | this.dragging = true; 4 | } 5 | -------------------------------------------------------------------------------- /src/VueFileUpload/methods/onDragLeave.js: -------------------------------------------------------------------------------- 1 | export default function(event) { 2 | event.preventDefault(); 3 | this.dragging = false; 4 | } 5 | -------------------------------------------------------------------------------- /src/VImageInput/components/ActionButton/index.js: -------------------------------------------------------------------------------- 1 | import render from './render'; 2 | 3 | export default { 4 | functional: true, 5 | render, 6 | }; 7 | -------------------------------------------------------------------------------- /src/VImageInput/components/ClearButton/index.js: -------------------------------------------------------------------------------- 1 | import render from './render'; 2 | 3 | export default { 4 | functional: true, 5 | render, 6 | }; 7 | -------------------------------------------------------------------------------- /src/VImageInput/components/FileUpload/index.js: -------------------------------------------------------------------------------- 1 | import render from './render'; 2 | 3 | export default { 4 | functional: true, 5 | render, 6 | }; 7 | -------------------------------------------------------------------------------- /src/VImageInput/components/ScalingSlider/index.js: -------------------------------------------------------------------------------- 1 | import render from './render'; 2 | 3 | export default { 4 | functional: true, 5 | render, 6 | }; 7 | -------------------------------------------------------------------------------- /src/VImageInput/computed/computedMaxScaling.js: -------------------------------------------------------------------------------- 1 | export default function() { 2 | return Math.max(this.imageMaxScaling, this.computedMinScaling); 3 | } 4 | -------------------------------------------------------------------------------- /src/VImageInput/components/FlipHorizontallyButton/index.js: -------------------------------------------------------------------------------- 1 | import render from './render'; 2 | 3 | export default { 4 | functional: true, 5 | render, 6 | }; 7 | -------------------------------------------------------------------------------- /src/VImageInput/components/FlipVerticallyButton/index.js: -------------------------------------------------------------------------------- 1 | import render from './render'; 2 | 3 | export default { 4 | functional: true, 5 | render, 6 | }; 7 | -------------------------------------------------------------------------------- /src/VImageInput/components/RotateClockwiseButton/index.js: -------------------------------------------------------------------------------- 1 | import render from './render'; 2 | 3 | export default { 4 | functional: true, 5 | render, 6 | }; 7 | -------------------------------------------------------------------------------- /src/VImageInput/computed/computedMaxCroppingLeft.js: -------------------------------------------------------------------------------- 1 | export default function() { 2 | return Math.max(0, this.scaledRotatedInternalImageWidthDifference); 3 | } 4 | -------------------------------------------------------------------------------- /src/VImageInput/computed/computedMaxCroppingTop.js: -------------------------------------------------------------------------------- 1 | export default function() { 2 | return Math.max(0, this.scaledRotatedInternalImageHeightDifference); 3 | } 4 | -------------------------------------------------------------------------------- /src/VImageInput/computed/computedMinCroppingLeft.js: -------------------------------------------------------------------------------- 1 | export default function() { 2 | return Math.min(0, this.scaledRotatedInternalImageWidthDifference); 3 | } 4 | -------------------------------------------------------------------------------- /src/VImageInput/computed/computedMinCroppingTop.js: -------------------------------------------------------------------------------- 1 | export default function() { 2 | return Math.min(0, this.scaledRotatedInternalImageHeightDifference); 3 | } 4 | -------------------------------------------------------------------------------- /src/VImageInput/computed/relativeRotatedInternalImageWidth.js: -------------------------------------------------------------------------------- 1 | export default function() { 2 | return this.imageWidth / this.rotatedInternalImageWidth; 3 | } 4 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import Component from './VImageInput'; 2 | 3 | globalThis.window?.Vue?.component(Component.name, Component); 4 | 5 | export default Component; 6 | -------------------------------------------------------------------------------- /src/VImageInput/components/RotateCounterClockwiseButton/index.js: -------------------------------------------------------------------------------- 1 | import render from './render'; 2 | 3 | export default { 4 | functional: true, 5 | render, 6 | }; 7 | -------------------------------------------------------------------------------- /src/VImageInput/computed/relativeRotatedInternalImageHeight.js: -------------------------------------------------------------------------------- 1 | export default function() { 2 | return this.imageHeight / this.rotatedInternalImageHeight; 3 | } 4 | -------------------------------------------------------------------------------- /src/VImageInput/computed/scaledRotatedInternalImageWidth.js: -------------------------------------------------------------------------------- 1 | export default function() { 2 | return Math.round(this.scaling * this.rotatedInternalImageWidth); 3 | } 4 | -------------------------------------------------------------------------------- /src/VImageInput/computed/rotatedInternalImageHeight.js: -------------------------------------------------------------------------------- 1 | export default function() { 2 | return this.rotated ? this.internalImageWidth : this.internalImageHeight; 3 | } 4 | -------------------------------------------------------------------------------- /src/VImageInput/computed/rotatedInternalImageWidth.js: -------------------------------------------------------------------------------- 1 | export default function() { 2 | return this.rotated ? this.internalImageHeight : this.internalImageWidth; 3 | } 4 | -------------------------------------------------------------------------------- /src/VImageInput/computed/scaledRotatedInternalImageHeight.js: -------------------------------------------------------------------------------- 1 | export default function() { 2 | return Math.round(this.scaling * this.rotatedInternalImageHeight); 3 | } 4 | -------------------------------------------------------------------------------- /src/VImageInput/computed/scaledRotatedInternalImageHeightDifference.js: -------------------------------------------------------------------------------- 1 | export default function() { 2 | return this.imageHeight - this.scaledRotatedInternalImageHeight; 3 | } 4 | -------------------------------------------------------------------------------- /src/VImageInput/computed/scaledRotatedInternalImageWidthDifference.js: -------------------------------------------------------------------------------- 1 | export default function() { 2 | return this.imageWidth - this.scaledRotatedInternalImageWidth; 3 | } 4 | -------------------------------------------------------------------------------- /src/VImageInput/methods/onPan.js: -------------------------------------------------------------------------------- 1 | export default function(event) { 2 | this.croppingLeft += event.x - event.previousX; 3 | this.croppingTop += event.y - event.previousY; 4 | } 5 | -------------------------------------------------------------------------------- /src/VImageInput/watch/value.js: -------------------------------------------------------------------------------- 1 | export default { 2 | handler(value) { 3 | if (value) { 4 | this.load(value); 5 | } else { 6 | this.clear(); 7 | } 8 | }, 9 | immediate: true, 10 | }; 11 | -------------------------------------------------------------------------------- /src/VueFileUpload/methods/onChange.js: -------------------------------------------------------------------------------- 1 | export default function(event) { 2 | event.preventDefault(); 3 | let {files} = event.target; 4 | if (files) { 5 | if (files.length) { 6 | this.load(files[0]); 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/core/Object/mapValues.js: -------------------------------------------------------------------------------- 1 | export default function(object, iteratee) { 2 | let result = {}; 3 | Object.entries(object).forEach(([key, value]) => { 4 | result[key] = iteratee(value, key, object); 5 | }); 6 | return result; 7 | } 8 | -------------------------------------------------------------------------------- /src/core/Function/cast.js: -------------------------------------------------------------------------------- 1 | import Function_constant from './constant'; 2 | import Function_isFunction from './isFunction'; 3 | 4 | export default function(value) { 5 | return Function_isFunction(value) ? value : Function_constant(value); 6 | } 7 | -------------------------------------------------------------------------------- /src/VImageInput/watch/index.js: -------------------------------------------------------------------------------- 1 | import imageData from './imageData'; 2 | import updateImageData from './updateImageData'; 3 | import value from './value'; 4 | 5 | export default { 6 | imageData, 7 | updateImageData, 8 | value, 9 | }; 10 | -------------------------------------------------------------------------------- /src/VueFileUpload/methods/onDrop.js: -------------------------------------------------------------------------------- 1 | export default function(event) { 2 | event.preventDefault(); 3 | this.dragging = false; 4 | let {files} = event.dataTransfer; 5 | if (files) { 6 | if (files.length) { 7 | this.load(files[0]); 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/VImageInput/methods/flipVertically.js: -------------------------------------------------------------------------------- 1 | export default function() { 2 | if (this.rotated) { 3 | this.flippedHorizontally = !this.flippedHorizontally; 4 | } else { 5 | this.flippedVertically = !this.flippedVertically; 6 | } 7 | this.dirtyOriginTop = 1 - this.dirtyOriginTop; 8 | } 9 | -------------------------------------------------------------------------------- /src/VImageInput/methods/flipHorizontally.js: -------------------------------------------------------------------------------- 1 | export default function() { 2 | if (this.rotated) { 3 | this.flippedVertically = !this.flippedVertically; 4 | } else { 5 | this.flippedHorizontally = !this.flippedHorizontally; 6 | } 7 | this.dirtyOriginLeft = 1 - this.dirtyOriginLeft; 8 | } 9 | -------------------------------------------------------------------------------- /src/VueFileUpload/methods/onClick.js: -------------------------------------------------------------------------------- 1 | export default function(event) { 2 | event.preventDefault(); 3 | let input = document.createElement('input'); 4 | input.setAttribute('type', 'file'); 5 | let {onChange} = this; 6 | input.addEventListener('change', onChange); 7 | input.click(); 8 | } 9 | -------------------------------------------------------------------------------- /src/VueFileUpload/index.js: -------------------------------------------------------------------------------- 1 | import computed from './computed'; 2 | import data from './data'; 3 | import methods from './methods'; 4 | import props from './props'; 5 | import render from './render'; 6 | 7 | export default { 8 | props, 9 | data, 10 | computed, 11 | methods, 12 | render, 13 | }; 14 | -------------------------------------------------------------------------------- /src/VImageInput/components/ClearButton/render.js: -------------------------------------------------------------------------------- 1 | export default function(h, {parent}) { 2 | return h( 3 | 'MyActionButton', 4 | { 5 | props: { 6 | icon: parent.clearIcon, 7 | iconStyle: parent.clearIconStyle, 8 | }, 9 | on: { 10 | click: parent.clear, 11 | }, 12 | }, 13 | ); 14 | } 15 | -------------------------------------------------------------------------------- /src/VImageInput/computed/scaling.js: -------------------------------------------------------------------------------- 1 | import Math_clamp from '../../core/Math/clamp'; 2 | 3 | export default { 4 | get() { 5 | return Math_clamp( 6 | this.dirtyScaling, 7 | this.computedMinScaling, 8 | this.computedMaxScaling, 9 | ); 10 | }, 11 | 12 | set(value) { 13 | this.dirtyScaling = value; 14 | }, 15 | }; 16 | -------------------------------------------------------------------------------- /src/VueFileUpload/data.js: -------------------------------------------------------------------------------- 1 | import Function_noop from '../core/Function/noop'; 2 | 3 | export default function() { 4 | return { 5 | cancel: Function_noop, 6 | dragging: false, 7 | error: null, 8 | failed: false, 9 | file: null, 10 | loaded: false, 11 | loading: false, 12 | progress: 0, 13 | result: null, 14 | }; 15 | } 16 | -------------------------------------------------------------------------------- /src/VImageInput/components/FlipVerticallyButton/render.js: -------------------------------------------------------------------------------- 1 | export default function(h, {parent}) { 2 | return h( 3 | 'MyActionButton', 4 | { 5 | props: { 6 | icon: parent.flipVerticallyIcon, 7 | iconStyle: parent.flipVerticallyIconStyle, 8 | }, 9 | on: { 10 | click: parent.flipVertically, 11 | }, 12 | }, 13 | ); 14 | } 15 | -------------------------------------------------------------------------------- /src/VImageInput/components/RotateClockwiseButton/render.js: -------------------------------------------------------------------------------- 1 | export default function(h, {parent}) { 2 | return h( 3 | 'MyActionButton', 4 | { 5 | props: { 6 | icon: parent.rotateClockwiseIcon, 7 | iconStyle: parent.rotateClockwiseIconStyle, 8 | }, 9 | on: { 10 | click: parent.rotateClockwise, 11 | }, 12 | }, 13 | ); 14 | } 15 | -------------------------------------------------------------------------------- /src/VImageInput/components/FlipHorizontallyButton/render.js: -------------------------------------------------------------------------------- 1 | export default function(h, {parent}) { 2 | return h( 3 | 'MyActionButton', 4 | { 5 | props: { 6 | icon: parent.flipHorizontallyIcon, 7 | iconStyle: parent.flipHorizontallyIconStyle, 8 | }, 9 | on: { 10 | click: parent.flipHorizontally, 11 | }, 12 | }, 13 | ); 14 | } 15 | -------------------------------------------------------------------------------- /src/VImageInput/components/RotateCounterClockwiseButton/render.js: -------------------------------------------------------------------------------- 1 | export default function(h, {parent}) { 2 | return h( 3 | 'MyActionButton', 4 | { 5 | props: { 6 | icon: parent.rotateCounterClockwiseIcon, 7 | iconStyle: parent.rotateCounterClockwiseIconStyle, 8 | }, 9 | on: { 10 | click: parent.rotateCounterClockwise, 11 | }, 12 | }, 13 | ); 14 | } 15 | -------------------------------------------------------------------------------- /src/VImageInput/data.js: -------------------------------------------------------------------------------- 1 | export default function() { 2 | return { 3 | dirtyOriginLeft: 1/2, 4 | dirtyOriginTop: 1/2, 5 | dirtyScaling: 0, 6 | fileInfo: null, 7 | flippedHorizontally: false, 8 | flippedVertically: false, 9 | imageData: null, 10 | internalImageData: null, 11 | internalImageHeight: 0, 12 | internalImageWidth: 0, 13 | rotated: false, 14 | }; 15 | } 16 | -------------------------------------------------------------------------------- /src/VImageInput/computed/checkeredBackground.js: -------------------------------------------------------------------------------- 1 | export default function() { 2 | //let {theme} = this; 3 | return [ 4 | `url('data:image/svg+xml;base64,${btoa('')}')`, 5 | 'center center / 16px 16px', 6 | 'repeat', 7 | '#fff', 8 | ].join(' '); 9 | } 10 | -------------------------------------------------------------------------------- /src/VueFileUpload/computed/on.js: -------------------------------------------------------------------------------- 1 | export default function() { 2 | let {disabled} = this; 3 | if (disabled) { 4 | return {}; 5 | } 6 | let { 7 | onClick, 8 | onDragEnter, 9 | onDragLeave, 10 | onDragOver, 11 | onDrop, 12 | } = this; 13 | return { 14 | click: onClick, 15 | dragenter: onDragEnter, 16 | dragleave: onDragLeave, 17 | dragover: onDragOver, 18 | drop: onDrop, 19 | }; 20 | } 21 | -------------------------------------------------------------------------------- /a-la-carte.js: -------------------------------------------------------------------------------- 1 | import VImageInput from './index'; 2 | 3 | import { 4 | VBtn, 5 | VCard, 6 | VIcon, 7 | VProgressCircular, 8 | VSlider, 9 | VSpacer, 10 | } from 'vuetify/lib'; 11 | 12 | let {name} = VImageInput; 13 | 14 | export default { 15 | name, 16 | components: { 17 | VBtn, 18 | VCard, 19 | VIcon, 20 | VProgressCircular, 21 | VSlider, 22 | VSpacer, 23 | }, 24 | extends: VImageInput, 25 | }; 26 | -------------------------------------------------------------------------------- /src/VImageInput/methods/rotateClockwise.js: -------------------------------------------------------------------------------- 1 | export default function() { 2 | if (this.rotated) { 3 | this.rotated = false; 4 | this.flippedHorizontally = !this.flippedHorizontally; 5 | this.flippedVertically = !this.flippedVertically; 6 | } else { 7 | this.rotated = true; 8 | } 9 | let {dirtyOriginTop} = this; 10 | this.dirtyOriginTop = this.dirtyOriginLeft; 11 | this.dirtyOriginLeft = 1 - dirtyOriginTop; 12 | } 13 | -------------------------------------------------------------------------------- /src/VImageInput/methods/rotateCounterClockwise.js: -------------------------------------------------------------------------------- 1 | export default function() { 2 | if (this.rotated) { 3 | this.rotated = false; 4 | } else { 5 | this.rotated = true; 6 | this.flippedHorizontally = !this.flippedHorizontally; 7 | this.flippedVertically = !this.flippedVertically; 8 | } 9 | let {dirtyOriginLeft} = this; 10 | this.dirtyOriginLeft = this.dirtyOriginTop; 11 | this.dirtyOriginTop = 1 - dirtyOriginLeft; 12 | } 13 | -------------------------------------------------------------------------------- /src/VImageInput/computed/computedMinScaling.js: -------------------------------------------------------------------------------- 1 | export default function() { 2 | switch (this.imageMinScaling) { 3 | case 'cover': 4 | return Math.max( 5 | this.relativeRotatedInternalImageWidth, 6 | this.relativeRotatedInternalImageHeight, 7 | ); 8 | case 'contain': 9 | return Math.min( 10 | this.relativeRotatedInternalImageWidth, 11 | this.relativeRotatedInternalImageHeight, 12 | ); 13 | } 14 | return 0; 15 | } 16 | -------------------------------------------------------------------------------- /src/VueFileUpload/render.js: -------------------------------------------------------------------------------- 1 | export default function() { 2 | let { 3 | $scopedSlots, 4 | cancel, 5 | dragging, 6 | error, 7 | failed, 8 | file, 9 | loaded, 10 | loading, 11 | on, 12 | progress, 13 | result, 14 | } = this; 15 | return $scopedSlots.default({ 16 | cancel, 17 | dragging, 18 | error, 19 | failed, 20 | file, 21 | loaded, 22 | loading, 23 | on, 24 | progress, 25 | result, 26 | }); 27 | } 28 | -------------------------------------------------------------------------------- /src/VueFileUpload/methods/index.js: -------------------------------------------------------------------------------- 1 | import load from './load'; 2 | import onChange from './onChange'; 3 | import onClick from './onClick'; 4 | import onDragEnter from './onDragEnter'; 5 | import onDragLeave from './onDragLeave'; 6 | import onDragOver from './onDragOver'; 7 | import onDrop from './onDrop'; 8 | 9 | export default { 10 | load, 11 | onChange, 12 | onClick, 13 | onDragEnter, 14 | onDragLeave, 15 | onDragOver, 16 | onDrop, 17 | }; 18 | -------------------------------------------------------------------------------- /src/VImageInput/components/ActionButton/render.js: -------------------------------------------------------------------------------- 1 | export default function(h, { 2 | listeners, 3 | parent, 4 | props, 5 | }) { 6 | return h( 7 | 'VBtn', 8 | { 9 | class: 'ma-1', 10 | props: { 11 | disabled: parent.disabled, 12 | flat: true, 13 | icon: true, 14 | }, 15 | on: listeners, 16 | }, 17 | [h( 18 | 'VIcon', 19 | { 20 | style: props.iconStyle, 21 | }, 22 | props.icon, 23 | )], 24 | ); 25 | } 26 | -------------------------------------------------------------------------------- /src/VImageInput/components/ScalingSlider/render.js: -------------------------------------------------------------------------------- 1 | export default function(h, {parent}) { 2 | let { 3 | computedMaxScaling, 4 | computedMinScaling, 5 | disabled, 6 | scaleTo, 7 | scaling, 8 | } = parent; 9 | return h( 10 | 'VSlider', 11 | { 12 | class: 'ma-1', 13 | props: { 14 | disabled, 15 | hideDetails: true, 16 | max: computedMaxScaling, 17 | min: computedMinScaling, 18 | step: 1 / 1000, 19 | value: scaling, 20 | }, 21 | on: { 22 | input: scaleTo, 23 | }, 24 | }, 25 | ); 26 | } 27 | -------------------------------------------------------------------------------- /src/VImageInput/index.js: -------------------------------------------------------------------------------- 1 | import components from './components'; 2 | import computed from './computed'; 3 | import created from './created'; 4 | import data from './data'; 5 | import directives from './directives'; 6 | import methods from './methods'; 7 | import name from './name'; 8 | import props from './props'; 9 | import render from './render'; 10 | import watch from './watch'; 11 | 12 | export default { 13 | name, 14 | components, 15 | directives, 16 | props, 17 | data, 18 | computed, 19 | watch, 20 | created, 21 | methods, 22 | render, 23 | }; 24 | -------------------------------------------------------------------------------- /src/VImageInput/methods/index.js: -------------------------------------------------------------------------------- 1 | import clear from './clear'; 2 | import flipHorizontally from './flipHorizontally'; 3 | import flipVertically from './flipVertically'; 4 | import load from './load'; 5 | import onPan from './onPan'; 6 | import rotateClockwise from './rotateClockwise'; 7 | import rotateCounterClockwise from './rotateCounterClockwise'; 8 | import scaleTo from './scaleTo'; 9 | 10 | export default { 11 | clear, 12 | flipHorizontally, 13 | flipVertically, 14 | load, 15 | onPan, 16 | rotateClockwise, 17 | rotateCounterClockwise, 18 | scaleTo, 19 | }; 20 | -------------------------------------------------------------------------------- /src/VImageInput/computed/croppingLeft.js: -------------------------------------------------------------------------------- 1 | import Math_clamp from '../../core/Math/clamp'; 2 | 3 | export default { 4 | get() { 5 | return Math.round(Math_clamp( 6 | this.scaledRotatedInternalImageWidthDifference * this.dirtyOriginLeft, 7 | this.computedMinCroppingLeft, 8 | this.computedMaxCroppingLeft, 9 | )); 10 | }, 11 | 12 | set(value) { 13 | let {scaledRotatedInternalImageWidthDifference} = this; 14 | this.dirtyOriginLeft = scaledRotatedInternalImageWidthDifference 15 | ? value / scaledRotatedInternalImageWidthDifference 16 | : 1/2; 17 | }, 18 | }; 19 | -------------------------------------------------------------------------------- /src/VImageInput/computed/croppingTop.js: -------------------------------------------------------------------------------- 1 | import Math_clamp from '../../core/Math/clamp'; 2 | 3 | export default { 4 | get() { 5 | return Math.round(Math_clamp( 6 | this.scaledRotatedInternalImageHeightDifference * this.dirtyOriginTop, 7 | this.computedMinCroppingTop, 8 | this.computedMaxCroppingTop, 9 | )); 10 | }, 11 | 12 | set(value) { 13 | let {scaledRotatedInternalImageHeightDifference} = this; 14 | this.dirtyOriginTop = scaledRotatedInternalImageHeightDifference 15 | ? value / scaledRotatedInternalImageHeightDifference 16 | : 1/2; 17 | }, 18 | }; 19 | -------------------------------------------------------------------------------- /src/VImageInput/methods/load.js: -------------------------------------------------------------------------------- 1 | export default function(data) { 2 | if (this.imageData !== data) { 3 | let image = new Image(); 4 | image.addEventListener('load', () => { 5 | let width = image.naturalWidth; 6 | let height = image.naturalHeight; 7 | this.internalImageData = (width && height) ? data : null; 8 | this.internalImageWidth = width; 9 | this.internalImageHeight = height; 10 | this.flippedHorizontally = false; 11 | this.flippedVertically = false; 12 | this.rotated = false; 13 | this.scaling = 0; 14 | this.dirtyOriginLeft = 1/2; 15 | this.dirtyOriginTop = 1/2; 16 | }); 17 | image.src = data; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/VImageInput/components/index.js: -------------------------------------------------------------------------------- 1 | import MyActionButton from './ActionButton'; 2 | import MyClearButton from './ClearButton'; 3 | import MyFlipHorizontallyButton from './FlipHorizontallyButton'; 4 | import MyFlipVerticallyButton from './FlipVerticallyButton'; 5 | import MyRotateClockwiseButton from './RotateClockwiseButton'; 6 | import MyRotateCounterClockwiseButton from './RotateCounterClockwiseButton'; 7 | import MyScalingSlider from './ScalingSlider'; 8 | import MyFileUpload from './FileUpload'; 9 | 10 | export default { 11 | MyActionButton, 12 | MyClearButton, 13 | MyFileUpload, 14 | MyFlipHorizontallyButton, 15 | MyFlipVerticallyButton, 16 | MyRotateClockwiseButton, 17 | MyRotateCounterClockwiseButton, 18 | MyScalingSlider, 19 | }; 20 | -------------------------------------------------------------------------------- /src/VImageInput/created.js: -------------------------------------------------------------------------------- 1 | import componentName from './name'; 2 | 3 | export default function() { 4 | let { 5 | $props, 6 | constructor, 7 | } = this; 8 | let {warn} = constructor.super.util; 9 | Object.entries({ 10 | imageBackgroundColor: 'backgroundColor', 11 | imageMaxScaling: 'maxScaling', 12 | imageMinScaling: 'minScaling', 13 | rotateCounterClockwiseIcon: 'rotateCounterclockwiseIcon', 14 | rotateCounterClockwiseIconStyle: 'rotateCounterclockwiseIconStyle', 15 | }).forEach(([newPropName, oldPropName]) => { 16 | if ($props[oldPropName] !== undefined) { 17 | warn(`[${componentName}]: The property '${oldPropName}' has been renamed to '${newPropName}'.`); 18 | } 19 | }); 20 | [ 21 | 'scalingSliderColor', 22 | ].forEach(propName => { 23 | if ($props[propName] !== undefined) { 24 | warn(`[${componentName}]: The property '${propName}' has been removed.`); 25 | } 26 | }); 27 | } 28 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import {babel} from '@rollup/plugin-babel'; 2 | import commonjs from '@rollup/plugin-commonjs'; 3 | import {nodeResolve} from '@rollup/plugin-node-resolve'; 4 | import serve from 'rollup-plugin-serve'; 5 | import {terser} from 'rollup-plugin-terser'; 6 | 7 | import {main} from './package.json'; 8 | 9 | let plugins = [ 10 | nodeResolve(), 11 | commonjs({ 12 | ignoreGlobal: true, 13 | requireReturnsDefault: true, 14 | }), 15 | babel({ 16 | babelHelpers: 'bundled', 17 | presets: [['@babel/preset-env', { 18 | targets: 'defaults and not IE 11', 19 | }]], 20 | }), 21 | terser(), 22 | ]; 23 | 24 | if (process.env.ROLLUP_WATCH) { 25 | plugins.push(serve({ 26 | contentBase: '', 27 | open: true, 28 | })); 29 | } 30 | 31 | export default { 32 | input: 'src/index.js', 33 | plugins, 34 | output: { 35 | file: main, 36 | format: 'umd', 37 | name: 'VuetifyImageInput', 38 | }, 39 | }; 40 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vuetify-image-input", 3 | "version": "19.2.2", 4 | "description": "Provides basic image editing tools.", 5 | "keywords": [ 6 | "component", 7 | "image", 8 | "input", 9 | "vue", 10 | "vuetify" 11 | ], 12 | "license": "MIT", 13 | "author": "Sergej Sintschilin ", 14 | "files": [ 15 | "a-la-carte.js" 16 | ], 17 | "main": "index.js", 18 | "repository": "github:SeregPie/VuetifyImageInput", 19 | "scripts": { 20 | "build": "rollup -c", 21 | "prepublishOnly": "npm run build", 22 | "serve": "rollup -c -w" 23 | }, 24 | "devDependencies": { 25 | "@babel/core": "^7.13.14", 26 | "@babel/preset-env": "^7.13.12", 27 | "@rollup/plugin-babel": "^5.3.0", 28 | "@rollup/plugin-commonjs": "^18.0.0", 29 | "@rollup/plugin-node-resolve": "^11.2.1", 30 | "eslint": "^7.23.0", 31 | "eslint-config-seregpie": "^1.0.2", 32 | "rollup": "^2.44.0", 33 | "rollup-plugin-serve": "^1.1.0", 34 | "rollup-plugin-terser": "^7.0.2", 35 | "vueclaw": "^1.1.0" 36 | }, 37 | "peerDependencies": { 38 | "vuetify": "^2" 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018-2021 Sergej Sintschilin 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 | -------------------------------------------------------------------------------- /demo/script.js: -------------------------------------------------------------------------------- 1 | (() => { 2 | 3 | new Vue({ 4 | el: '#App', 5 | vuetify: new Vuetify(), 6 | data() { 7 | return { 8 | clearable: VuetifyImageInput.props.clearable.default, 9 | disabled: VuetifyImageInput.props.disabled.default, 10 | 11 | fileInfo: null, 12 | fullHeight: VuetifyImageInput.props.fullHeight.default, 13 | fullWidth: VuetifyImageInput.props.fullWidth.default, 14 | hideActions: VuetifyImageInput.props.hideActions.default, 15 | image: null, 16 | imageHeight: VuetifyImageInput.props.imageHeight.default, 17 | imageMinScaling: VuetifyImageInput.props.imageMinScaling.default, 18 | imageWidth: VuetifyImageInput.props.imageWidth.default, 19 | readonly: VuetifyImageInput.props.readonly.default, 20 | ui: { 21 | drawer: true, 22 | snackbar: { 23 | fileInfo: false, 24 | }, 25 | }, 26 | }; 27 | }, 28 | computed: { 29 | dark: { 30 | get() { 31 | return this.$vuetify.theme.dark; 32 | }, 33 | set(value) { 34 | this.$vuetify.theme.dark = value; 35 | }, 36 | }, 37 | }, 38 | methods: { 39 | onFileInfo(value) { 40 | this.fileInfo = value; 41 | this.ui.snackbar.fileInfo = true; 42 | }, 43 | }, 44 | }); 45 | 46 | })(); 47 | -------------------------------------------------------------------------------- /src/VueFileUpload/methods/load.js: -------------------------------------------------------------------------------- 1 | import Function_noop from '../../core/Function/noop'; 2 | import Promise_try from '../../core/Promise/try'; 3 | 4 | export default function(file) { 5 | Promise_try(() => { 6 | this.cancel(); 7 | let reader = new FileReader(); 8 | let promise = new Promise((resolve, reject) => { 9 | reader.addEventListener('progress', event => { 10 | let {loaded} = event; 11 | this.progress = loaded; 12 | this.$emit('progress', { 13 | file, 14 | loaded, 15 | }); 16 | }); 17 | reader.addEventListener('load', () => { 18 | let {result} = reader; 19 | Object.assign(this, { 20 | loaded: true, 21 | result, 22 | }); 23 | this.$emit('load', { 24 | file, 25 | result, 26 | }); 27 | resolve(); 28 | }); 29 | reader.addEventListener('abort', () => { 30 | this.$emit('cancel', {file}); 31 | resolve(); 32 | }); 33 | reader.addEventListener('error', reject); 34 | }); 35 | let cancel = (() => { 36 | if (reader.readyState === 1) { 37 | reader.abort(); 38 | } 39 | }); 40 | reader.readAsDataURL(file); 41 | Object.assign(this, { 42 | cancel, 43 | error: null, 44 | failed: false, 45 | file, 46 | loaded: false, 47 | loading: true, 48 | progress: 0, 49 | result: null, 50 | }); 51 | return promise; 52 | }).catch(error => { 53 | Object.assign(this, { 54 | error, 55 | failed: true, 56 | }); 57 | this.$emit('error', { 58 | error, 59 | file, 60 | }); 61 | }).finally(() => { 62 | Object.assign(this, { 63 | cancel: Function_noop, 64 | loading: false, 65 | }); 66 | }); 67 | } 68 | -------------------------------------------------------------------------------- /src/VImageInput/computed/updateImageData.js: -------------------------------------------------------------------------------- 1 | export default function() { 2 | let { 3 | croppingLeft, 4 | croppingTop, 5 | debounce, 6 | flippedHorizontally, 7 | flippedVertically, 8 | imageBackgroundColor, 9 | imageFormat, 10 | imageHeight, 11 | imageQuality, 12 | imageWidth, 13 | internalImageData, 14 | internalImageHeight, 15 | internalImageWidth, 16 | rotated, 17 | scaling, 18 | } = this; 19 | return setTimeout(() => { 20 | let imageData; 21 | if (internalImageData) { 22 | let internalImage = new Image(); 23 | internalImage.src = internalImageData; 24 | let canvas = document.createElement('canvas'); 25 | let context = canvas.getContext('2d'); 26 | canvas.width = imageWidth; 27 | canvas.height = imageHeight; 28 | if (imageBackgroundColor) { 29 | context.fillStyle = imageBackgroundColor; 30 | context.fillRect(0, 0, imageWidth, imageHeight); 31 | } 32 | context.translate(croppingLeft, croppingTop); 33 | context.scale(scaling, scaling); 34 | if (rotated) { 35 | context.translate(internalImageHeight, 0); 36 | context.rotate(Math.PI / 2); 37 | } 38 | if (flippedHorizontally) { 39 | context.translate(internalImageWidth, 0); 40 | context.scale(-1, 1); 41 | } 42 | if (flippedVertically) { 43 | context.translate(0, internalImageHeight); 44 | context.scale(1, -1); 45 | } 46 | context.drawImage(internalImage, 0, 0); 47 | if (flippedHorizontally) { 48 | context.translate(imageWidth, 0); 49 | context.scale(-1, 1); 50 | } 51 | if (flippedVertically) { 52 | context.translate(0, imageHeight); 53 | context.scale(1, -1); 54 | } 55 | imageData = canvas.toDataURL(`image/${imageFormat}`, imageQuality); 56 | } else { 57 | imageData = null; 58 | } 59 | this.imageData = imageData; 60 | }, debounce); 61 | } 62 | -------------------------------------------------------------------------------- /src/VImageInput/computed/index.js: -------------------------------------------------------------------------------- 1 | import checkeredBackground from './checkeredBackground'; 2 | import computedMaxCroppingLeft from './computedMaxCroppingLeft'; 3 | import computedMaxCroppingTop from './computedMaxCroppingTop'; 4 | import computedMaxScaling from './computedMaxScaling'; 5 | import computedMinCroppingLeft from './computedMinCroppingLeft'; 6 | import computedMinCroppingTop from './computedMinCroppingTop'; 7 | import computedMinScaling from './computedMinScaling'; 8 | import croppingLeft from './croppingLeft'; 9 | import croppingTop from './croppingTop'; 10 | import relativeRotatedInternalImageHeight from './relativeRotatedInternalImageHeight'; 11 | import relativeRotatedInternalImageWidth from './relativeRotatedInternalImageWidth'; 12 | import rotatedInternalImageHeight from './rotatedInternalImageHeight'; 13 | import rotatedInternalImageWidth from './rotatedInternalImageWidth'; 14 | import scaledRotatedInternalImageHeight from './scaledRotatedInternalImageHeight'; 15 | import scaledRotatedInternalImageHeightDifference from './scaledRotatedInternalImageHeightDifference'; 16 | import scaledRotatedInternalImageWidth from './scaledRotatedInternalImageWidth'; 17 | import scaledRotatedInternalImageWidthDifference from './scaledRotatedInternalImageWidthDifference'; 18 | import scaling from './scaling'; 19 | import updateImageData from './updateImageData'; 20 | 21 | export default { 22 | checkeredBackground, 23 | computedMaxCroppingLeft, 24 | computedMaxCroppingTop, 25 | computedMaxScaling, 26 | computedMinCroppingLeft, 27 | computedMinCroppingTop, 28 | computedMinScaling, 29 | croppingLeft, 30 | croppingTop, 31 | relativeRotatedInternalImageHeight, 32 | relativeRotatedInternalImageWidth, 33 | rotatedInternalImageHeight, 34 | rotatedInternalImageWidth, 35 | scaledRotatedInternalImageHeight, 36 | scaledRotatedInternalImageHeightDifference, 37 | scaledRotatedInternalImageWidth, 38 | scaledRotatedInternalImageWidthDifference, 39 | scaling, 40 | updateImageData, 41 | }; 42 | -------------------------------------------------------------------------------- /src/VImageInput/components/FileUpload/render.js: -------------------------------------------------------------------------------- 1 | import VueFileUpload from '../../../VueFileUpload'; 2 | 3 | export default function(h, { 4 | data, 5 | listeners, 6 | parent, 7 | }) { 8 | let { 9 | disabled, 10 | errorIcon, 11 | errorIconStyle, 12 | successIcon, 13 | successIconStyle, 14 | uploadIcon, 15 | uploadIconStyle, 16 | } = parent; 17 | let {load} = listeners; 18 | let {style} = data; 19 | return h( 20 | VueFileUpload, 21 | { 22 | style, 23 | props: { 24 | disabled, 25 | }, 26 | scopedSlots: { 27 | default: (({ 28 | dragging, 29 | failed, 30 | file, 31 | loaded, 32 | loading, 33 | on, 34 | progress, 35 | }) => { 36 | return h( 37 | 'VCard', 38 | { 39 | style: { 40 | alignItems: 'center', 41 | display: 'flex', 42 | height: '100%', 43 | justifyContent: 'center', 44 | width: '100%', 45 | }, 46 | props: { 47 | disabled, 48 | outlined: true, 49 | }, 50 | on, 51 | }, 52 | [(() => { 53 | if (loading) { 54 | let indeterminate; 55 | let value = progress / file.size * 100; 56 | let text; 57 | if (value) { 58 | text = `${Math.round(value)}%`; 59 | } else { 60 | indeterminate = true; 61 | } 62 | return h( 63 | 'VProgressCircular', 64 | { 65 | props: { 66 | color: 'primary', 67 | indeterminate, 68 | rotate: -90, 69 | size: 64, 70 | value, 71 | width: 4, 72 | }, 73 | }, 74 | text, 75 | ); 76 | } 77 | let style; 78 | let color; 79 | let text; 80 | if (loaded) { 81 | style = successIconStyle; 82 | color = 'success'; 83 | text = successIcon; 84 | } else 85 | if (failed) { 86 | style = errorIconStyle; 87 | color = 'error'; 88 | text = errorIcon; 89 | } else { 90 | style = uploadIconStyle; 91 | if (dragging) { 92 | color = 'primary'; 93 | } 94 | text = uploadIcon; 95 | } 96 | return h( 97 | 'VIcon', 98 | { 99 | style, 100 | props: { 101 | color, 102 | disabled, 103 | large: true, 104 | }, 105 | }, 106 | text, 107 | ); 108 | })()], 109 | ); 110 | }), 111 | }, 112 | on: { 113 | load, 114 | }, 115 | }, 116 | ); 117 | } 118 | -------------------------------------------------------------------------------- /src/VImageInput/props/index.js: -------------------------------------------------------------------------------- 1 | import clearable from './clearable'; 2 | import clearIcon from './clearIcon'; 3 | import clearIconStyle from './clearIconStyle'; 4 | import debounce from './debounce'; 5 | import disabled from './disabled'; 6 | import errorIcon from './errorIcon'; 7 | import errorIconStyle from './errorIconStyle'; 8 | import flipHorizontallyIcon from './flipHorizontallyIcon'; 9 | import flipHorizontallyIconStyle from './flipHorizontallyIconStyle'; 10 | import flipVerticallyIcon from './flipVerticallyIcon'; 11 | import flipVerticallyIconStyle from './flipVerticallyIconStyle'; 12 | import fullHeight from './fullHeight'; 13 | import fullWidth from './fullWidth'; 14 | import hideActions from './hideActions'; 15 | import imageBackgroundColor from './imageBackgroundColor'; 16 | import imageFormat from './imageFormat'; 17 | import imageHeight from './imageHeight'; 18 | import imageMaxScaling from './imageMaxScaling'; 19 | import imageMinScaling from './imageMinScaling'; 20 | import imageQuality from './imageQuality'; 21 | import imageWidth from './imageWidth'; 22 | import name from './name'; 23 | import overlayBackgroundColor from './overlayBackgroundColor'; 24 | import overlayBorderColor from './overlayBorderColor'; 25 | import overlayBorderWidth from './overlayBorderWidth'; 26 | import overlayPadding from './overlayPadding'; 27 | import readonly from './readonly'; 28 | import rotateClockwiseIcon from './rotateClockwiseIcon'; 29 | import rotateClockwiseIconStyle from './rotateClockwiseIconStyle'; 30 | import rotateCounterClockwiseIcon from './rotateCounterClockwiseIcon'; 31 | import rotateCounterClockwiseIconStyle from './rotateCounterClockwiseIconStyle'; 32 | import successIcon from './successIcon'; 33 | import successIconStyle from './successIconStyle'; 34 | import uploadIcon from './uploadIcon'; 35 | import uploadIconStyle from './uploadIconStyle'; 36 | import value from './value'; 37 | 38 | export default { 39 | clearable, 40 | clearIcon, 41 | clearIconStyle, 42 | debounce, 43 | disabled, 44 | errorIcon, 45 | errorIconStyle, 46 | flipHorizontallyIcon, 47 | flipHorizontallyIconStyle, 48 | flipVerticallyIcon, 49 | flipVerticallyIconStyle, 50 | fullHeight, 51 | fullWidth, 52 | hideActions, 53 | imageBackgroundColor, 54 | imageFormat, 55 | imageHeight, 56 | imageMaxScaling, 57 | imageMinScaling, 58 | imageQuality, 59 | imageWidth, 60 | name, 61 | overlayBackgroundColor, 62 | overlayBorderColor, 63 | overlayBorderWidth, 64 | overlayPadding, 65 | readonly, 66 | rotateClockwiseIcon, 67 | rotateClockwiseIconStyle, 68 | rotateCounterClockwiseIcon, 69 | rotateCounterClockwiseIconStyle, 70 | successIcon, 71 | successIconStyle, 72 | uploadIcon, 73 | uploadIconStyle, 74 | value, 75 | 76 | backgroundColor: {}, 77 | maxScaling: {}, 78 | minScaling: {}, 79 | rotateCounterclockwiseIcon: {}, 80 | rotateCounterclockwiseIconStyle: {}, 81 | scalingSliderColor: {}, 82 | }; 83 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # VuetifyImageInput 2 | 3 | Provides basic image editing tools. 4 | 5 | ## demo 6 | 7 | [Try it out!](https://seregpie.github.io/VuetifyImageInput/) 8 | 9 | ## dependencies 10 | 11 | - [VueClaw](https://github.com/SeregPie/VueClaw) 12 | 13 | ## setup 14 | 15 | ### npm 16 | 17 | ```shell 18 | npm i vuetify-image-input 19 | ``` 20 | 21 | --- 22 | 23 | ```javascript 24 | import VuetifyImageInput from 'vuetify-image-input'; 25 | ``` 26 | 27 | *or* 28 | 29 | Use the [treeshaking](https://vuetifyjs.com/features/treeshaking) system. 30 | 31 | ```javascript 32 | import VuetifyImageInput from 'vuetify-image-input/a-la-carte'; 33 | ``` 34 | 35 | ### browser 36 | 37 | ```html 38 | 42 | 43 | 44 | 45 | ``` 46 | 47 | The component is globally available as `VuetifyImageInput`. If Vue is detected, the component is registered automatically. 48 | 49 | ## usage 50 | 51 | Register the component globally. 52 | 53 | ```javascript 54 | import Vue from 'vue'; 55 | import VImageInput from 'vuetify-image-input'; 56 | 57 | Vue.component(VImageInput.name, VImageInput); 58 | ``` 59 | 60 | *or* 61 | 62 | Register the component locally. 63 | 64 | ```javascript 65 | import VImageInput from 'vuetify-image-input'; 66 | 67 | export default { 68 | components: { 69 | VImageInput, 70 | }, 71 | // ... 72 | }; 73 | ``` 74 | 75 | --- 76 | 77 | Use the component inside a template. 78 | 79 | ```html 80 | 87 | ``` 88 | 89 | ## properties 90 | 91 | | name | type | default | description | 92 | | ---: | :--- | :--- | :--- | 93 | | `clearable` | `Boolean` | `false` | | 94 | | `clearIcon` | `String` | `'$clear'` | | 95 | | `clearIconStyle` | `Object` | | | 96 | | `debounce` | `Number` | `0` | | 97 | | `disabled` | `Boolean` | `false` | | 98 | | `errorIcon` | `String` | `'$error'` | | 99 | | `errorIconStyle` | `Object` | | | 100 | | `flipHorizontallyIcon` | `String` | `'mdi-flip-horizontal'` | | 101 | | `flipHorizontallyIconStyle` | `Object` | | | 102 | | `flipVerticallyIcon` | `String` | `'mdi-flip-vertical'` | | 103 | | `flipVerticallyIconStyle` | `Object` | | | 104 | | `fullHeight` | `Boolean` | `false` | | 105 | | `fullWidth` | `Boolean` | `false` | | 106 | | `hideActions` | `Boolean` | `false` | | 107 | | `imageBackgroundColor` | `String` | | | 108 | | `imageFormat` | `String` | `'png'` | Possible values are `'png'`, `'jpeg'` and `'webp'`. | 109 | | `imageHeight` | `Number` | `256` | | 110 | | `imageMaxScaling` | `Number` | `1` | | 111 | | `imageMinScaling` | `String` | `'cover'` | Possible values are `'cover'` and `'contain'`. | 112 | | `imageQuality` | | | | 113 | | `imageWidth` | `Number` | `256` | | 114 | | `name` | `String` | | | 115 | | `overlayBackgroundColor` | `String` | `'rgba(0,0,0,0.5)'` | | 116 | | `overlayBorderColor` | `String` | `'#fff'` | | 117 | | `overlayBorderWidth` | `String` | `'4px'` | | 118 | | `overlayPadding` | `String` | `'50px'` | | 119 | | `readonly` | `Boolean` | `false` | | 120 | | `rotateClockwiseIcon` | `String` | `'mdi-rotate-right'` | | 121 | | `rotateClockwiseIconStyle` | `Object` | | | 122 | | `rotateCounterClockwiseIcon` | `String` | `'mdi-rotate-left'` | | 123 | | `rotateCounterClockwiseIconStyle` | `Object` | | | 124 | | `successIcon` | `String` | `'$success'` | | 125 | | `successIconStyle` | `Object` | | | 126 | | `uploadIcon` | `String` | `'mdi-upload'` | | 127 | | `uploadIconStyle` | `Object` | | | 128 | | `value` | `String` | | | 129 | 130 | ## events 131 | 132 | | name | 133 | | ---: | 134 | | `file-info` | 135 | | `input` | 136 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 9 | VuetifyImageInput 10 | 14 | 18 | 22 | 27 | 28 | 29 | 30 | 34 | 35 | 36 | 40 | 45 |
46 | 52 |
53 |
54 | 63 |
64 |
65 | 74 |
75 |
76 | 82 | 86 | 90 | 91 |
92 |
93 | 99 |
100 |
101 | 107 |
108 |
109 | 115 |
116 |
117 | 123 |
124 |
125 | 131 |
132 |
133 | 139 | 140 | VuetifyImageInput 141 | 142 | 146 | mdi-invert-colors 147 | 148 | 153 | mdi-github 154 | 155 | 156 | 157 | 161 | 175 | 176 | 177 | 181 |
{{ JSON.stringify(fileInfo, null, '  ') }}
184 |
185 |
186 | 187 | 188 | -------------------------------------------------------------------------------- /src/VImageInput/render.js: -------------------------------------------------------------------------------- 1 | export default function(h) { 2 | return h( 3 | 'div', 4 | { 5 | style: { 6 | display: ['-ms-inline-grid', 'inline-grid'], 7 | gridColumns: '1fr auto', 8 | gridRows: '1fr auto', 9 | gridTemplateColumns: '1fr auto', 10 | gridTemplateRows: '1fr auto', 11 | ...(this.fullHeight ? {height: '100%'} : {}), 12 | ...(this.fullWidth ? {width: '100%'} : {}), 13 | }, 14 | }, 15 | [ 16 | h( 17 | 'div', 18 | { 19 | style: { 20 | gridColumn: 1, 21 | gridRow: 1, 22 | overflow: 'hidden', 23 | position: 'relative', 24 | }, 25 | }, 26 | [ 27 | h( 28 | 'div', 29 | { 30 | style: { 31 | alignItems: 'center', 32 | background: this.checkeredBackground, 33 | display: 'flex', 34 | height: '100%', 35 | justifyContent: 'center', 36 | overflow: 'hidden', 37 | padding: `calc(${this.overlayPadding} + ${this.overlayBorderWidth})`, 38 | position: 'relative', 39 | width: '100%', 40 | zIndex: 0, 41 | ...(this.internalImageData || this.readonly 42 | ? {} 43 | : { 44 | opacity: 0, 45 | pointerEvents: 'none', 46 | visibility: 'hidden', 47 | } 48 | ), 49 | }, 50 | ...(this.internalImageData 51 | ? (this.disabled || this.readonly 52 | ? {} 53 | : {directives: [{ 54 | name: 'Claw', 55 | arg: 'pan', 56 | value: this.onPan, 57 | }]} 58 | ) 59 | : {} 60 | ), 61 | }, 62 | [h( 63 | 'div', 64 | { 65 | style: { 66 | boxShadow: `0 0 4000px 4000px ${this.overlayBackgroundColor}`, 67 | maxHeight: '100%', 68 | maxWidth: '100%', 69 | outline: `${this.overlayBorderWidth} solid ${this.overlayBorderColor}`, 70 | pointerEvents: 'none', 71 | }, 72 | }, 73 | [h( 74 | 'div', 75 | { 76 | style: { 77 | height: `${this.imageHeight}px`, 78 | position: 'relative', 79 | width: `${this.imageWidth}px`, 80 | zIndex: -1, 81 | ...(this.imageBackgroundColor 82 | ? {backgroundColor: this.imageBackgroundColor} 83 | : {} 84 | ), 85 | }, 86 | }, 87 | [ 88 | ...(this.internalImageData 89 | ? [h( 90 | 'img', 91 | { 92 | style: { 93 | display: 'block', 94 | pointerEvents: 'none', 95 | position: 'absolute', 96 | transform: [ 97 | `translate(${this.croppingLeft}px,${this.croppingTop}px)`, 98 | `scale(${this.scaling})`, 99 | ...(this.rotated ? [`translateX(${this.internalImageHeight}px)`, 'rotate(90deg)'] : []), 100 | ...(this.flippedVertically ? [`translateY(${this.internalImageHeight}px)`, 'scaleY(-1)'] : []), 101 | ...(this.flippedHorizontally ? [`translateX(${this.internalImageWidth}px)`, 'scaleX(-1)'] : []), 102 | ].join(' '), 103 | transformOrigin: '0 0', 104 | }, 105 | attrs: { 106 | src: this.internalImageData, 107 | }, 108 | }, 109 | )] 110 | : [] 111 | ), 112 | ...(this.internalImageData && this.imageData 113 | ? [h( 114 | 'input', 115 | { 116 | attrs: { 117 | name: this.name, 118 | type: 'hidden', 119 | value: this.imageData, 120 | }, 121 | }, 122 | )] 123 | : [] 124 | ), 125 | ], 126 | )], 127 | )], 128 | ), 129 | ...(this.internalImageData || this.readonly 130 | ? [] 131 | : [h( 132 | 'MyFileUpload', 133 | { 134 | style: { 135 | bottom: 0, 136 | left: 0, 137 | position: 'absolute', 138 | right: 0, 139 | top: 0, 140 | }, 141 | on: { 142 | load: (({ 143 | file: { 144 | lastModified, 145 | name, 146 | size, 147 | type, 148 | }, 149 | result, 150 | }) => { 151 | this.$emit('file-info', { 152 | name, 153 | size, 154 | type, 155 | lastModified, 156 | }); 157 | this.load(result); 158 | }), 159 | }, 160 | }, 161 | )] 162 | ), 163 | ], 164 | ), 165 | h( 166 | 'div', 167 | { 168 | style: { 169 | display: 'flex', 170 | flexDirection: 'column', 171 | gridColumn: 2, 172 | gridRow: 1, 173 | justifyContent: 'center', 174 | ...(this.internalImageData 175 | ? {} 176 | : { 177 | opacity: 0, 178 | pointerEvents: 'none', 179 | visibility: 'hidden', 180 | } 181 | ), 182 | }, 183 | }, 184 | [ 185 | ...(this.clearable 186 | ? [ 187 | h('MyClearButton'), 188 | h('VSpacer'), 189 | ] 190 | : [] 191 | ), 192 | ...(this.hideActions || this.readonly 193 | ? [] 194 | : [ 195 | h('MyRotateClockwiseButton'), 196 | h('MyRotateCounterClockwiseButton'), 197 | h('MyFlipHorizontallyButton'), 198 | h('MyFlipVerticallyButton'), 199 | ] 200 | ), 201 | ], 202 | ), 203 | h( 204 | 'div', 205 | { 206 | style: { 207 | gridColumn: 1, 208 | gridRow: 2, 209 | ...(this.internalImageData 210 | ? {} 211 | : { 212 | opacity: 0, 213 | pointerEvents: 'none', 214 | visibility: 'hidden', 215 | } 216 | ), 217 | }, 218 | }, 219 | (this.hideActions || this.readonly 220 | ? [] 221 | : [h('MyScalingSlider')] 222 | ), 223 | ), 224 | ], 225 | ); 226 | } 227 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | !function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):(t="undefined"!=typeof globalThis?globalThis:t||self).VuetifyImageInput=e()}(this,(function(){"use strict";function t(){}var e={props:{disabled:{type:Boolean,default:!1}},data:function(){return{cancel:t,dragging:!1,error:null,failed:!1,file:null,loaded:!1,loading:!1,progress:0,result:null}},computed:{on:function(){let{disabled:t}=this;if(t)return{};let{onClick:e,onDragEnter:i,onDragLeave:n,onDragOver:a,onDrop:r}=this;return{click:e,dragenter:i,dragleave:n,dragover:a,drop:r}}},methods:{load:function(e){var i;(i=()=>{this.cancel();let t=new FileReader,i=new Promise(((i,n)=>{t.addEventListener("progress",(t=>{let{loaded:i}=t;this.progress=i,this.$emit("progress",{file:e,loaded:i})})),t.addEventListener("load",(()=>{let{result:n}=t;Object.assign(this,{loaded:!0,result:n}),this.$emit("load",{file:e,result:n}),i()})),t.addEventListener("abort",(()=>{this.$emit("cancel",{file:e}),i()})),t.addEventListener("error",n)}));return t.readAsDataURL(e),Object.assign(this,{cancel:()=>{1===t.readyState&&t.abort()},error:null,failed:!1,file:e,loaded:!1,loading:!0,progress:0,result:null}),i},new Promise((t=>{t(i())}))).catch((t=>{Object.assign(this,{error:t,failed:!0}),this.$emit("error",{error:t,file:e})})).finally((()=>{Object.assign(this,{cancel:t,loading:!1})}))},onChange:function(t){t.preventDefault();let{files:e}=t.target;e&&e.length&&this.load(e[0])},onClick:function(t){t.preventDefault();let e=document.createElement("input");e.setAttribute("type","file");let{onChange:i}=this;e.addEventListener("change",i),e.click()},onDragEnter:function(t){t.preventDefault(),this.dragging=!0},onDragLeave:function(t){t.preventDefault(),this.dragging=!1},onDragOver:function(t){t.preventDefault()},onDrop:function(t){t.preventDefault(),this.dragging=!1;let{files:e}=t.dataTransfer;e&&e.length&&this.load(e[0])}},render:function(){let{$scopedSlots:t,cancel:e,dragging:i,error:n,failed:a,file:r,loaded:o,loading:l,on:s,progress:c,result:d}=this;return t.default({cancel:e,dragging:i,error:n,failed:a,file:r,loaded:o,loading:l,on:s,progress:c,result:d})}};var i={MyActionButton:{functional:!0,render:function(t,{listeners:e,parent:i,props:n}){return t("VBtn",{class:"ma-1",props:{disabled:i.disabled,flat:!0,icon:!0},on:e},[t("VIcon",{style:n.iconStyle},n.icon)])}},MyClearButton:{functional:!0,render:function(t,{parent:e}){return t("MyActionButton",{props:{icon:e.clearIcon,iconStyle:e.clearIconStyle},on:{click:e.clear}})}},MyFileUpload:{functional:!0,render:function(t,{data:i,listeners:n,parent:a}){let{disabled:r,errorIcon:o,errorIconStyle:l,successIcon:s,successIconStyle:c,uploadIcon:d,uploadIconStyle:u}=a,{load:h}=n,{style:p}=i;return t(e,{style:p,props:{disabled:r},scopedSlots:{default:({dragging:e,failed:i,file:n,loaded:a,loading:h,on:p,progress:g})=>t("VCard",{style:{alignItems:"center",display:"flex",height:"100%",justifyContent:"center",width:"100%"},props:{disabled:r,outlined:!0},on:p},[(()=>{if(h){let e,i,a=g/n.size*100;return a?i=`${Math.round(a)}%`:e=!0,t("VProgressCircular",{props:{color:"primary",indeterminate:e,rotate:-90,size:64,value:a,width:4}},i)}let p,f,m;return a?(p=c,f="success",m=s):i?(p=l,f="error",m=o):(p=u,e&&(f="primary"),m=d),t("VIcon",{style:p,props:{color:f,disabled:r,large:!0}},m)})()])},on:{load:h}})}},MyFlipHorizontallyButton:{functional:!0,render:function(t,{parent:e}){return t("MyActionButton",{props:{icon:e.flipHorizontallyIcon,iconStyle:e.flipHorizontallyIconStyle},on:{click:e.flipHorizontally}})}},MyFlipVerticallyButton:{functional:!0,render:function(t,{parent:e}){return t("MyActionButton",{props:{icon:e.flipVerticallyIcon,iconStyle:e.flipVerticallyIconStyle},on:{click:e.flipVertically}})}},MyRotateClockwiseButton:{functional:!0,render:function(t,{parent:e}){return t("MyActionButton",{props:{icon:e.rotateClockwiseIcon,iconStyle:e.rotateClockwiseIconStyle},on:{click:e.rotateClockwise}})}},MyRotateCounterClockwiseButton:{functional:!0,render:function(t,{parent:e}){return t("MyActionButton",{props:{icon:e.rotateCounterClockwiseIcon,iconStyle:e.rotateCounterClockwiseIconStyle},on:{click:e.rotateCounterClockwise}})}},MyScalingSlider:{functional:!0,render:function(t,{parent:e}){let{computedMaxScaling:i,computedMinScaling:n,disabled:a,scaleTo:r,scaling:o}=e;return t("VSlider",{class:"ma-1",props:{disabled:a,hideDetails:!0,max:i,min:n,step:.001,value:o},on:{input:r}})}}};function n(t,e,i){return Math.min(Math.max(t,e),i)}var a={checkeredBackground:function(){return[`url('data:image/svg+xml;base64,${btoa('')}')`,"center center / 16px 16px","repeat","#fff"].join(" ")},computedMaxCroppingLeft:function(){return Math.max(0,this.scaledRotatedInternalImageWidthDifference)},computedMaxCroppingTop:function(){return Math.max(0,this.scaledRotatedInternalImageHeightDifference)},computedMaxScaling:function(){return Math.max(this.imageMaxScaling,this.computedMinScaling)},computedMinCroppingLeft:function(){return Math.min(0,this.scaledRotatedInternalImageWidthDifference)},computedMinCroppingTop:function(){return Math.min(0,this.scaledRotatedInternalImageHeightDifference)},computedMinScaling:function(){switch(this.imageMinScaling){case"cover":return Math.max(this.relativeRotatedInternalImageWidth,this.relativeRotatedInternalImageHeight);case"contain":return Math.min(this.relativeRotatedInternalImageWidth,this.relativeRotatedInternalImageHeight)}return 0},croppingLeft:{get(){return Math.round(n(this.scaledRotatedInternalImageWidthDifference*this.dirtyOriginLeft,this.computedMinCroppingLeft,this.computedMaxCroppingLeft))},set(t){let{scaledRotatedInternalImageWidthDifference:e}=this;this.dirtyOriginLeft=e?t/e:.5}},croppingTop:{get(){return Math.round(n(this.scaledRotatedInternalImageHeightDifference*this.dirtyOriginTop,this.computedMinCroppingTop,this.computedMaxCroppingTop))},set(t){let{scaledRotatedInternalImageHeightDifference:e}=this;this.dirtyOriginTop=e?t/e:.5}},relativeRotatedInternalImageHeight:function(){return this.imageHeight/this.rotatedInternalImageHeight},relativeRotatedInternalImageWidth:function(){return this.imageWidth/this.rotatedInternalImageWidth},rotatedInternalImageHeight:function(){return this.rotated?this.internalImageWidth:this.internalImageHeight},rotatedInternalImageWidth:function(){return this.rotated?this.internalImageHeight:this.internalImageWidth},scaledRotatedInternalImageHeight:function(){return Math.round(this.scaling*this.rotatedInternalImageHeight)},scaledRotatedInternalImageHeightDifference:function(){return this.imageHeight-this.scaledRotatedInternalImageHeight},scaledRotatedInternalImageWidth:function(){return Math.round(this.scaling*this.rotatedInternalImageWidth)},scaledRotatedInternalImageWidthDifference:function(){return this.imageWidth-this.scaledRotatedInternalImageWidth},scaling:{get(){return n(this.dirtyScaling,this.computedMinScaling,this.computedMaxScaling)},set(t){this.dirtyScaling=t}},updateImageData:function(){let{croppingLeft:t,croppingTop:e,debounce:i,flippedHorizontally:n,flippedVertically:a,imageBackgroundColor:r,imageFormat:o,imageHeight:l,imageQuality:s,imageWidth:c,internalImageData:d,internalImageHeight:u,internalImageWidth:h,rotated:p,scaling:g}=this;return setTimeout((()=>{let i;if(d){let f=new Image;f.src=d;let m=document.createElement("canvas"),y=m.getContext("2d");m.width=c,m.height=l,r&&(y.fillStyle=r,y.fillRect(0,0,c,l)),y.translate(t,e),y.scale(g,g),p&&(y.translate(u,0),y.rotate(Math.PI/2)),n&&(y.translate(h,0),y.scale(-1,1)),a&&(y.translate(0,u),y.scale(1,-1)),y.drawImage(f,0,0),n&&(y.translate(c,0),y.scale(-1,1)),a&&(y.translate(0,l),y.scale(1,-1)),i=m.toDataURL(`image/${o}`,s)}else i=null;this.imageData=i}),i)}},r="VImageInput";function o(t){var e={exports:{}};return t(e,e.exports),e.exports}var l=o((function(t,e){t.exports=function(){function t(t){return void 0===t}function e(){}var i={t:e,n:e,o:e,i:e,e:e,u:e};function n(t,e,i,n){return Math.hypot(i-t,n-e)}var a={mousedown:function(t){this.t(t)},mousemove:function(t){this.n(t)},mouseup:function(t){this.o(t)},touchend:function(t){this.i(t)},touchmove:function(t){this.e(t)},touchstart:function(t){this.u(t)}},r=function(){function e(t,e){var i=this;void 0===e&&(e={});var n=e.delay;void 0===n&&(n=500);var r=e.distance;void 0===r&&(r=1),this.target=t,Object.assign(this,{delay:n,distance:r}),this.c(),this.r={};var o,l,s,c=(o=a,l=function(t){return t.bind(i)},s={},Object.entries(o).forEach((function(t){var e=t[0],i=t[1];s[e]=l(i)})),s),d=c.mousedown,u=c.mousemove,h=c.mouseup,p=c.touchend,g=c.touchmove,f=c.touchstart;this.s={mousedown:d,touchend:p,touchmove:g,touchstart:f},this.a={mousemove:u,mouseup:h}}var r={isIdle:{configurable:!0}};return e.prototype.h=function(){var t=this;Object.entries(this.s).forEach((function(e){var i=e[0],n=e[1];t.target.addEventListener(i,n)})),Object.entries(this.a).forEach((function(t){var e=t[0],i=t[1];window.addEventListener(e,i)}))},e.prototype.f=function(){var t=this;Object.entries(this.s).forEach((function(e){var i=e[0],n=e[1];t.target.removeEventListener(i,n)})),Object.entries(this.a).forEach((function(t){var e=t[0],i=t[1];window.removeEventListener(e,i)}))},e.prototype.on=function(t,e){this.isIdle&&this.h(),t=t.toLowerCase();var i=this.r[t];return i||(this.r[t]=i=[]),i.push(e),this},e.prototype.off=function(e,i){if(t(e))this.r={};else if(e=e.toLowerCase(),t(i))delete this.r[e];else{var n=this.r[e];if(n){var a=n.indexOf(i);0<=a&&(n.splice(a,1),n.length||delete this.r[e])}}return this.isIdle&&this.f(),this},e.prototype.trigger=function(t,e){t=t.toLowerCase();var i=this.r[t];return i&&i.forEach((function(t){t(e)})),this},r.isIdle.get=function(){return function(t){for(var e in t)if(i=t,n=e,Object.prototype.hasOwnProperty.call(i,n))return!1;var i,n;return!0}(this.r)},e.prototype.v=function(t){this.m=Object.assign({},i,t)},e.prototype.c=function(){var t,e,i,a,r,o=this,l=function(t){return function(e){e.preventDefault(),0=o.distance&&h()},g={};switch(t){case"touch":Object.assign(g,{u:l(h),e:l(p),i:l(h)});break;case"mouse":Object.assign(g,{n:s(p),o:c(h)})}o.v(g)}),o.delay),h=function(){if(e=Date.now(),n(r,d,i,a)>=o.distance){clearTimeout(u),o.trigger("panStart",{pointerType:t,timeStamp:e,x:r,y:d});var h=e,p=h,g=r,f=d,m=function(){o.trigger("pan",{initialTimeStamp:h,initialX:r,initialY:d,pointerType:t,previousTimeStamp:p,previousX:g,previousY:f,timeStamp:e,x:i,y:a})};m();var y=function(){e=Date.now(),m(),p=e,g=i,f=a},v=function(){e=Date.now(),o.trigger("panEnd",{initialTimeStamp:h,initialX:r,initialY:d,pointerType:t,timeStamp:e,x:i,y:a}),o.c()},I={};switch(t){case"touch":Object.assign(I,{u:l(v),e:l(y),i:l(v)});break;case"mouse":Object.assign(I,{n:s(y),o:c(v)})}o.v(I)}},p=function(){clearTimeout(u),e=Date.now(),o.trigger("tap",{pointerType:t,timeStamp:e,x:i,y:a}),o.c()},g={};switch(t){case"touch":Object.assign(g,{u:l((function(){clearTimeout(u),o.c()})),e:l(h),i:l(p)});break;case"mouse":Object.assign(g,{n:s(h),o:c(p)})}o.v(g)};this.v({u:(r=function(){t="touch",d()},function(t){1===t.touches.length&&l(r)(t)}),t:c((function(){t="mouse",d()}))})},Object.defineProperties(e.prototype,r),e}();return Object.keys(i).forEach((function(t){Object.defineProperty(r.prototype,t,{get:function(){return this.m[t]}})})),r}()})),s=o((function(t,e){t.exports=function(t){t=t&&t.hasOwnProperty("default")?t.default:t;var e=new Map,i={name:"Claw",bind:function(i,n){var a=n.arg,r=n.value,o=e.get(i);o||e.set(i,o=new t(i)),o.on(a,r)},update:function(t,i){var n=i.arg,a=i.value,r=e.get(t);r.off(n),r.on(n,a)},unbind:function(t,i){var n=i.arg,a=e.get(t);a.off(n),a.isIdle&&e.delete(t)}};return"undefined"!=typeof window&&window.Vue&&window.Vue.directive(i.name,i),i}(l)}));var c={type:Boolean,default:!1},d={type:Boolean,default:!1},u={type:Boolean,default:!1},h={type:Boolean,default:!1},p={type:Boolean,default:!1},g={type:Boolean,default:!1};var f,m,y={name:r,components:i,directives:{Claw:s},props:{clearable:c,clearIcon:{type:String,default:"$clear"},clearIconStyle:Object,debounce:{type:Number,default:0},disabled:d,errorIcon:{type:String,default:"$error"},errorIconStyle:Object,flipHorizontallyIcon:{type:String,default:"mdi-flip-horizontal"},flipHorizontallyIconStyle:Object,flipVerticallyIcon:{type:String,default:"mdi-flip-vertical"},flipVerticallyIconStyle:Object,fullHeight:u,fullWidth:h,hideActions:p,imageBackgroundColor:String,imageFormat:{type:String,default:"png"},imageHeight:{type:Number,default:256},imageMaxScaling:{type:Number,default:1},imageMinScaling:{type:String,default:"cover"},imageQuality:{},imageWidth:{type:Number,default:256},name:String,overlayBackgroundColor:{type:String,default:"rgba(0,0,0,0.5)"},overlayBorderColor:{type:String,default:"#fff"},overlayBorderWidth:{type:String,default:"4px"},overlayPadding:{type:String,default:"50px"},readonly:g,rotateClockwiseIcon:{type:String,default:"mdi-rotate-right"},rotateClockwiseIconStyle:Object,rotateCounterClockwiseIcon:{type:String,default:"mdi-rotate-left"},rotateCounterClockwiseIconStyle:Object,successIcon:{type:String,default:"$success"},successIconStyle:Object,uploadIcon:{type:String,default:"mdi-upload"},uploadIconStyle:Object,value:String,backgroundColor:{},maxScaling:{},minScaling:{},rotateCounterclockwiseIcon:{},rotateCounterclockwiseIconStyle:{},scalingSliderColor:{}},data:function(){return{dirtyOriginLeft:.5,dirtyOriginTop:.5,dirtyScaling:0,fileInfo:null,flippedHorizontally:!1,flippedVertically:!1,imageData:null,internalImageData:null,internalImageHeight:0,internalImageWidth:0,rotated:!1}},computed:a,watch:{imageData:function(t){this.$emit("input",t)},updateImageData:function(t,e){clearTimeout(e)},value:{handler(t){t?this.load(t):this.clear()},immediate:!0}},created:function(){let{$props:t,constructor:e}=this,{warn:i}=e.super.util;Object.entries({imageBackgroundColor:"backgroundColor",imageMaxScaling:"maxScaling",imageMinScaling:"minScaling",rotateCounterClockwiseIcon:"rotateCounterclockwiseIcon",rotateCounterClockwiseIconStyle:"rotateCounterclockwiseIconStyle"}).forEach((([e,n])=>{void 0!==t[n]&&i(`[${r}]: The property '${n}' has been renamed to '${e}'.`)})),["scalingSliderColor"].forEach((e=>{void 0!==t[e]&&i(`[${r}]: The property '${e}' has been removed.`)}))},methods:{clear:function(){this.internalImageData=null},flipHorizontally:function(){this.rotated?this.flippedVertically=!this.flippedVertically:this.flippedHorizontally=!this.flippedHorizontally,this.dirtyOriginLeft=1-this.dirtyOriginLeft},flipVertically:function(){this.rotated?this.flippedHorizontally=!this.flippedHorizontally:this.flippedVertically=!this.flippedVertically,this.dirtyOriginTop=1-this.dirtyOriginTop},load:function(t){if(this.imageData!==t){let e=new Image;e.addEventListener("load",(()=>{let i=e.naturalWidth,n=e.naturalHeight;this.internalImageData=i&&n?t:null,this.internalImageWidth=i,this.internalImageHeight=n,this.flippedHorizontally=!1,this.flippedVertically=!1,this.rotated=!1,this.scaling=0,this.dirtyOriginLeft=.5,this.dirtyOriginTop=.5})),e.src=t}},onPan:function(t){this.croppingLeft+=t.x-t.previousX,this.croppingTop+=t.y-t.previousY},rotateClockwise:function(){this.rotated?(this.rotated=!1,this.flippedHorizontally=!this.flippedHorizontally,this.flippedVertically=!this.flippedVertically):this.rotated=!0;let{dirtyOriginTop:t}=this;this.dirtyOriginTop=this.dirtyOriginLeft,this.dirtyOriginLeft=1-t},rotateCounterClockwise:function(){this.rotated?this.rotated=!1:(this.rotated=!0,this.flippedHorizontally=!this.flippedHorizontally,this.flippedVertically=!this.flippedVertically);let{dirtyOriginLeft:t}=this;this.dirtyOriginLeft=this.dirtyOriginTop,this.dirtyOriginTop=1-t},scaleTo:function(t){this.scaling=t}},render:function(t){return t("div",{style:{display:["-ms-inline-grid","inline-grid"],gridColumns:"1fr auto",gridRows:"1fr auto",gridTemplateColumns:"1fr auto",gridTemplateRows:"1fr auto",...this.fullHeight?{height:"100%"}:{},...this.fullWidth?{width:"100%"}:{}}},[t("div",{style:{gridColumn:1,gridRow:1,overflow:"hidden",position:"relative"}},[t("div",{style:{alignItems:"center",background:this.checkeredBackground,display:"flex",height:"100%",justifyContent:"center",overflow:"hidden",padding:`calc(${this.overlayPadding} + ${this.overlayBorderWidth})`,position:"relative",width:"100%",zIndex:0,...this.internalImageData||this.readonly?{}:{opacity:0,pointerEvents:"none",visibility:"hidden"}},...this.internalImageData?this.disabled||this.readonly?{}:{directives:[{name:"Claw",arg:"pan",value:this.onPan}]}:{}},[t("div",{style:{boxShadow:`0 0 4000px 4000px ${this.overlayBackgroundColor}`,maxHeight:"100%",maxWidth:"100%",outline:`${this.overlayBorderWidth} solid ${this.overlayBorderColor}`,pointerEvents:"none"}},[t("div",{style:{height:`${this.imageHeight}px`,position:"relative",width:`${this.imageWidth}px`,zIndex:-1,...this.imageBackgroundColor?{backgroundColor:this.imageBackgroundColor}:{}}},[...this.internalImageData?[t("img",{style:{display:"block",pointerEvents:"none",position:"absolute",transform:[`translate(${this.croppingLeft}px,${this.croppingTop}px)`,`scale(${this.scaling})`,...this.rotated?[`translateX(${this.internalImageHeight}px)`,"rotate(90deg)"]:[],...this.flippedVertically?[`translateY(${this.internalImageHeight}px)`,"scaleY(-1)"]:[],...this.flippedHorizontally?[`translateX(${this.internalImageWidth}px)`,"scaleX(-1)"]:[]].join(" "),transformOrigin:"0 0"},attrs:{src:this.internalImageData}})]:[],...this.internalImageData&&this.imageData?[t("input",{attrs:{name:this.name,type:"hidden",value:this.imageData}})]:[]])])]),...this.internalImageData||this.readonly?[]:[t("MyFileUpload",{style:{bottom:0,left:0,position:"absolute",right:0,top:0},on:{load:({file:{lastModified:t,name:e,size:i,type:n},result:a})=>{this.$emit("file-info",{name:e,size:i,type:n,lastModified:t}),this.load(a)}}})]]),t("div",{style:{display:"flex",flexDirection:"column",gridColumn:2,gridRow:1,justifyContent:"center",...this.internalImageData?{}:{opacity:0,pointerEvents:"none",visibility:"hidden"}}},[...this.clearable?[t("MyClearButton"),t("VSpacer")]:[],...this.hideActions||this.readonly?[]:[t("MyRotateClockwiseButton"),t("MyRotateCounterClockwiseButton"),t("MyFlipHorizontallyButton"),t("MyFlipVerticallyButton")]]),t("div",{style:{gridColumn:1,gridRow:2,...this.internalImageData?{}:{opacity:0,pointerEvents:"none",visibility:"hidden"}}},this.hideActions||this.readonly?[]:[t("MyScalingSlider")])])}};return null===(f=globalThis.window)||void 0===f||null===(m=f.Vue)||void 0===m||m.component(y.name,y),y})); 2 | --------------------------------------------------------------------------------