Merge pull request '2848 - Added photo cropper' (#709) from 2848-photo_cropper into dev
gitea/salix/pipeline/head This commit looks good
Details
gitea/salix/pipeline/head This commit looks good
Details
Reviewed-on: #709 Reviewed-by: Carlos Jimenez Ruiz <carlosjr@verdnatura.es>
This commit is contained in:
commit
8be54f6115
|
@ -14,6 +14,7 @@
|
||||||
"angular-animate": "^1.7.8",
|
"angular-animate": "^1.7.8",
|
||||||
"angular-translate": "^2.18.1",
|
"angular-translate": "^2.18.1",
|
||||||
"angular-translate-loader-partial": "^2.18.1",
|
"angular-translate-loader-partial": "^2.18.1",
|
||||||
|
"croppie": "^2.6.5",
|
||||||
"js-yaml": "^3.13.1",
|
"js-yaml": "^3.13.1",
|
||||||
"mg-crud": "^1.1.2",
|
"mg-crud": "^1.1.2",
|
||||||
"oclazyload": "^0.6.3",
|
"oclazyload": "^0.6.3",
|
||||||
|
@ -77,6 +78,11 @@
|
||||||
"sprintf-js": "~1.0.2"
|
"sprintf-js": "~1.0.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/croppie": {
|
||||||
|
"version": "2.6.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/croppie/-/croppie-2.6.5.tgz",
|
||||||
|
"integrity": "sha512-IlChnVUGG5T3w2gRZIaQgBtlvyuYnlUWs2YZIXXR3H9KrlO1PtBT3j+ykxvy9eZIWhk+V5SpBmhCQz5UXKrEKQ=="
|
||||||
|
},
|
||||||
"node_modules/esprima": {
|
"node_modules/esprima": {
|
||||||
"version": "4.0.1",
|
"version": "4.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
|
||||||
|
@ -200,6 +206,11 @@
|
||||||
"sprintf-js": "~1.0.2"
|
"sprintf-js": "~1.0.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"croppie": {
|
||||||
|
"version": "2.6.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/croppie/-/croppie-2.6.5.tgz",
|
||||||
|
"integrity": "sha512-IlChnVUGG5T3w2gRZIaQgBtlvyuYnlUWs2YZIXXR3H9KrlO1PtBT3j+ykxvy9eZIWhk+V5SpBmhCQz5UXKrEKQ=="
|
||||||
|
},
|
||||||
"esprima": {
|
"esprima": {
|
||||||
"version": "4.0.1",
|
"version": "4.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
|
||||||
|
|
|
@ -14,6 +14,7 @@
|
||||||
"angular-animate": "^1.7.8",
|
"angular-animate": "^1.7.8",
|
||||||
"angular-translate": "^2.18.1",
|
"angular-translate": "^2.18.1",
|
||||||
"angular-translate-loader-partial": "^2.18.1",
|
"angular-translate-loader-partial": "^2.18.1",
|
||||||
|
"croppie": "^2.6.5",
|
||||||
"js-yaml": "^3.13.1",
|
"js-yaml": "^3.13.1",
|
||||||
"mg-crud": "^1.1.2",
|
"mg-crud": "^1.1.2",
|
||||||
"oclazyload": "^0.6.3",
|
"oclazyload": "^0.6.3",
|
||||||
|
|
|
@ -0,0 +1,252 @@
|
||||||
|
@import "./variables";
|
||||||
|
|
||||||
|
|
||||||
|
.croppie-container {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.croppie-container .cr-image {
|
||||||
|
z-index: -1;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
transform-origin: 0 0;
|
||||||
|
max-height: none;
|
||||||
|
max-width: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.croppie-container .cr-boundary {
|
||||||
|
border: 2px solid $color-primary;
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
margin: 0 auto;
|
||||||
|
z-index: 1;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.croppie-container .cr-viewport,
|
||||||
|
.croppie-container .cr-resizer {
|
||||||
|
position: absolute;
|
||||||
|
border: 2px solid #fff;
|
||||||
|
margin: auto;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
right: 0;
|
||||||
|
left: 0;
|
||||||
|
box-shadow: 0 0 2000px 2000px rgba(0, 0, 0, 0.5);
|
||||||
|
z-index: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.croppie-container .cr-resizer {
|
||||||
|
z-index: 2;
|
||||||
|
box-shadow: none;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.croppie-container .cr-resizer-vertical,
|
||||||
|
.croppie-container .cr-resizer-horisontal {
|
||||||
|
position: absolute;
|
||||||
|
pointer-events: all;
|
||||||
|
}
|
||||||
|
|
||||||
|
.croppie-container .cr-resizer-vertical::after,
|
||||||
|
.croppie-container .cr-resizer-horisontal::after {
|
||||||
|
display: block;
|
||||||
|
position: absolute;
|
||||||
|
box-sizing: border-box;
|
||||||
|
border: 1px solid black;
|
||||||
|
background: #fff;
|
||||||
|
width: 10px;
|
||||||
|
height: 10px;
|
||||||
|
content: '';
|
||||||
|
}
|
||||||
|
|
||||||
|
.croppie-container .cr-resizer-vertical {
|
||||||
|
bottom: -5px;
|
||||||
|
cursor: row-resize;
|
||||||
|
width: 100%;
|
||||||
|
height: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.croppie-container .cr-resizer-vertical::after {
|
||||||
|
left: 50%;
|
||||||
|
margin-left: -5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.croppie-container .cr-resizer-horisontal {
|
||||||
|
right: -5px;
|
||||||
|
cursor: col-resize;
|
||||||
|
width: 10px;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.croppie-container .cr-resizer-horisontal::after {
|
||||||
|
top: 50%;
|
||||||
|
margin-top: -5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.croppie-container .cr-original-image {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.croppie-container .cr-vp-circle {
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.croppie-container .cr-overlay {
|
||||||
|
z-index: 1;
|
||||||
|
position: absolute;
|
||||||
|
cursor: move;
|
||||||
|
touch-action: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.croppie-container .cr-slider-wrap {
|
||||||
|
margin: 15px auto;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.croppie-result {
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.croppie-result img {
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
|
||||||
|
.croppie-container .cr-image,
|
||||||
|
.croppie-container .cr-overlay,
|
||||||
|
.croppie-container .cr-viewport {
|
||||||
|
-webkit-transform: translateZ(0);
|
||||||
|
-moz-transform: translateZ(0);
|
||||||
|
-ms-transform: translateZ(0);
|
||||||
|
transform: translateZ(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*************************************/
|
||||||
|
/***** STYLING RANGE INPUT ***********/
|
||||||
|
/*************************************/
|
||||||
|
/*http://brennaobrien.com/blog/2014/05/style-input-type-range-in-every-browser.html */
|
||||||
|
/*************************************/
|
||||||
|
|
||||||
|
.cr-slider {
|
||||||
|
-webkit-appearance: none;
|
||||||
|
/*removes default webkit styles*/
|
||||||
|
/*border: 1px solid white; *//*fix for FF unable to apply focus style bug */
|
||||||
|
width: 300px;
|
||||||
|
/*required for proper track sizing in FF*/
|
||||||
|
max-width: 100%;
|
||||||
|
padding-top: 8px;
|
||||||
|
padding-bottom: 8px;
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cr-slider::-webkit-slider-runnable-track {
|
||||||
|
width: 100%;
|
||||||
|
height: 3px;
|
||||||
|
background: rgba(0, 0, 0, 0.2);
|
||||||
|
border: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cr-slider::-webkit-slider-thumb {
|
||||||
|
-webkit-appearance: none;
|
||||||
|
border: none;
|
||||||
|
height: 16px;
|
||||||
|
width: 16px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: $color-primary;
|
||||||
|
margin-top: -6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cr-slider:focus {
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
.cr-slider:focus::-webkit-slider-runnable-track {
|
||||||
|
background: #ccc;
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
.cr-slider::-moz-range-track {
|
||||||
|
width: 100%;
|
||||||
|
height: 3px;
|
||||||
|
background: rgba(0, 0, 0, 0.5);
|
||||||
|
border: 0;
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cr-slider::-moz-range-thumb {
|
||||||
|
border: none;
|
||||||
|
height: 16px;
|
||||||
|
width: 16px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: #ddd;
|
||||||
|
margin-top: -6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*hide the outline behind the border*/
|
||||||
|
.cr-slider:-moz-focusring {
|
||||||
|
outline: 1px solid white;
|
||||||
|
outline-offset: -1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cr-slider::-ms-track {
|
||||||
|
width: 100%;
|
||||||
|
height: 5px;
|
||||||
|
background: transparent;
|
||||||
|
/*remove bg colour from the track, we'll use ms-fill-lower and ms-fill-upper instead */
|
||||||
|
border-color: transparent;/*leave room for the larger thumb to overflow with a transparent border */
|
||||||
|
border-width: 6px 0;
|
||||||
|
color: transparent;/*remove default tick marks*/
|
||||||
|
}
|
||||||
|
.cr-slider::-ms-fill-lower {
|
||||||
|
background: rgba(0, 0, 0, 0.5);
|
||||||
|
border-radius: 10px;
|
||||||
|
}
|
||||||
|
.cr-slider::-ms-fill-upper {
|
||||||
|
background: rgba(0, 0, 0, 0.5);
|
||||||
|
border-radius: 10px;
|
||||||
|
}
|
||||||
|
.cr-slider::-ms-thumb {
|
||||||
|
border: none;
|
||||||
|
height: 16px;
|
||||||
|
width: 16px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: #ddd;
|
||||||
|
margin-top:1px;
|
||||||
|
}
|
||||||
|
.cr-slider:focus::-ms-fill-lower {
|
||||||
|
background: rgba(0, 0, 0, 0.5);
|
||||||
|
}
|
||||||
|
.cr-slider:focus::-ms-fill-upper {
|
||||||
|
background: rgba(0, 0, 0, 0.5);
|
||||||
|
}
|
||||||
|
/*******************************************/
|
||||||
|
|
||||||
|
/***********************************/
|
||||||
|
/* Rotation Tools */
|
||||||
|
/***********************************/
|
||||||
|
.cr-rotate-controls {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 5px;
|
||||||
|
left: 5px;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
.cr-rotate-controls button {
|
||||||
|
border: 0;
|
||||||
|
background: none;
|
||||||
|
}
|
||||||
|
.cr-rotate-controls i:before {
|
||||||
|
display: inline-block;
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 900;
|
||||||
|
font-size: 22px;
|
||||||
|
}
|
||||||
|
.cr-rotate-l i:before {
|
||||||
|
content: '↺';
|
||||||
|
}
|
||||||
|
.cr-rotate-r i:before {
|
||||||
|
content: '↻';
|
||||||
|
}
|
|
@ -1,36 +1,62 @@
|
||||||
<vn-dialog class="edit"
|
<vn-dialog class="edit"
|
||||||
vn-id="dialog"
|
vn-id="dialog"
|
||||||
on-accept="$ctrl.onUploadAccept()"
|
on-accept="$ctrl.onUploadAccept()"
|
||||||
message="Upload new photo">
|
message="Edit photo">
|
||||||
<tpl-body class="upload-photo">
|
<tpl-body class="upload-photo">
|
||||||
<vn-horizontal ng-show="file.value" class="photo vn-mb-md">
|
<vn-horizontal>
|
||||||
<div><img vn-id="photo" ng-src=""/></div>
|
<vn-one ng-if="file.value">
|
||||||
</vn-horizontal>
|
<vn-horizontal>
|
||||||
<vn-horizontal>
|
<vn-icon-button vn-none
|
||||||
<vn-textfield
|
icon="rotate_left"
|
||||||
vn-one
|
vn-tooltip="Rotate left"
|
||||||
label="File name"
|
ng-click="$ctrl.rotateLeft()">
|
||||||
ng-model="$ctrl.newPhoto.fileName"
|
</vn-icon-button>
|
||||||
required="true">
|
<div id="photoContainer"></div>
|
||||||
</vn-input-file>
|
<vn-icon-button vn-none
|
||||||
</vn-horizontal>
|
icon="rotate_right"
|
||||||
<vn-horizontal>
|
vn-tooltip="Rotate right"
|
||||||
<vn-input-file vn-id="file"
|
ng-click="$ctrl.rotateRight()">
|
||||||
vn-one
|
</vn-icon-button>
|
||||||
label="File"
|
</vn-horizontal>
|
||||||
ng-model="$ctrl.newPhoto.files"
|
</vn-one>
|
||||||
on-change="$ctrl.updatePhotoPreview(value)"
|
<vn-one>
|
||||||
accept="{{$ctrl.allowedContentTypes}}"
|
<vn-horizontal>
|
||||||
required="true">
|
<vn-input-file vn-id="file"
|
||||||
<append>
|
vn-one
|
||||||
<vn-icon vn-none
|
label="File"
|
||||||
color-marginal
|
ng-model="$ctrl.newPhoto.files"
|
||||||
title="{{$ctrl.contentTypesInfo}}"
|
on-change="$ctrl.updatePhotoPreview(value)"
|
||||||
icon="info">
|
accept="{{$ctrl.allowedContentTypes}}"
|
||||||
</vn-icon>
|
required="true">
|
||||||
</append>
|
<append>
|
||||||
</vn-input-file>
|
<vn-icon vn-none
|
||||||
</vn-horizontal>
|
color-marginal
|
||||||
|
title="{{$ctrl.contentTypesInfo}}"
|
||||||
|
icon="info">
|
||||||
|
</vn-icon>
|
||||||
|
</append>
|
||||||
|
</vn-input-file>
|
||||||
|
</vn-horizontal>
|
||||||
|
<vn-horizontal>
|
||||||
|
<vn-autocomplete
|
||||||
|
label="Type"
|
||||||
|
ng-model="$ctrl.viewportType"
|
||||||
|
data="$ctrl.viewportTypes"
|
||||||
|
selection="$ctrl.viewportSelection"
|
||||||
|
show-field="description"
|
||||||
|
value-field="code">
|
||||||
|
</vn-autocomplete>
|
||||||
|
</vn-horizontal>
|
||||||
|
<vn-horizontal>
|
||||||
|
<vn-textfield
|
||||||
|
vn-one
|
||||||
|
label="File name"
|
||||||
|
ng-model="$ctrl.newPhoto.fileName"
|
||||||
|
required="true">
|
||||||
|
</vn-input-file>
|
||||||
|
</vn-horizontal>
|
||||||
|
</vn-one>
|
||||||
|
</vn-horizontal>
|
||||||
</tpl-body>
|
</tpl-body>
|
||||||
<tpl-buttons>
|
<tpl-buttons>
|
||||||
<input type="button" response="cancel" translate-attr="{value: 'Cancel'}"/>
|
<input type="button" response="cancel" translate-attr="{value: 'Cancel'}"/>
|
||||||
|
|
|
@ -1,24 +1,74 @@
|
||||||
import ngModule from '../../module';
|
import ngModule from '../../module';
|
||||||
import Component from 'core/lib/component';
|
import Component from 'core/lib/component';
|
||||||
|
import Croppie from 'croppie';
|
||||||
import './style.scss';
|
import './style.scss';
|
||||||
|
import './croppie.scss';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Small card with basing entity information and actions.
|
* Small card with basing entity information and actions.
|
||||||
*/
|
*/
|
||||||
export default class UploadPhoto extends Component {
|
export default class UploadPhoto extends Component {
|
||||||
|
constructor($element, $) {
|
||||||
|
super($element, $);
|
||||||
|
|
||||||
|
this.viewportTypes = [
|
||||||
|
{
|
||||||
|
code: 'normal',
|
||||||
|
description: this.$t('Normal'),
|
||||||
|
viewport: {
|
||||||
|
width: 400,
|
||||||
|
height: 400
|
||||||
|
},
|
||||||
|
output: {
|
||||||
|
width: 1200,
|
||||||
|
height: 1200
|
||||||
|
}
|
||||||
|
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: 'panoramic',
|
||||||
|
description: this.$t('Panoramic'),
|
||||||
|
viewport: {
|
||||||
|
width: 675,
|
||||||
|
height: 450
|
||||||
|
},
|
||||||
|
output: {
|
||||||
|
width: 1350,
|
||||||
|
height: 900
|
||||||
|
}
|
||||||
|
}
|
||||||
|
];
|
||||||
|
this.viewportType = 'normal';
|
||||||
|
this.getAllowedContentTypes();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Opens the dialog and sets the default data
|
* Opens the dialog and sets the default data
|
||||||
* @param {*} collection - Collection name
|
* @param {*} collection - Collection name
|
||||||
* @param {*} id - Entity id
|
* @param {*} id - Entity id
|
||||||
*/
|
*/
|
||||||
show(collection, id) {
|
show(collection, id) {
|
||||||
|
this.editor = null;
|
||||||
this.newPhoto = {
|
this.newPhoto = {
|
||||||
id: id,
|
id: id,
|
||||||
collection: collection,
|
collection: collection,
|
||||||
fileName: id
|
fileName: id
|
||||||
};
|
};
|
||||||
this.$.dialog.show();
|
this.$.dialog.show();
|
||||||
this.getAllowedContentTypes();
|
}
|
||||||
|
|
||||||
|
get viewportSelection() {
|
||||||
|
return this._viewportSelection;
|
||||||
|
}
|
||||||
|
|
||||||
|
set viewportSelection(value) {
|
||||||
|
this._viewportSelection = value;
|
||||||
|
|
||||||
|
if (value && this.newPhoto.files) {
|
||||||
|
this.displayEditor();
|
||||||
|
const files = this.newPhoto.files;
|
||||||
|
this.updatePhotoPreview(files);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
getAllowedContentTypes() {
|
getAllowedContentTypes() {
|
||||||
|
@ -41,12 +91,39 @@ export default class UploadPhoto extends Component {
|
||||||
*/
|
*/
|
||||||
updatePhotoPreview(value) {
|
updatePhotoPreview(value) {
|
||||||
if (value && value[0]) {
|
if (value && value[0]) {
|
||||||
|
if (!this.editor)
|
||||||
|
this.displayEditor();
|
||||||
|
|
||||||
const reader = new FileReader();
|
const reader = new FileReader();
|
||||||
reader.onload = e => this.$.photo.src = e.target.result;
|
reader.onload = e => this.editor.bind({url: e.target.result});
|
||||||
reader.readAsDataURL(value[0]);
|
reader.readAsDataURL(value[0]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
displayEditor() {
|
||||||
|
const viewportType = this.viewportSelection;
|
||||||
|
const viewport = viewportType.viewport;
|
||||||
|
const boundaryWidth = viewport.width + 200;
|
||||||
|
const boundaryHeight = viewport.height + 200;
|
||||||
|
|
||||||
|
const container = document.getElementById('photoContainer');
|
||||||
|
if (this.editor) this.editor.destroy();
|
||||||
|
this.editor = new Croppie(container, {
|
||||||
|
viewport: {width: viewport.width, height: viewport.height},
|
||||||
|
boundary: {width: boundaryWidth, height: boundaryHeight},
|
||||||
|
enableOrientation: true,
|
||||||
|
showZoomer: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
rotateLeft() {
|
||||||
|
this.editor.rotate(90);
|
||||||
|
}
|
||||||
|
|
||||||
|
rotateRight() {
|
||||||
|
this.editor.rotate(-90);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Dialog response handler
|
* Dialog response handler
|
||||||
*
|
*
|
||||||
|
@ -57,12 +134,22 @@ export default class UploadPhoto extends Component {
|
||||||
if (!this.newPhoto.files)
|
if (!this.newPhoto.files)
|
||||||
throw new Error(`Select an image`);
|
throw new Error(`Select an image`);
|
||||||
|
|
||||||
this.makeRequest();
|
const viewportType = this.viewportSelection;
|
||||||
|
const output = viewportType.output;
|
||||||
|
const options = {
|
||||||
|
type: 'blob',
|
||||||
|
size: {
|
||||||
|
width: output.width,
|
||||||
|
height: output.height
|
||||||
|
}
|
||||||
|
};
|
||||||
|
return this.editor.result(options)
|
||||||
|
.then(blob => this.newPhoto.blob = blob)
|
||||||
|
.then(() => this.makeRequest());
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this.vnApp.showError(this.$t(e.message));
|
this.vnApp.showError(this.$t(e.message));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -79,10 +166,13 @@ export default class UploadPhoto extends Component {
|
||||||
params: this.newPhoto,
|
params: this.newPhoto,
|
||||||
headers: {'Content-Type': undefined},
|
headers: {'Content-Type': undefined},
|
||||||
timeout: this.canceler.promise,
|
timeout: this.canceler.promise,
|
||||||
transformRequest: files => {
|
transformRequest: ([file]) => {
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
for (let i = 0; i < files.length; i++)
|
const now = new Date();
|
||||||
formData.append(files[i].name, files[i]);
|
const timestamp = now.getTime();
|
||||||
|
const fileName = `${file.name}_${timestamp}`;
|
||||||
|
|
||||||
|
formData.append('blob', this.newPhoto.blob, fileName);
|
||||||
|
|
||||||
return formData;
|
return formData;
|
||||||
},
|
},
|
||||||
|
|
|
@ -6,7 +6,9 @@ describe('Salix', () => {
|
||||||
let $scope;
|
let $scope;
|
||||||
let $httpBackend;
|
let $httpBackend;
|
||||||
|
|
||||||
beforeEach(ngModule('salix'));
|
beforeEach(ngModule('salix', $translateProvider => {
|
||||||
|
$translateProvider.translations('en', {});
|
||||||
|
}));
|
||||||
|
|
||||||
beforeEach(inject(($componentController, $rootScope, _$httpBackend_) => {
|
beforeEach(inject(($componentController, $rootScope, _$httpBackend_) => {
|
||||||
$scope = $rootScope.$new();
|
$scope = $rootScope.$new();
|
||||||
|
@ -14,12 +16,58 @@ describe('Salix', () => {
|
||||||
const $element = angular.element('<vn-upload-photo></vn-upload-photo>');
|
const $element = angular.element('<vn-upload-photo></vn-upload-photo>');
|
||||||
controller = $componentController('vnUploadPhoto', {$element, $scope});
|
controller = $componentController('vnUploadPhoto', {$element, $scope});
|
||||||
controller.newPhoto = {};
|
controller.newPhoto = {};
|
||||||
|
controller.$t = m => m;
|
||||||
}));
|
}));
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
$scope.$destroy();
|
$scope.$destroy();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('viewportSelection()', () => {
|
||||||
|
it('should call to displayEditor() and updatePhotoPreview() methods', () => {
|
||||||
|
controller.displayEditor = jest.fn();
|
||||||
|
controller.updatePhotoPreview = jest.fn();
|
||||||
|
|
||||||
|
const files = [{name: 'test.jpg'}];
|
||||||
|
controller.newPhoto.files = files;
|
||||||
|
|
||||||
|
controller.viewportSelection = {code: 'normal'};
|
||||||
|
|
||||||
|
expect(controller.displayEditor).toHaveBeenCalledWith();
|
||||||
|
expect(controller.updatePhotoPreview).toHaveBeenCalledWith(files);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('displayEditor()', () => {
|
||||||
|
it('should define the editor property', () => {
|
||||||
|
controller.viewportSelection = {
|
||||||
|
code: 'normal',
|
||||||
|
description: 'Normal',
|
||||||
|
viewport: {
|
||||||
|
width: 400,
|
||||||
|
height: 400
|
||||||
|
},
|
||||||
|
output: {
|
||||||
|
width: 1200,
|
||||||
|
height: 1200
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const element = document.createElement('div');
|
||||||
|
|
||||||
|
jest.spyOn(document, 'getElementById').mockReturnValue(element);
|
||||||
|
|
||||||
|
controller.displayEditor();
|
||||||
|
|
||||||
|
const editor = controller.editor;
|
||||||
|
|
||||||
|
expect(editor).toBeDefined();
|
||||||
|
expect(editor.options.viewport.width).toEqual(400);
|
||||||
|
expect(editor.options.viewport.width).toEqual(400);
|
||||||
|
expect(editor.options.boundary.width).toEqual(600);
|
||||||
|
expect(editor.options.boundary.height).toEqual(600);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('onUploadAccept()', () => {
|
describe('onUploadAccept()', () => {
|
||||||
it('should throw an error message containing "Select an image"', () => {
|
it('should throw an error message containing "Select an image"', () => {
|
||||||
jest.spyOn(controller.vnApp, 'showError');
|
jest.spyOn(controller.vnApp, 'showError');
|
||||||
|
@ -29,13 +77,33 @@ describe('Salix', () => {
|
||||||
expect(controller.vnApp.showError).toHaveBeenCalledWith('Select an image');
|
expect(controller.vnApp.showError).toHaveBeenCalledWith('Select an image');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should call to the makeRequest() method', () => {
|
it('should call to the makeRequest() method', done => {
|
||||||
|
controller.editor = {
|
||||||
|
result: () => {}
|
||||||
|
};
|
||||||
|
|
||||||
jest.spyOn(controller, 'makeRequest');
|
jest.spyOn(controller, 'makeRequest');
|
||||||
|
jest.spyOn(controller.editor, 'result').mockReturnValue(new Promise(resolve => resolve('blobFile')));
|
||||||
|
|
||||||
|
controller.viewportSelection = {
|
||||||
|
code: 'normal',
|
||||||
|
description: 'Normal',
|
||||||
|
viewport: {
|
||||||
|
width: 400,
|
||||||
|
height: 400
|
||||||
|
},
|
||||||
|
output: {
|
||||||
|
width: 1200,
|
||||||
|
height: 1200
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
controller.newPhoto.files = [0];
|
controller.newPhoto.files = [0];
|
||||||
controller.onUploadAccept();
|
controller.onUploadAccept().then(() => {
|
||||||
|
expect(controller.newPhoto.blob).toEqual('blobFile');
|
||||||
expect(controller.makeRequest).toHaveBeenCalledWith();
|
expect(controller.makeRequest).toHaveBeenCalledWith();
|
||||||
|
done();
|
||||||
|
}).catch(done.fail);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -44,7 +112,11 @@ describe('Salix', () => {
|
||||||
jest.spyOn(controller.vnApp, 'showSuccess');
|
jest.spyOn(controller.vnApp, 'showSuccess');
|
||||||
jest.spyOn(controller, 'emit');
|
jest.spyOn(controller, 'emit');
|
||||||
|
|
||||||
|
$httpBackend.expectGET('ImageContainers/allowedContentTypes').respond(200, ['image/jpg']);
|
||||||
|
|
||||||
controller.newPhoto.files = [{name: 'hola'}];
|
controller.newPhoto.files = [{name: 'hola'}];
|
||||||
|
controller.newPhoto.blob = new Blob([]);
|
||||||
|
|
||||||
$httpBackend.expectRoute('POST', 'Images/upload').respond(200);
|
$httpBackend.expectRoute('POST', 'Images/upload').respond(200);
|
||||||
controller.makeRequest();
|
controller.makeRequest();
|
||||||
$httpBackend.flush();
|
$httpBackend.flush();
|
||||||
|
|
|
@ -1,3 +1,6 @@
|
||||||
Upload new photo: Subir una nueva foto
|
Edit photo: Editar foto
|
||||||
Select an image: Selecciona una imagen
|
Select an image: Selecciona una imagen
|
||||||
File name: Nombre del fichero
|
File name: Nombre del fichero
|
||||||
|
Rotate left: Girar a la izquierda
|
||||||
|
Rotate right: Girar a la derecha
|
||||||
|
Panoramic: Panorámico
|
|
@ -1,24 +1,9 @@
|
||||||
@import "./variables";
|
@import "./variables";
|
||||||
|
|
||||||
.upload-photo {
|
.upload-photo {
|
||||||
.photo {
|
|
||||||
position: relative;
|
|
||||||
margin: 0 auto;
|
|
||||||
text-align: center;
|
|
||||||
|
|
||||||
& > div {
|
& > vn-horizontal {
|
||||||
border: 3px solid $color-primary;
|
align-items: initial;
|
||||||
max-width: 256px;
|
|
||||||
max-height: 256px;
|
|
||||||
border-radius: 50%;
|
|
||||||
overflow: hidden
|
|
||||||
}
|
|
||||||
|
|
||||||
& > div > img[ng-src] {
|
|
||||||
width: 256px;
|
|
||||||
height: 256px;
|
|
||||||
display: block
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
& > vn-spinner {
|
& > vn-spinner {
|
||||||
|
@ -28,10 +13,13 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
vn-input-file {
|
vn-input-file {
|
||||||
max-width: 256px;
|
|
||||||
|
|
||||||
div.control {
|
div.control {
|
||||||
overflow: hidden
|
overflow: hidden
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.form {
|
||||||
|
align-items: initial;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue