kopia lustrzana https://github.com/msurguy/SquiggleCam
using different component
rodzic
fd495faa69
commit
847365d9b8
|
@ -64,6 +64,14 @@ module.exports = {
|
|||
limit: 10000,
|
||||
name: utils.assetsPath('fonts/[name].[hash:7].[ext]')
|
||||
}
|
||||
},
|
||||
{
|
||||
test: /\.scss$/,
|
||||
use: [
|
||||
'vue-style-loader',
|
||||
'css-loader',
|
||||
'sass-loader'
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
|
|
|
@ -4,6 +4,8 @@
|
|||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
||||
<title>image-editor</title>
|
||||
<link rel="stylesheet" href="https://unpkg.com/font-awesome@4.7.0/css/font-awesome.min.css">
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
|
|
Plik diff jest za duży
Load Diff
|
@ -35,6 +35,8 @@
|
|||
"build": "node build/build.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"cropperjs": "^1.4.3",
|
||||
"normalize.css": "^8.0.1",
|
||||
"pica": "^5.0.0",
|
||||
"vue": "^2.5.2"
|
||||
},
|
||||
|
@ -56,6 +58,7 @@
|
|||
"friendly-errors-webpack-plugin": "^1.6.1",
|
||||
"html-webpack-plugin": "^2.30.1",
|
||||
"node-notifier": "^5.1.2",
|
||||
"node-sass": "^4.11.0",
|
||||
"optimize-css-assets-webpack-plugin": "^3.2.0",
|
||||
"ora": "^1.2.0",
|
||||
"portfinder": "^1.0.13",
|
||||
|
@ -63,6 +66,7 @@
|
|||
"postcss-loader": "^2.0.8",
|
||||
"postcss-url": "^7.2.1",
|
||||
"rimraf": "^2.6.0",
|
||||
"sass-loader": "^7.1.0",
|
||||
"semver": "^5.3.0",
|
||||
"shelljs": "^0.7.6",
|
||||
"uglifyjs-webpack-plugin": "^1.1.1",
|
||||
|
|
99
src/App.vue
99
src/App.vue
|
@ -1,27 +1,100 @@
|
|||
<template>
|
||||
<div id="app">
|
||||
<ImageProvider/>
|
||||
<header class="header">
|
||||
<span class="title">Photo Editor</span>
|
||||
<navbar :data="data" @change="change"></navbar>
|
||||
</header>
|
||||
<main class="main">
|
||||
<editor v-if="data.loaded" ref="editor" :data="data"></editor>
|
||||
<loader v-else ref="loader" :data="data"></loader>
|
||||
</main>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ImageProvider from './components/ImageProvider'
|
||||
import Navbar from './components/Navbar'
|
||||
import Loader from './components/Loader'
|
||||
import Editor from './components/Editor'
|
||||
|
||||
export default {
|
||||
name: 'App',
|
||||
components: {
|
||||
ImageProvider
|
||||
}
|
||||
navbar: Navbar ,
|
||||
loader: Loader,
|
||||
editor: Editor
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
data: {
|
||||
cropped: false,
|
||||
cropping: false,
|
||||
loaded: false,
|
||||
name: '',
|
||||
previousUrl: '',
|
||||
type: '',
|
||||
url: '',
|
||||
},
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
change(action) {
|
||||
const { editor } = this.$refs;
|
||||
switch (action) {
|
||||
case 'crop':
|
||||
editor.crop();
|
||||
break;
|
||||
case 'clear':
|
||||
editor.clear();
|
||||
break;
|
||||
case 'restore':
|
||||
editor.restore();
|
||||
break;
|
||||
case 'remove':
|
||||
editor.reset();
|
||||
break;
|
||||
default:
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
#app {
|
||||
font-family: 'Avenir', Helvetica, Arial, sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
text-align: center;
|
||||
color: #2c3e50;
|
||||
margin-top: 60px;
|
||||
}
|
||||
<style scoped>
|
||||
.app {
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
}
|
||||
.header {
|
||||
background-color: #666;
|
||||
height: 3rem;
|
||||
overflow: hidden;
|
||||
padding-left: 1rem;
|
||||
padding-right: 1rem;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
@media (min-width: 768px) {
|
||||
.header {
|
||||
padding-left: 1.5rem;
|
||||
padding-right: 1.5rem;
|
||||
}
|
||||
}
|
||||
.title {
|
||||
color: #fff;
|
||||
display: block;
|
||||
float: left;
|
||||
font-size: 1.125rem;
|
||||
line-height: 3rem;
|
||||
}
|
||||
.main {
|
||||
background-color: #333;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 3rem;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -0,0 +1,355 @@
|
|||
<template>
|
||||
<div class="editor">
|
||||
<div class="canvas" @dblclick="dblclick">
|
||||
<img ref="image" :alt="data.name" :src="data.url" @load="start">
|
||||
</div>
|
||||
<div class="toolbar" v-if="cropper" @click="click">
|
||||
<button class="toolbar__button" data-action="move" title="Move (M)"><span class="fa fa-arrows"></span></button>
|
||||
<button class="toolbar__button" data-action="crop" title="Crop (C)"><span class="fa fa-crop"></span></button>
|
||||
<button class="toolbar__button" data-action="zoom-in" title="Zoom In (I)"><span class="fa fa-search-plus"></span></button>
|
||||
<button class="toolbar__button" data-action="zoom-out" title="Zoom Out (O)"><span class="fa fa-search-minus"></span></button>
|
||||
<button class="toolbar__button" data-action="rotate-left" title="Rotate Left (L)"><span class="fa fa-rotate-left"></span></button>
|
||||
<button class="toolbar__button" data-action="rotate-right" title="Rotate Right (R)"><span class="fa fa-rotate-right"></span></button>
|
||||
<button class="toolbar__button" data-action="flip-horizontal" title="Flip Horizontal (H)"><span class="fa fa-arrows-h"></span></button>
|
||||
<button class="toolbar__button" data-action="flip-vertical" title="Flip Vertical (V)"><span class="fa fa-arrows-v"></span></button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Cropper from 'cropperjs';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
data: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
canvasData: null,
|
||||
cropBoxData: null,
|
||||
croppedData: null,
|
||||
cropper: null,
|
||||
};
|
||||
},
|
||||
|
||||
methods: {
|
||||
click({ target }) {
|
||||
const { cropper } = this;
|
||||
const action = target.getAttribute('data-action') || target.parentElement.getAttribute('data-action');
|
||||
|
||||
switch (action) {
|
||||
case 'move':
|
||||
case 'crop':
|
||||
cropper.setDragMode(action);
|
||||
break;
|
||||
|
||||
case 'zoom-in':
|
||||
cropper.zoom(0.1);
|
||||
break;
|
||||
|
||||
case 'zoom-out':
|
||||
cropper.zoom(-0.1);
|
||||
break;
|
||||
|
||||
case 'rotate-left':
|
||||
cropper.rotate(-90);
|
||||
break;
|
||||
|
||||
case 'rotate-right':
|
||||
cropper.rotate(90);
|
||||
break;
|
||||
|
||||
case 'flip-horizontal':
|
||||
cropper.scaleX(-cropper.getData().scaleX || -1);
|
||||
break;
|
||||
|
||||
case 'flip-vertical':
|
||||
cropper.scaleY(-cropper.getData().scaleY || -1);
|
||||
break;
|
||||
|
||||
default:
|
||||
}
|
||||
},
|
||||
|
||||
keydown(e) {
|
||||
switch (e.key) {
|
||||
// Undo crop
|
||||
case 'z':
|
||||
if (e.ctrlKey) {
|
||||
e.preventDefault();
|
||||
this.restore();
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
// Delete the image
|
||||
case 'Delete':
|
||||
this.reset();
|
||||
break;
|
||||
|
||||
default:
|
||||
}
|
||||
|
||||
const { cropper } = this;
|
||||
|
||||
if (!cropper) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (e.key) {
|
||||
// Crop the image
|
||||
case 'Enter':
|
||||
this.crop();
|
||||
break;
|
||||
|
||||
// Clear crop area
|
||||
case 'Escape':
|
||||
this.clear();
|
||||
break;
|
||||
|
||||
// Move to the left
|
||||
case 'ArrowLeft':
|
||||
e.preventDefault();
|
||||
cropper.move(-1, 0);
|
||||
break;
|
||||
|
||||
// Move to the top
|
||||
case 'ArrowUp':
|
||||
e.preventDefault();
|
||||
cropper.move(0, -1);
|
||||
break;
|
||||
|
||||
// Move to the right
|
||||
case 'ArrowRight':
|
||||
e.preventDefault();
|
||||
cropper.move(1, 0);
|
||||
break;
|
||||
|
||||
// Move to the bottom
|
||||
case 'ArrowDown':
|
||||
e.preventDefault();
|
||||
cropper.move(0, 1);
|
||||
break;
|
||||
|
||||
// Enter crop mode
|
||||
case 'c':
|
||||
cropper.setDragMode('crop');
|
||||
break;
|
||||
|
||||
// Enter move mode
|
||||
case 'm':
|
||||
cropper.setDragMode('move');
|
||||
break;
|
||||
|
||||
// Zoom in
|
||||
case 'i':
|
||||
cropper.zoom(0.1);
|
||||
break;
|
||||
|
||||
// Zoom out
|
||||
case 'o':
|
||||
cropper.zoom(-0.1);
|
||||
break;
|
||||
|
||||
// Rotate left
|
||||
case 'l':
|
||||
cropper.rotate(-90);
|
||||
break;
|
||||
|
||||
// Rotate right
|
||||
case 'r':
|
||||
cropper.rotate(90);
|
||||
break;
|
||||
|
||||
// Flip horizontal
|
||||
case 'h':
|
||||
cropper.scaleX(-cropper.getData().scaleX || -1);
|
||||
break;
|
||||
|
||||
// Flip vertical
|
||||
case 'v':
|
||||
cropper.scaleY(-cropper.getData().scaleY || -1);
|
||||
break;
|
||||
|
||||
default:
|
||||
}
|
||||
},
|
||||
|
||||
dblclick(e) {
|
||||
if (e.target.className.indexOf('cropper-face') >= 0) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
this.crop();
|
||||
}
|
||||
},
|
||||
|
||||
start() {
|
||||
const { data } = this;
|
||||
|
||||
if (data.cropped) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.cropper = new Cropper(this.$refs.image, {
|
||||
autoCrop: false,
|
||||
dragMode: 'move',
|
||||
background: false,
|
||||
aspectRatio: 1,
|
||||
viewMode: 1,
|
||||
movable: false,
|
||||
|
||||
ready: () => {
|
||||
if (this.croppedData) {
|
||||
this.cropper
|
||||
.crop()
|
||||
.setData(this.croppedData)
|
||||
.setCanvasData(this.canvasData)
|
||||
.setCropBoxData(this.cropBoxData);
|
||||
|
||||
this.croppedData = null;
|
||||
this.canvasData = null;
|
||||
this.cropBoxData = null;
|
||||
}
|
||||
this.cropper.setDragMode('crop');
|
||||
|
||||
},
|
||||
|
||||
crop: ({ detail }) => {
|
||||
if (detail.width > 0 && detail.height > 0 && !data.cropping) {
|
||||
this.update({
|
||||
cropping: true,
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
},
|
||||
|
||||
stop() {
|
||||
if (this.cropper) {
|
||||
this.cropper.destroy();
|
||||
this.cropper = null;
|
||||
}
|
||||
},
|
||||
|
||||
crop() {
|
||||
const { cropper, data } = this;
|
||||
|
||||
if (data.cropping) {
|
||||
this.croppedData = cropper.getData();
|
||||
this.canvasData = cropper.getCanvasData();
|
||||
this.cropBoxData = cropper.getCropBoxData();
|
||||
this.update({
|
||||
cropped: true,
|
||||
cropping: false,
|
||||
previousUrl: data.url,
|
||||
url: cropper.getCroppedCanvas(data.type === 'image/png' ? {} : {
|
||||
fillColor: '#fff',
|
||||
}).toDataURL(data.type),
|
||||
});
|
||||
this.stop();
|
||||
}
|
||||
},
|
||||
|
||||
clear() {
|
||||
if (this.data.cropping) {
|
||||
this.cropper.clear();
|
||||
this.update({
|
||||
cropping: false,
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
restore() {
|
||||
if (this.data.cropped) {
|
||||
this.update({
|
||||
cropped: false,
|
||||
previousUrl: '',
|
||||
url: this.data.previousUrl,
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
reset() {
|
||||
this.stop();
|
||||
this.update({
|
||||
cropped: false,
|
||||
cropping: false,
|
||||
loaded: false,
|
||||
name: '',
|
||||
previousUrl: '',
|
||||
type: '',
|
||||
url: '',
|
||||
});
|
||||
},
|
||||
|
||||
update(data) {
|
||||
Object.assign(this.data, data);
|
||||
},
|
||||
},
|
||||
|
||||
mounted() {
|
||||
window.addEventListener('keydown', (this.onKeydown = this.keydown.bind(this)));
|
||||
},
|
||||
|
||||
beforeDestroy() {
|
||||
window.removeEventListener('keydown', this.onKeydown);
|
||||
this.stop();
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.editor {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.canvas {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
height: 100%;
|
||||
justify-content: center;
|
||||
|
||||
& > img {
|
||||
max-height: 100%;
|
||||
max-width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.toolbar {
|
||||
background-color: rgba(0, 0, 0, .5);
|
||||
bottom: 1rem;
|
||||
color: #fff;
|
||||
height: 2rem;
|
||||
left: 50%;
|
||||
margin-left: -8rem;
|
||||
position: absolute;
|
||||
width: 16rem;
|
||||
z-index: 2015;
|
||||
}
|
||||
|
||||
.toolbar__button {
|
||||
background-color: transparent;
|
||||
border-width: 0;
|
||||
color: #fff;
|
||||
cursor: pointer;
|
||||
display: block;
|
||||
float: left;
|
||||
font-size: .875rem;
|
||||
height: 2rem;
|
||||
text-align: center;
|
||||
width: 2rem;
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: #0074d9;
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -1,113 +0,0 @@
|
|||
<template>
|
||||
<div class="hello">
|
||||
<h1>{{ msg }}</h1>
|
||||
<h2>Essential Links</h2>
|
||||
<ul>
|
||||
<li>
|
||||
<a
|
||||
href="https://vuejs.org"
|
||||
target="_blank"
|
||||
>
|
||||
Core Docs
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
href="https://forum.vuejs.org"
|
||||
target="_blank"
|
||||
>
|
||||
Forum
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
href="https://chat.vuejs.org"
|
||||
target="_blank"
|
||||
>
|
||||
Community Chat
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
href="https://twitter.com/vuejs"
|
||||
target="_blank"
|
||||
>
|
||||
Twitter
|
||||
</a>
|
||||
</li>
|
||||
<br>
|
||||
<li>
|
||||
<a
|
||||
href="http://vuejs-templates.github.io/webpack/"
|
||||
target="_blank"
|
||||
>
|
||||
Docs for This Template
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
<h2>Ecosystem</h2>
|
||||
<ul>
|
||||
<li>
|
||||
<a
|
||||
href="http://router.vuejs.org/"
|
||||
target="_blank"
|
||||
>
|
||||
vue-router
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
href="http://vuex.vuejs.org/"
|
||||
target="_blank"
|
||||
>
|
||||
vuex
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
href="http://vue-loader.vuejs.org/"
|
||||
target="_blank"
|
||||
>
|
||||
vue-loader
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
href="https://github.com/vuejs/awesome-vue"
|
||||
target="_blank"
|
||||
>
|
||||
awesome-vue
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'HelloWorld',
|
||||
data () {
|
||||
return {
|
||||
msg: 'Welcome to Your Vue.js App'
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- Add "scoped" attribute to limit CSS to this component only -->
|
||||
<style scoped>
|
||||
h1, h2 {
|
||||
font-weight: normal;
|
||||
}
|
||||
ul {
|
||||
list-style-type: none;
|
||||
padding: 0;
|
||||
}
|
||||
li {
|
||||
display: inline-block;
|
||||
margin: 0 10px;
|
||||
}
|
||||
a {
|
||||
color: #42b983;
|
||||
}
|
||||
</style>
|
|
@ -1,213 +0,0 @@
|
|||
<template>
|
||||
<div>
|
||||
<img v-bind:src="imagePreview" alt="">
|
||||
<input type="file" @change="updatePreview">
|
||||
<canvas ref="originalCanvas"></canvas>
|
||||
<canvas ref="resizedCanvas" width="300" height="300"></canvas>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Pica from 'pica';
|
||||
import getOrientation from '../lib/imageUtils';
|
||||
|
||||
export default {
|
||||
name: "FileUploader",
|
||||
data: function(){
|
||||
return {
|
||||
offscreenImage: null,
|
||||
uploadedFile: {
|
||||
name: '',
|
||||
size: 0,
|
||||
type: '',
|
||||
},
|
||||
uploadedImage: {
|
||||
width: 0,
|
||||
height: 0,
|
||||
data: [],
|
||||
orientation: 0
|
||||
},
|
||||
imagePreview: '',
|
||||
pica: undefined
|
||||
}
|
||||
},
|
||||
created: function(){
|
||||
//this.offscreenImage = new Image();
|
||||
const img = new Image();
|
||||
this.offscreenImage = img;
|
||||
|
||||
img.onload = () => {
|
||||
console.log(img);
|
||||
};
|
||||
|
||||
this.pica = Pica();
|
||||
},
|
||||
methods: {
|
||||
orientImage(canvas, ctx, orientation) {
|
||||
let width = canvas.width;
|
||||
let height = canvas.height;
|
||||
|
||||
if (!orientation || orientation > 8) return;
|
||||
|
||||
if (orientation > 4) {
|
||||
ctx.canvas.width = height;
|
||||
ctx.canvas.height = width;
|
||||
}
|
||||
|
||||
switch (orientation) {
|
||||
|
||||
case 2:
|
||||
// Horizontal flip
|
||||
ctx.translate(width, 0);
|
||||
ctx.scale(-1, 1);
|
||||
break;
|
||||
|
||||
case 3:
|
||||
// rotate 180 degrees left
|
||||
ctx.translate(width, height);
|
||||
ctx.rotate(Math.PI);
|
||||
break;
|
||||
|
||||
case 4:
|
||||
// Vertical flip
|
||||
ctx.translate(0, height);
|
||||
ctx.scale(1, -1);
|
||||
break;
|
||||
|
||||
case 5:
|
||||
// Vertical flip + rotate right
|
||||
ctx.rotate(0.5 * Math.PI);
|
||||
ctx.scale(1, -1);
|
||||
break;
|
||||
|
||||
case 6:
|
||||
// Rotate right
|
||||
ctx.rotate(0.5 * Math.PI);
|
||||
ctx.translate(0, -height);
|
||||
break;
|
||||
|
||||
case 7:
|
||||
// Horizontal flip + rotate right
|
||||
ctx.rotate(0.5 * Math.PI);
|
||||
ctx.translate(width, -height);
|
||||
ctx.scale(-1, 1);
|
||||
break;
|
||||
|
||||
case 8:
|
||||
// Rotate left
|
||||
ctx.rotate(-0.5 * Math.PI);
|
||||
ctx.translate(-width, 0);
|
||||
break;
|
||||
|
||||
default:
|
||||
}
|
||||
},
|
||||
openUpload() {
|
||||
//
|
||||
},
|
||||
loadImage(file){
|
||||
const ctx = this.$refs.originalCanvas.getContext('2d');
|
||||
let orientation;
|
||||
|
||||
|
||||
//image = new Image();
|
||||
|
||||
// this.offscreenImage.onerror = () => { N.wire.emit('notify', t('err_image_invalid')); };
|
||||
|
||||
this.offscreenImage.onload = () => {
|
||||
this.$refs.originalCanvas.width = this.offscreenImage.width;
|
||||
this.$refs.originalCanvas.height = this.offscreenImage.height;
|
||||
|
||||
this.orientImage(this.$refs.originalCanvas, ctx, this.uploadedImage.orientation);
|
||||
|
||||
let imageWidth = this.$refs.originalCanvas.width;
|
||||
let imageHeight = this.$refs.originalCanvas.height;
|
||||
|
||||
///let canvasCropped = document.createElement('canvas');
|
||||
|
||||
//canvasCropped.width = 500;
|
||||
//canvasCropped.height = 500;
|
||||
|
||||
//let ctxCropped = canvasCropped.getContext('2d');
|
||||
|
||||
//console.log(this.pica);
|
||||
ctx.drawImage(this.offscreenImage, 0, 0, this.offscreenImage.width, this.offscreenImage.height);
|
||||
|
||||
this.pica.resize(this.$refs.originalCanvas, this.$refs.resizedCanvas)
|
||||
.then(() => this.pica.toBlob(this.$refs.resizedCanvas, 'image/jpeg', 90))
|
||||
.then(function (blob) {
|
||||
console.log(blob);
|
||||
})
|
||||
.catch((e) => {
|
||||
console.log(e);
|
||||
//N.wire.emit('notify', t('err_image_invalid'));
|
||||
throw 'CANCELED';
|
||||
});
|
||||
|
||||
//this.resizeImage();
|
||||
|
||||
};
|
||||
|
||||
let reader = new FileReader();
|
||||
|
||||
reader.onloadend = e => {
|
||||
this.uploadedImage.orientation = getOrientation(e.target.result);
|
||||
this.offscreenImage.src = window.URL.createObjectURL(file);
|
||||
};
|
||||
|
||||
reader.readAsArrayBuffer(file);
|
||||
|
||||
},
|
||||
updatePreview(e) {
|
||||
let files = e.target.files;
|
||||
const allowedFiles = ['image/jpeg', 'image/png', 'image/jpg'];
|
||||
//const pica = Pica();
|
||||
|
||||
if (files.length === 0) {
|
||||
console.log('No files were selected');
|
||||
}
|
||||
|
||||
if (!allowedFiles.includes(files[0].type)){
|
||||
console.log(`${files[0].name} is not an image`);
|
||||
return;
|
||||
}
|
||||
|
||||
const file = files[0];
|
||||
this.loadImage(file);
|
||||
|
||||
// let source = document.createElement('canvas');
|
||||
// let dest = document.createElement('canvas');
|
||||
//
|
||||
// source.width = 1600;
|
||||
// source.height = 1600;
|
||||
//
|
||||
// dest.width = 400;
|
||||
// dest.height = 400;
|
||||
|
||||
// const reader = new FileReader();
|
||||
//
|
||||
// reader.onloadend = e => {
|
||||
// //let fileData = new Uint8Array(reader.result);
|
||||
// this.offscreenImage.src = window.URL.createObjectURL(file);
|
||||
//
|
||||
// }
|
||||
|
||||
//reader.onload = (e) => {
|
||||
// this.imagePreview = this.imageData = e.target.result;
|
||||
|
||||
//source.getContext('2d').drawImage(img, cropX, 0, width, img.height);
|
||||
|
||||
|
||||
//pica.resize(this.imagePreview, this.$refs.resizedPreview)
|
||||
// .then(result => console.log('resize done!'));
|
||||
//};
|
||||
|
||||
//reader.readAsDataURL(files[0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
|
@ -0,0 +1,104 @@
|
|||
<template>
|
||||
<div class="loader" @change="change" @dragover="dragover" @drop="drop">
|
||||
<p>Drop image here or
|
||||
<label class="browse">browse...
|
||||
<input class="sr-only" id="file" type="file" accept="image/*">
|
||||
</label>
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
const URL = window.URL || window.webkitURL;
|
||||
|
||||
export default {
|
||||
props: {
|
||||
data: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
read(files) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!files || files.length === 0) {
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
|
||||
const file = files[0];
|
||||
|
||||
if (/^image\/\w+$/.test(file.type)) {
|
||||
if (URL) {
|
||||
resolve({
|
||||
loaded: true,
|
||||
name: file.name,
|
||||
type: file.type,
|
||||
url: URL.createObjectURL(file),
|
||||
});
|
||||
} else {
|
||||
reject(new Error('Your browser is not supported.'));
|
||||
}
|
||||
} else {
|
||||
reject(new Error('Please choose an image file.'));
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
change({ target }) {
|
||||
this.read(target.files).then((data) => {
|
||||
target.value = '';
|
||||
this.update(data);
|
||||
}).catch((e) => {
|
||||
target.value = '';
|
||||
this.alert(e);
|
||||
});
|
||||
},
|
||||
|
||||
dragover(e) {
|
||||
e.preventDefault();
|
||||
},
|
||||
|
||||
drop(e) {
|
||||
e.preventDefault();
|
||||
this.read(e.dataTransfer.files).catch(this.alert);
|
||||
},
|
||||
|
||||
alert(e) {
|
||||
window.alert(e && e.message ? e.message : e);
|
||||
},
|
||||
|
||||
update(data) {
|
||||
Object.assign(this.data, data);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.loader {
|
||||
display: table;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
|
||||
& > p {
|
||||
color: #999;
|
||||
display: table-cell;
|
||||
text-align: center;
|
||||
vertical-align: middle;
|
||||
}
|
||||
}
|
||||
|
||||
.browse {
|
||||
color: #0074d9;
|
||||
cursor: pointer;
|
||||
margin-left: .25rem;
|
||||
|
||||
&:hover {
|
||||
color: #08f;
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,76 @@
|
|||
<template>
|
||||
<div class="navbar">
|
||||
<nav class="nav" @click="click">
|
||||
<label class="nav__button" for="file" title="Upload" role="button" v-if="!data.loaded"><span class="fa fa-upload"></span></label>
|
||||
<button type="button" class="nav__button" data-action="restore" title="Undo (Ctrl + Z)" v-if="data.cropped"><span class="fa fa-undo"></span></button>
|
||||
<button type="button" class="nav__button nav__button--danger" data-action="remove" title="Delete (Delete)" v-if="data.loaded && !data.cropping"><span class="fa fa-trash"></span></button>
|
||||
<button type="button" class="nav__button nav__button--danger" data-action="clear" title="Cancel (Esc)" v-if="data.cropping"><span class="fa fa-ban"></span></button>
|
||||
<button type="button" class="nav__button nav__button--success" data-action="crop" title="OK (Enter)" v-if="data.cropping"><span class="fa fa-check"></span></button>
|
||||
<a class="nav__button nav__button--success" title="Download" :download="data.name" :href="data.url" v-if="downloadable && data.loaded"><span class="fa fa-download"></span></a>
|
||||
<a class="nav__button" href="https://github.com/fengyuanchen/photo-editor" title="View on GitHub"><span class="fa fa-github"></span></a>
|
||||
</nav>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
downloadable: typeof document.createElement('a').download !== 'undefined',
|
||||
};
|
||||
},
|
||||
|
||||
props: {
|
||||
data: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
click({ target }) {
|
||||
const action = target.getAttribute('data-action') || target.parentElement.getAttribute('data-action');
|
||||
|
||||
if (action) {
|
||||
this.$emit('change', action);
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.navbar {
|
||||
float: right;
|
||||
}
|
||||
|
||||
.nav__button {
|
||||
background-color: transparent;
|
||||
border-width: 0;
|
||||
color: #fff;
|
||||
cursor: pointer;
|
||||
display: block;
|
||||
float: left;
|
||||
height: 3rem;
|
||||
line-height: 3rem;
|
||||
text-align: center;
|
||||
width: 3rem;
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: #0074d9;
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
|
||||
.nav--success:hover {
|
||||
background-color: #2ecc40;
|
||||
}
|
||||
|
||||
.nav--danger:hover {
|
||||
background-color: #ff4136;
|
||||
}
|
||||
</style>
|
|
@ -1,42 +0,0 @@
|
|||
export default function getOrientation(data) {
|
||||
let view = new DataView(data);
|
||||
if (view.getUint16(0, false) !== 0xFFD8)
|
||||
{
|
||||
return -2;
|
||||
}
|
||||
let length = view.byteLength, offset = 2;
|
||||
while (offset < length)
|
||||
{
|
||||
if (view.getUint16(offset+2, false) <= 8) return -1;
|
||||
let marker = view.getUint16(offset, false);
|
||||
offset += 2;
|
||||
if (marker === 0xFFE1)
|
||||
{
|
||||
if (view.getUint32(offset += 2, false) !== 0x45786966)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
let little = view.getUint16(offset += 6, false) === 0x4949;
|
||||
offset += view.getUint32(offset + 4, little);
|
||||
let tags = view.getUint16(offset, little);
|
||||
offset += 2;
|
||||
for (let i = 0; i < tags; i++)
|
||||
{
|
||||
if (view.getUint16(offset + (i * 12), little) === 0x0112)
|
||||
{
|
||||
return view.getUint16(offset + (i * 12) + 8, little);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if ((marker & 0xFF00) !== 0xFF00)
|
||||
{
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
offset += view.getUint16(offset, false);
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
|
@ -1,7 +1,10 @@
|
|||
// The Vue build version to load with the `import` command
|
||||
// (runtime-only or standalone) has been set in webpack.base.conf with an alias.
|
||||
import 'normalize.css'
|
||||
import Vue from 'vue'
|
||||
import App from './App'
|
||||
import './styles/index.css'
|
||||
import 'cropperjs/dist/cropper.css';
|
||||
|
||||
Vue.config.productionTip = false
|
||||
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
*,
|
||||
::before,
|
||||
::after {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: #fff;
|
||||
color: #212529;
|
||||
font-family: -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol";
|
||||
font-size: 1rem;
|
||||
font-weight: 400;
|
||||
line-height: 1.5;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.sr-only {
|
||||
border: 0;
|
||||
clip: rect(0, 0, 0, 0);
|
||||
height: 1px;
|
||||
overflow: hidden;
|
||||
padding: 0;
|
||||
position: absolute;
|
||||
white-space: nowrap;
|
||||
width: 1px;
|
||||
}
|
Ładowanie…
Reference in New Issue