2576 - Photo upload component #470

Merged
carlosjr merged 11 commits from 2576-photo_uploader into dev 2020-12-21 09:55:54 +00:00
52 changed files with 527 additions and 110 deletions
Showing only changes of commit 1ba1d27ba0 - Show all commits

10
.gitignore vendored
View File

@ -1,11 +1,11 @@
coverage coverage
node_modules node_modules
dist dist
e2e/dms/*/ storage
!e2e/dms/c4c !storage/dms/c4c
!e2e/dms/c81 !storage/dms/c81
!e2e/dms/ecc !storage/dms/ecc
!e2e/dms/a87 !storage/dms/a87
npm-debug.log npm-debug.log
.eslintcache .eslintcache
datasources.*.json datasources.*.json

View File

@ -84,7 +84,7 @@ module.exports = Self => {
}; };
async function uploadNewFile(ctx, dms, myOptions) { async function uploadNewFile(ctx, dms, myOptions) {
const storageConnector = Self.app.dataSources.storage.connector; const storageConnector = Self.app.dataSources.dmsStorage.connector;
const models = Self.app.models; const models = Self.app.models;
const fileOptions = {}; const fileOptions = {};

View File

@ -46,7 +46,7 @@ module.exports = Self => {
}); });
Self.uploadFile = async(ctx, options) => { Self.uploadFile = async(ctx, options) => {
const storageConnector = Self.app.dataSources.storage.connector; const storageConnector = Self.app.dataSources.dmsStorage.connector;
const models = Self.app.models; const models = Self.app.models;
const fileOptions = {}; const fileOptions = {};
const args = ctx.args; const args = ctx.args;
@ -98,7 +98,7 @@ module.exports = Self => {
async function createDms(ctx, file, myOptions) { async function createDms(ctx, file, myOptions) {
const models = Self.app.models; const models = Self.app.models;
const storageConnector = Self.app.dataSources.storage.connector; const storageConnector = Self.app.dataSources.dmsStorage.connector;
const myUserId = ctx.req.accessToken.userId; const myUserId = ctx.req.accessToken.userId;
const myWorker = await models.Worker.findOne({where: {userFk: myUserId}}, myOptions); const myWorker = await models.Worker.findOne({where: {userFk: myUserId}}, myOptions);
const args = ctx.args; const args = ctx.args;
@ -121,7 +121,6 @@ module.exports = Self => {
return newDms.updateAttribute('file', fileName, myOptions); return newDms.updateAttribute('file', fileName, myOptions);
} }
/** /**
* Returns a container instance * Returns a container instance
* If doesn't exists creates a new one * If doesn't exists creates a new one

View File

@ -2,7 +2,7 @@ const UserError = require('vn-loopback/util/user-error');
const fs = require('fs-extra'); const fs = require('fs-extra');
module.exports = Self => { module.exports = Self => {
Self.remoteMethod('download', { Self.remoteMethodCtx('download', {
description: 'Get the user image', description: 'Get the user image',
accessType: 'READ', accessType: 'READ',
accepts: [ accepts: [
@ -49,15 +49,9 @@ module.exports = Self => {
} }
}); });
Self.download = async function(collection, size, id) { Self.download = async function(ctx, collection, size, id) {
const models = Self.app.models; const models = Self.app.models;
const filter = { const filter = {where: {name: collection}};
where: {
name: collection},
include: {
relation: 'readRole'
}
};
const imageCollection = await models.ImageCollection.findOne(filter); const imageCollection = await models.ImageCollection.findOne(filter);
const entity = await models[imageCollection.model].findById(id, { const entity = await models[imageCollection.model].findById(id, {
fields: ['id', imageCollection.property] fields: ['id', imageCollection.property]
@ -69,28 +63,22 @@ module.exports = Self => {
if (!image) return false; if (!image) return false;
const imageRole = imageCollection.readRole().name; const hasReadRole = models.ImageCollection.hasReadRole(ctx, collection);
const hasRole = await models.Account.hasRole(id, imageRole); if (!hasReadRole)
if (!hasRole)
throw new UserError(`You don't have enough privileges`); throw new UserError(`You don't have enough privileges`);
let file; const container = await models.ImageContainer.getContainer(collection);
let env = process.env.NODE_ENV; const rootPath = container.client.root;
if (env && env != 'development') { const file = {
file = { path: `${rootPath}/${collection}/${size}/${image.name}.png`,
path: `/var/lib/salix/image/${collection}/${size}/${image.name}.png`, contentType: 'image/png',
contentType: 'image/png', name: `${image.name}.png`
name: `${image.name}.png` };
};
} else { if (!fs.existsSync(file.path)) return [];
file = {
path: `${process.cwd()}/storage/image/${collection}/${size}/${image.name}.png`,
contentType: 'image/png',
name: `${image.name}.png`
};
}
await fs.access(file.path); await fs.access(file.path);
let stream = fs.createReadStream(file.path); const stream = fs.createReadStream(file.path);
return [stream, file.contentType, `filename="${file.name}"`]; return [stream, file.contentType, `filename="${file.name}"`];
}; };
}; };

View File

@ -0,0 +1,77 @@
const UserError = require('vn-loopback/util/user-error');
const fs = require('fs-extra');
module.exports = Self => {
Self.remoteMethodCtx('upload', {
description: 'Uploads a file and inserts into dms model',
accessType: 'WRITE',
accepts: [
{
arg: 'id',
type: 'Number',
description: 'The entity id',
required: true
},
{
arg: 'collection',
type: 'string',
description: 'The collection name',
required: true
}],
returns: {
type: 'Object',
root: true
},
http: {
path: `/upload`,
verb: 'POST'
}
});
Self.upload = async ctx => {
const models = Self.app.models;
const fileOptions = {};
const args = ctx.args;
const hasWriteRole = await models.ImageCollection.hasWriteRole(ctx, args.collection);
if (!hasWriteRole)
throw new UserError(`You don't have enough privileges`);
if (process.env.NODE_ENV == 'test')
throw new UserError(`You can't upload images on the test instance`);
// Upload file to temporary path
const container = await getContainer(args.collection);
const uploaded = await models.ImageContainer.upload(container.name, ctx.req, ctx.result, fileOptions);
const [uploadedFile] = Object.values(uploaded.files).map(file => {
return file[0];
});
const file = await models.ImageContainer.getFile(container.name, uploadedFile.name);
const srcFile = `${file.client.root}/${file.container}/${file.name}`;
await models.Image.registerImage(container.name, srcFile, args.id);
};
Review

discard

discard
/**
* Returns a container instance
* If doesn't exists creates a new one
*
* @param {String} name Container name
* @return {Object} Container instance
*/
async function getContainer(name) {
const models = Self.app.models;
let container;
try {
container = await models.ImageContainer.getContainer(name);
} catch (err) {
if (err.code === 'ENOENT') {
container = await models.ImageContainer.createContainer({
name: name
});
} else throw err;
}
return container;
}
};

View File

@ -18,7 +18,7 @@
"dataSource": "vn" "dataSource": "vn"
}, },
"Container": { "Container": {
"dataSource": "storage" "dataSource": "dmsStorage"
}, },
"Continent": { "Continent": {
"dataSource": "vn" "dataSource": "vn"
@ -44,6 +44,9 @@
"ImageCollectionSize": { "ImageCollectionSize": {
"dataSource": "vn" "dataSource": "vn"
}, },
"ImageContainer": {
"dataSource": "imageStorage"
},
"Language": { "Language": {
"dataSource": "vn" "dataSource": "vn"
}, },

View File

@ -14,7 +14,7 @@ module.exports = Self => {
}; };
Self.getFile = async function(id) { Self.getFile = async function(id) {
const storageConnector = Self.app.dataSources.storage.connector; const storageConnector = Self.app.dataSources.dmsStorage.connector;
const models = Self.app.models; const models = Self.app.models;
const dms = await Self.findById(id); const dms = await Self.findById(id);
const pathHash = storageConnector.getPathHash(dms.id); const pathHash = storageConnector.getPathHash(dms.id);

View File

@ -0,0 +1,63 @@
module.exports = Self => {
/**
* Checks if current user has
* read privileges over a collection
*
* @param {object} ctx - Request context
* @param {interger} name - Collection name
* @param {object} options - Query options
* @return {boolean} True for user with read privileges
*/
Self.hasReadRole = async(ctx, name, options) => {
const collection = await Self.findOne({where: {name}}, {
include: {
relation: 'readRole'
}
}, options);
return await hasRole(ctx, collection, options);
};
/**
* Checks if current user has
* write privileges over a collection
*
* @param {object} ctx - Request context
* @param {string} name - Collection name
* @param {object} options - Query options
* @return {boolean} True for user with write privileges
*/
Self.hasWriteRole = async(ctx, name, options) => {
const collection = await Self.findOne({where: {name}}, {
include: {
relation: 'writeRole'
}
}, options);
return await hasRole(ctx, collection, options);
};
/**
* Checks if current user has
* read or write privileges
* @param {Object} ctx - Context
* @param {Object} collection - Collection [read/write]
* @param {Object} options - Query options
*/
async function hasRole(ctx, collection, options) {
const models = Self.app.models;
const myUserId = ctx.req.accessToken.userId;
const readRole = collection.readRole() && collection.readRole().name;
const writeRole = collection.writeRole() && collection.writeRole().name;
const requiredRole = readRole || writeRole;
const hasRequiredRole = await models.Account.hasRole(myUserId, requiredRole, options);
const isRoot = await models.Account.hasRole(myUserId, 'root', options);
if (isRoot || hasRequiredRole)
return true;
return false;
}
};

View File

@ -48,7 +48,12 @@
"type": "belongsTo", "type": "belongsTo",
"model": "Role", "model": "Role",
"foreignKey": "readRoleFk" "foreignKey": "readRoleFk"
} },
"writeRole": {
"type": "belongsTo",
"model": "Role",
"foreignKey": "writeRoleFk"
}
}, },
"acls": [ "acls": [
{ {

View File

@ -0,0 +1,13 @@
{
"name": "ImageContainer",
"base": "VnModel",
"idInjection": true,
"options": {
"validateUpsert": true
},
"properties": {},
"validations": [],
"relations": {},
"acls": [],
"methods": []
}

View File

@ -4,12 +4,9 @@ const path = require('path');
module.exports = Self => { module.exports = Self => {
require('../methods/image/download')(Self); require('../methods/image/download')(Self);
require('../methods/image/upload')(Self);
Self.getPath = function() { Self.registerImage = async(collectionName, srcFile, entityId) => {
return '/var/lib/salix/image';
};
Self.registerImage = async(collectionName, file, srcFilePath) => {
const models = Self.app.models; const models = Self.app.models;
const tx = await Self.beginTransaction({}); const tx = await Self.beginTransaction({});
const myOptions = {transaction: tx}; const myOptions = {transaction: tx};
@ -33,8 +30,8 @@ module.exports = Self => {
} }
}, myOptions); }, myOptions);
const file = srcFile.split('/').pop();
const fileName = file.split('.')[0]; const fileName = file.split('.')[0];
const rootPath = Self.getPath();
const data = { const data = {
name: fileName, name: fileName,
collectionFk: collectionName collectionFk: collectionName
@ -47,6 +44,8 @@ module.exports = Self => {
}, myOptions); }, myOptions);
// Resizes and saves the image // Resizes and saves the image
const container = await models.ImageContainer.getContainer(collectionName);
const rootPath = container.client.root;
const collectionDir = path.join(rootPath, collectionName); const collectionDir = path.join(rootPath, collectionName);
const dstDir = path.join(collectionDir, 'full'); const dstDir = path.join(collectionDir, 'full');
const dstFile = path.join(dstDir, file); const dstFile = path.join(dstDir, file);
@ -57,7 +56,7 @@ module.exports = Self => {
}; };
await fs.mkdir(dstDir, {recursive: true}); await fs.mkdir(dstDir, {recursive: true});
await sharp(srcFilePath, {failOnError: false}) await sharp(srcFile, {failOnError: false})
.resize(collection.maxWidth, collection.maxHeight, resizeOpts) .resize(collection.maxWidth, collection.maxHeight, resizeOpts)
.png() .png()
.toFile(dstFile); .toFile(dstFile);
@ -72,7 +71,7 @@ module.exports = Self => {
}; };
await fs.mkdir(dstDir, {recursive: true}); await fs.mkdir(dstDir, {recursive: true});
await sharp(srcFilePath, {failOnError: false}) await sharp(srcFile, {failOnError: false})
.resize(size.width, size.height, resizeOpts) .resize(size.width, size.height, resizeOpts)
.png() .png()
.toFile(dstFile); .toFile(dstFile);
@ -83,7 +82,7 @@ module.exports = Self => {
if (!model) if (!model)
throw new Error('Matching model not found'); throw new Error('Matching model not found');
const item = await model.findById(fileName, null, myOptions); const item = await model.findById(entityId, null, myOptions);
if (item) { if (item) {
await item.updateAttribute( await item.updateAttribute(
collection.property, collection.property,
@ -92,8 +91,8 @@ module.exports = Self => {
); );
} }
if (fs.existsSync(srcFilePath)) if (fs.existsSync(srcFile))
await fs.unlink(srcFilePath); await fs.unlink(srcFile);
await tx.commit(); await tx.commit();
return newImage; return newImage;

View File

@ -0,0 +1 @@
INSERT INTO salix.ACL (model, property, accessType, permission, principalType, principalId) VALUES ('Image', '*', 'WRITE', 'ALLOW', 'ROLE', 'employee')

View File

@ -0,0 +1,8 @@
ALTER TABLE `hedera`.`imageCollection`
ADD writeRoleFk INT NULL DEFAULT 1;
ALTER TABLE `hedera`.`imageCollection`
ADD CONSTRAINT role_id___fk
FOREIGN KEY (writeRoleFk) REFERENCES account.role (id)
ON UPDATE CASCADE;

View File

@ -447,6 +447,25 @@ INSERT INTO `imageCollection` VALUES (1,'catalog','Artículo',3840,2160,'Item','
/*!40000 ALTER TABLE `imageCollection` ENABLE KEYS */; /*!40000 ALTER TABLE `imageCollection` ENABLE KEYS */;
UNLOCK TABLES; UNLOCK TABLES;
--
-- Dumping data for table `imageCollectionSize`
--
LOCK TABLES `imageCollectionSize` WRITE;
/*!40000 ALTER TABLE `imageCollectionSize` DISABLE KEYS */;
INSERT INTO `imageCollectionSize` (`id`, `collectionFk`, `width`, `height`, `crop`)
VALUES
(2, 1, 50, 50, 1),
(3, 1, 200, 200, 1),
(5, 5, 200, 200, 1),
(6, 1, 70, 70, 1),
(8, 5, 50, 50, 1),
(9, 1, 1600, 900, 0),
(13, 6, 160, 160, 1),
(14, 6, 520, 520, 1),
(15, 6, 1600, 1600, 1);
/*!40000 ALTER TABLE `imageCollectionSize` ENABLE KEYS */;
UNLOCK TABLES;
-- --
-- Dumping data for table `tpvError` -- Dumping data for table `tpvError`
-- --

View File

@ -1 +0,0 @@
File: 4.txt. It works!

View File

@ -1 +0,0 @@
It works!

View File

@ -1 +0,0 @@
It works!

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

View File

@ -1 +0,0 @@
It works!

View File

@ -46,7 +46,7 @@ export function directive($timeout) {
$element.on('click', function(event) { $element.on('click', function(event) {
if (event.defaultPrevented) return; if (event.defaultPrevented) return;
let src = $attrs.zoomImage || $attrs.src; let src = $element[0].getAttribute('zoom-image') || $element[0].src;
if (src) if (src)
createContainers(src); createContainers(src);
else else

View File

@ -5,6 +5,24 @@
vn-descriptor-content { vn-descriptor-content {
display: block; display: block;
.photo {
position: relative;
& > img[ng-src] {
min-height: 16em;
display: block;
height: 100%;
width: 100%;
}
vn-float-button {
position: absolute;
margin: 1em;
bottom: 0;
right: 0
}
}
& > vn-spinner { & > vn-spinner {
display: block; display: block;
height: 40px; height: 40px;

View File

@ -13,3 +13,4 @@ import './section';
import './summary'; import './summary';
import './topbar/topbar'; import './topbar/topbar';
import './user-popover'; import './user-popover';
import './upload-photo';

View File

@ -0,0 +1,23 @@
<vn-dialog class="edit"
vn-id="dialog"
on-accept="$ctrl.onUploadAccept()"
message="Upload new photo">
<tpl-body class="upload-photo">
<vn-horizontal ng-show="file.value" class="photo vn-mb-md">
<div><img vn-id="photo" ng-src=""/></div>
</vn-horizontal>
<vn-horizontal>
<vn-input-file vn-id="file"
vn-one
label="File"
ng-model="$ctrl.newPhoto.files"
on-change="$ctrl.updatePhotoPreview(value)"
required="true">
</vn-input-file>
</vn-horizontal>
</tpl-body>
<tpl-buttons>
<input type="button" response="cancel" translate-attr="{value: 'Cancel'}"/>
<button response="accept" translate>Upload</button>
</tpl-buttons>
</vn-dialog>

View File

@ -0,0 +1,87 @@
import ngModule from '../../module';
import Component from 'core/lib/component';
import './style.scss';
/**
* Small card with basing entity information and actions.
*/
export default class UploadPhoto extends Component {
/**
* Opens the dialog and sets the default data
* @param {*} collection - Collection name
* @param {*} id - Entity id
*/
show(collection, id) {
this.newPhoto = {id, collection};
this.$.dialog.show();
}
/**
* Updates the image preview
*
* @param {string} value
*/
updatePhotoPreview(value) {
if (value && value[0]) {
const reader = new FileReader();
reader.onload = e => this.$.photo.src = e.target.result;
reader.readAsDataURL(value[0]);
}
}
/**
* Dialog response handler
*
* @return {boolean} Response
*/
onUploadAccept() {
try {
if (!this.newPhoto.files)
throw new Error(`Select an image`);
this.makeRequest();
} catch (e) {
this.vnApp.showError(this.$t(e.message));
return false;
}
return true;
}
/**
* Performs a cancellable request.
*
*/
makeRequest() {
if (this.canceler) this.canceler.resolve();
this.canceler = this.$q.defer();
const options = {
method: 'POST',
url: `Images/upload`,
params: this.newPhoto,
headers: {'Content-Type': undefined},
timeout: this.canceler.promise,
transformRequest: files => {
const formData = new FormData();
for (let i = 0; i < files.length; i++)
formData.append(files[i].name, files[i]);
return formData;
},
data: this.newPhoto.files
};
this.$http(options)
.then(() => this.vnApp.showSuccess(this.$t('Data saved!')))
.then(() => this.emit('response'))
.finally(() => this.canceler = null);
}
}
ngModule.vnComponent('vnUploadPhoto', {
controller: UploadPhoto,
template: require('./index.html'),
bindings: {
data: '<'
}
});

View File

@ -0,0 +1,2 @@
Upload new photo: Subir una nueva foto
Select an image: Selecciona una imagen

View File

@ -0,0 +1,31 @@
@import "./variables";
.upload-photo {
.photo {
position: relative;
margin: 0 auto;
text-align: center;
& > div {
border: 3px solid $color-primary;
max-width: 256px;
max-height: 256px;
border-radius: 50%;
overflow: hidden
}
& > div > img[ng-src] {
width: 256px;
height: 256px;
display: block;
height: 100%;
width: 100%;
}
}
& > vn-spinner {
display: block;
height: 40px;
padding: $spacing-md;
}
}

View File

@ -14,7 +14,7 @@
<vn-vertical class="user-popover vn-pa-md"> <vn-vertical class="user-popover vn-pa-md">
<div class="profile-card vn-pb-md"> <div class="profile-card vn-pb-md">
<img <img
ng-src="{{$ctrl.getImageUrl($root.user.id)}}" ng-src="{{::$root.imagePath('user', '160x160', $root.user.id)}}"
on-error-src/> on-error-src/>
<div class="vn-pl-sm"> <div class="vn-pl-sm">
<div> <div>

View File

@ -78,10 +78,6 @@ class Controller {
this.$.companies.refresh(); this.$.companies.refresh();
this.$.popover.show(event.target); this.$.popover.show(event.target);
} }
getImageUrl(userId) {
return '/api/Images/user/160x160/' + userId + '/download?access_token=' + this.vnToken.token;
}
} }
Controller.$inject = ['$scope', '$translate', 'vnConfig', 'vnAuth', 'vnToken']; Controller.$inject = ['$scope', '$translate', 'vnConfig', 'vnAuth', 'vnToken'];

View File

@ -7,9 +7,14 @@ export const appName = 'salix';
const ngModule = ng.module('salix', ['vnCore']); const ngModule = ng.module('salix', ['vnCore']);
export default ngModule; export default ngModule;
run.$inject = ['$window', '$rootScope', 'vnAuth', 'vnApp', '$state']; run.$inject = ['$window', '$rootScope', 'vnAuth', 'vnApp', 'vnToken', '$state'];
export function run($window, $rootScope, vnAuth, vnApp, $state) { export function run($window, $rootScope, vnAuth, vnApp, vnToken, $state) {
$rootScope.imagePath = appConfig.imagePath; $rootScope.imagePath = (collection, size, id) => {
if (!collection || !size || !id) return;
const basePath = `/api/Images/${collection}/${size}/${id}`;
return `${basePath}/download?access_token=${vnToken.token}`;
};
$window.validations = {}; $window.validations = {};
vnApp.name = appName; vnApp.name = appName;

View File

@ -160,5 +160,6 @@
"The social name cannot be empty": "La razón social no puede quedar en blanco", "The social name cannot be empty": "La razón social no puede quedar en blanco",
"The nif cannot be empty": "El NIF no puede quedar en blanco", "The nif cannot be empty": "El NIF no puede quedar en blanco",
"You need to fill sage information before you check verified data": "Debes rellenar la información de sage antes de marcar datos comprobados", "You need to fill sage information before you check verified data": "Debes rellenar la información de sage antes de marcar datos comprobados",
"ASSIGN_ZONE_FIRST": "Asigna una zona primero" "ASSIGN_ZONE_FIRST": "Asigna una zona primero",
"You can't upload images on the test environment": "No puedes subir imágenes en el entorno de pruebas"
} }

View File

@ -2,7 +2,7 @@ const uuid = require('uuid/v1');
const md5 = require('md5'); const md5 = require('md5');
module.exports = app => { module.exports = app => {
const storageConnector = app.dataSources.storage.connector; const storageConnector = app.dataSources.dmsStorage.connector;
storageConnector.getFilename = function(file) { storageConnector.getFilename = function(file) {
return `${uuid()}.${storageConnector.getFileExtension(file.name)}`; return `${uuid()}.${storageConnector.getFileExtension(file.name)}`;
@ -15,4 +15,17 @@ module.exports = app => {
storageConnector.getPathHash = function(id) { storageConnector.getPathHash = function(id) {
return md5(id.toString()).substring(0, 3); return md5(id.toString()).substring(0, 3);
}; };
const imageStorageConnector = app.dataSources.imageStorage.connector;
imageStorageConnector.getFilename = function(file) {
return `${uuid()}.png`;
};
/* imageStorageConnector.getFileExtension = function(fileName) {
return fileName.split('.').pop().toLowerCase();
};
imageStorageConnector.getPathHash = function(id) {
return md5(id.toString()).substring(0, 3);
}; */
}; };

View File

@ -17,11 +17,11 @@
"connectTimeout": 40000, "connectTimeout": 40000,
"acquireTimeout": 20000 "acquireTimeout": 20000
}, },
"storage": { "dmsStorage": {
"name": "storage", "name": "dmsStorage",
"connector": "loopback-component-storage", "connector": "loopback-component-storage",
"provider": "filesystem", "provider": "filesystem",
"root": "./e2e/dms", "root": "./storage/dms",
"maxFileSize": "262144000", "maxFileSize": "262144000",
"allowedContentTypes": [ "allowedContentTypes": [
"application/x-7z-compressed", "application/x-7z-compressed",
@ -36,5 +36,17 @@
"image/jpeg", "image/jpeg",
"image/jpg" "image/jpg"
] ]
},
"imageStorage": {
"name": "imageStorage",
"connector": "loopback-component-storage",
"provider": "filesystem",
"root": "./storage/image",
"maxFileSize": "52428800",
"allowedContentTypes": [
"image/png",
"image/jpeg",
"image/jpg"
]
} }
} }

View File

@ -13,7 +13,7 @@ module.exports = Self => {
}); });
Self.allowedContentTypes = async() => { Self.allowedContentTypes = async() => {
const storageConnector = Self.app.dataSources.storage.connector; const storageConnector = Self.app.dataSources.dmsStorage.connector;
const allowedContentTypes = storageConnector.allowedContentTypes; const allowedContentTypes = storageConnector.allowedContentTypes;
const modelAllowedContentTypes = Self.definition.settings.allowedContentTypes; const modelAllowedContentTypes = Self.definition.settings.allowedContentTypes;

View File

@ -13,7 +13,7 @@ module.exports = Self => {
}); });
Self.allowedContentTypes = async() => { Self.allowedContentTypes = async() => {
const storageConnector = Self.app.dataSources.storage.connector; const storageConnector = Self.app.dataSources.dmsStorage.connector;
const allowedContentTypes = storageConnector.allowedContentTypes; const allowedContentTypes = storageConnector.allowedContentTypes;
const modelAllowedContentTypes = Self.definition.settings.allowedContentTypes; const modelAllowedContentTypes = Self.definition.settings.allowedContentTypes;

View File

@ -70,8 +70,8 @@
</vn-td> </vn-td>
<vn-td shrink > <vn-td shrink >
<img <img
ng-src="{{::$root.imagePath}}/catalog/50x50/{{::buy.image}}" ng-src="{{::$root.imagePath('catalog', '50x50', buy.itemFk)}}"
zoom-image="{{::$root.imagePath}}/catalog/1600x900/{{::buy.image}}" zoom-image="{{::$root.imagePath('catalog', '1600x900', buy.itemFk)}}"
vn-click-stop vn-click-stop
on-error-src/> on-error-src/>
</vn-td> </vn-td>

View File

@ -57,7 +57,7 @@ module.exports = Self => {
writeStream.on('finish', async function() { writeStream.on('finish', async function() {
try { try {
await models.Image.registerImage('catalog', fileName, filePath); await models.Image.registerImage('catalog', filePath, image.itemFk);
await image.destroy(); await image.destroy();
} catch (error) { } catch (error) {
await errorHandler(image.itemFk, error, filePath); await errorHandler(image.itemFk, error, filePath);

View File

@ -1,5 +1,5 @@
<vn-portal slot="menu"> <vn-portal slot="menu">
<vn-item-descriptor item="$ctrl.item"></vn-item-descriptor> <vn-item-descriptor item="$ctrl.item" card-reload="$ctrl.reload()"></vn-item-descriptor>
<vn-left-menu source="card"></vn-left-menu> <vn-left-menu source="card"></vn-left-menu>
</vn-portal> </vn-portal>
<ui-view></ui-view> <ui-view></ui-view>

View File

@ -16,19 +16,15 @@
</vn-item> </vn-item>
</slot-menu> </slot-menu>
<slot-before> <slot-before>
<div style="position: relative" text-center> <div class="photo" text-center>
<img <img vn-id="photo"
ng-src="{{::$root.imagePath}}/catalog/200x200/{{$ctrl.item.image}}" ng-src="{{$root.imagePath('catalog', '200x200', $ctrl.item.id)}}"
zoom-image="{{::$root.imagePath}}/catalog/1600x900/{{$ctrl.item.image}}" zoom-image="{{$root.imagePath('catalog', '1600x900', $ctrl.item.id)}}"
on-error-src on-error-src/>
/> <vn-float-button ng-click="uploadPhoto.show('catalog', $ctrl.item.id)"
<a href="//verdnatura.es/#!form=admin/items&filter={{$ctrl.item.id}}" target="_blank"> icon="edit"
<vn-float-button vn-visible-by="marketing, buyer">
icon="edit" </vn-float-button>
style="position: absolute; margin: 1em; bottom: 0; right: 0;"
vn-visible-by="marketing, buyer">
</vn-float-button>
</a>
</div> </div>
<vn-horizontal class="item-state"> <vn-horizontal class="item-state">
<vn-one> <vn-one>
@ -102,4 +98,10 @@
</vn-confirm> </vn-confirm>
<vn-worker-descriptor-popover <vn-worker-descriptor-popover
vn-id="workerDescriptor"> vn-id="workerDescriptor">
</vn-worker-descriptor-popover> </vn-worker-descriptor-popover>
<!-- Upload photo dialog -->
<vn-upload-photo
vn-id="uploadPhoto"
on-response="$ctrl.onUploadResponse()">
</vn-upload-photo>

View File

@ -3,6 +3,11 @@ import Descriptor from 'salix/components/descriptor';
import './style.scss'; import './style.scss';
class Controller extends Descriptor { class Controller extends Descriptor {
constructor($element, $, $rootScope) {
super($element, $);
this.$rootScope = $rootScope;
}
get item() { get item() {
return this.entity; return this.entity;
} }
@ -65,13 +70,27 @@ class Controller extends Descriptor {
this.$http.post(`Items/${this.item.id}/clone`) this.$http.post(`Items/${this.item.id}/clone`)
.then(res => this.$state.go('item.card.tags', {id: res.data.id})); .then(res => this.$state.go('item.card.tags', {id: res.data.id}));
} }
onUploadResponse() {
const timestamp = new Date().getTime();
const src = this.$rootScope.imagePath('catalog', '200x200', this.item.id);
const zoomSrc = this.$rootScope.imagePath('catalog', '1600x900', this.item.id);
const newSrc = `${src}&t=${timestamp}`;
const newZoomSrc = `${zoomSrc}&t=${timestamp}`;
this.$.photo.setAttribute('src', newSrc);
this.$.photo.setAttribute('zoom-image', newZoomSrc);
}
} }
Controller.$inject = ['$element', '$scope', '$rootScope'];
ngModule.vnComponent('vnItemDescriptor', { ngModule.vnComponent('vnItemDescriptor', {
template: require('./index.html'), template: require('./index.html'),
controller: Controller, controller: Controller,
bindings: { bindings: {
item: '<', item: '<',
dated: '<' dated: '<',
cardReload: '&'
} }
}); });

View File

@ -35,8 +35,8 @@
ui-sref="item.card.summary({id: item.id})"> ui-sref="item.card.summary({id: item.id})">
<vn-td shrink> <vn-td shrink>
<img <img
ng-src="{{::$root.imagePath}}/catalog/50x50/{{::item.image}}" ng-src="{{::$root.imagePath('catalog', '50x50', item.id)}}"
zoom-image="{{::$root.imagePath}}/catalog/1600x900/{{::item.image}}" zoom-image="{{::$root.imagePath('catalog', '1600x900', item.id)}}"
vn-click-stop vn-click-stop
on-error-src/> on-error-src/>
</vn-td> </vn-td>
@ -44,7 +44,7 @@
<span <span
vn-click-stop="itemDescriptor.show($event, item.id)" vn-click-stop="itemDescriptor.show($event, item.id)"
class="link"> class="link">
{{::item.id | zeroFill:6}} {{::item.id}}
</span> </span>
</vn-td> </vn-td>
<vn-td shrink>{{::item.grouping | dashIfEmpty}}</vn-td> <vn-td shrink>{{::item.grouping | dashIfEmpty}}</vn-td>

View File

@ -11,8 +11,8 @@
<vn-horizontal> <vn-horizontal>
<vn-one> <vn-one>
<img style="width: 100%; display: block;" <img style="width: 100%; display: block;"
ng-src="{{::$root.imagePath}}/catalog/200x200/{{$ctrl.item.image}}" ng-src="{{$root.imagePath('catalog', '200x200', $ctrl.item.id)}}"
zoom-image="{{::$root.imagePath}}/catalog/1600x900/{{$ctrl.item.image}}" on-error-src/> zoom-image="{{$root.imagePath('catalog', '1600x900', $ctrl.item.id)}}" on-error-src/>
<vn-horizontal class="item-state"> <vn-horizontal class="item-state">
<vn-one> <vn-one>
<p translate>Visible</p> <p translate>Visible</p>

View File

@ -5,11 +5,11 @@
<vn-card> <vn-card>
<div class="image"> <div class="image">
<div ng-if="::item.hex != null" class="item-color-background"> <div ng-if="::item.hex != null" class="item-color-background">
<div class="item-color" style="background-color: #{{::item.hex}}"></div> <div class="item-color" ng-style="{'background-color': '#' + item.hex}"></div>
</div> </div>
<img <img
ng-src="{{::$root.imagePath}}/catalog/200x200/{{::item.image}}" ng-src="{{::$root.imagePath('catalog', '200x200', item.id)}}"
zoom-image="{{::$root.imagePath}}/catalog/1600x900/{{::item.image}}" zoom-image="{{::$root.imagePath('catalog', '1600x900', item.id)}}"
on-error-src/> on-error-src/>
</div> </div>
<div class="description"> <div class="description">

View File

@ -32,8 +32,8 @@
<vn-tr ng-repeat="row in $ctrl.rows"> <vn-tr ng-repeat="row in $ctrl.rows">
<vn-td shrink> <vn-td shrink>
<img <img
ng-src="{{::$root.imagePath}}/catalog/50x50/{{::row.item.image}}" ng-src="{{::$root.imagePath('catalog', '50x50', row.item.id)}}"
zoom-image="{{::$root.imagePath}}/catalog/1600x900/{{::row.item.image}}" zoom-image="{{::$root.imagePath('catalog', '1600x900', row.item.id)}}"
on-error-src/> on-error-src/>
</vn-td> </vn-td>
<vn-td number> <vn-td number>

View File

@ -13,7 +13,7 @@ module.exports = Self => {
}); });
Self.allowedContentTypes = async() => { Self.allowedContentTypes = async() => {
const storageConnector = Self.app.dataSources.storage.connector; const storageConnector = Self.app.dataSources.dmsStorage.connector;
const allowedContentTypes = storageConnector.allowedContentTypes; const allowedContentTypes = storageConnector.allowedContentTypes;
const modelAllowedContentTypes = Self.definition.settings.allowedContentTypes; const modelAllowedContentTypes = Self.definition.settings.allowedContentTypes;

View File

@ -19,8 +19,8 @@
<vn-card> <vn-card>
<div class="image"> <div class="image">
<img <img
ng-src="{{::$root.imagePath}}/catalog/200x200/{{::sale.item.image}}" ng-src="{{::$root.imagePath('catalog', '200x200', sale.itemFk)}}"
zoom-image="{{::$root.imagePath}}/catalog/1600x900/{{::sale.item.image}}" zoom-image="{{::$root.imagePath('catalog', '1600x900', sale.itemFk)}}"
on-error-src/> on-error-src/>
</div> </div>
<div class="description"> <div class="description">

View File

@ -93,8 +93,8 @@
</vn-td> </vn-td>
<vn-td shrink> <vn-td shrink>
<img <img
ng-src="{{::$root.imagePath}}/catalog/50x50/{{sale.image}}" ng-src="{{::$root.imagePath('catalog', '50x50', sale.itemFk)}}"
zoom-image="{{::$root.imagePath}}/catalog/1600x900/{{sale.image}}" zoom-image="{{::$root.imagePath('catalog', '1600x900', sale.itemFk)}}"
on-error-src/> on-error-src/>
</vn-td> </vn-td>
<vn-td number> <vn-td number>

View File

@ -13,7 +13,7 @@ module.exports = Self => {
}); });
Self.allowedContentTypes = async() => { Self.allowedContentTypes = async() => {
const storageConnector = Self.app.dataSources.storage.connector; const storageConnector = Self.app.dataSources.dmsStorage.connector;
const allowedContentTypes = storageConnector.allowedContentTypes; const allowedContentTypes = storageConnector.allowedContentTypes;
const modelAllowedContentTypes = Self.definition.settings.allowedContentTypes; const modelAllowedContentTypes = Self.definition.settings.allowedContentTypes;

View File

@ -46,7 +46,7 @@ class Controller extends Section {
warehouseId: warehouseId, warehouseId: warehouseId,
companyId: companyId, companyId: companyId,
dmsTypeId: dmsTypeId, dmsTypeId: dmsTypeId,
description: this.$t('FileDescription', { description: this.$t('TravelFileDescription', {
travelId: this.travel.id travelId: this.travel.id
}).toUpperCase() }).toUpperCase()
}; };

View File

@ -8,7 +8,7 @@ Upload file: Subir fichero
Edit file: Editar fichero Edit file: Editar fichero
Upload: Subir Upload: Subir
File: Fichero File: Fichero
FileDescription: Travel id {{travelId}} TravelFileDescription: Travel id {{travelId}}
ContentTypesInfo: 'Tipos de archivo permitidos: {{allowedContentTypes}}' ContentTypesInfo: 'Tipos de archivo permitidos: {{allowedContentTypes}}'
Are you sure you want to continue?: ¿Seguro que quieres continuar? Are you sure you want to continue?: ¿Seguro que quieres continuar?
Add thermograph: Añadir termógrafo Add thermograph: Añadir termógrafo

View File

@ -13,7 +13,7 @@ module.exports = Self => {
}); });
Self.allowedContentTypes = async() => { Self.allowedContentTypes = async() => {
const storageConnector = Self.app.dataSources.storage.connector; const storageConnector = Self.app.dataSources.dmsStorage.connector;
const allowedContentTypes = storageConnector.allowedContentTypes; const allowedContentTypes = storageConnector.allowedContentTypes;
const modelAllowedContentTypes = Self.definition.settings.allowedContentTypes; const modelAllowedContentTypes = Self.definition.settings.allowedContentTypes;

View File

@ -1,6 +1,18 @@
<vn-descriptor-content <vn-descriptor-content
module="worker" module="worker"
description="$ctrl.worker.firstName +' '+ $ctrl.worker.lastName"> description="$ctrl.worker.firstName +' '+ $ctrl.worker.lastName">
<slot-before>
<div class="photo" text-center>
<img vn-id="photo"
ng-src="{{$root.imagePath('user', '520x520', $ctrl.worker.id)}}"
zoom-image="{{$root.imagePath('user', '1600x900', $ctrl.worker.id)}}"
on-error-src/>
<vn-float-button ng-click="uploadPhoto.show('user', $ctrl.worker.id)"
icon="edit"
vn-visible-by="marketing, hr">
</vn-float-button>
</div>
</slot-before>
<slot-body> <slot-body>
<div class="attributes"> <div class="attributes">
<vn-label-value <vn-label-value
@ -42,4 +54,10 @@
<div ng-transclude="btnThree"></div> <div ng-transclude="btnThree"></div>
</div> </div>
</slot-body> </slot-body>
</vn-descriptor-content> </vn-descriptor-content>
<!-- Upload photo dialog -->
<vn-upload-photo
vn-id="uploadPhoto"
on-response="$ctrl.onUploadResponse()">
</vn-upload-photo>

View File

@ -2,6 +2,11 @@ import ngModule from '../module';
import Descriptor from 'salix/components/descriptor'; import Descriptor from 'salix/components/descriptor';
class Controller extends Descriptor { class Controller extends Descriptor {
constructor($element, $, $rootScope) {
super($element, $);
this.$rootScope = $rootScope;
}
get worker() { get worker() {
return this.entity; return this.entity;
} }
@ -48,8 +53,21 @@ class Controller extends Descriptor {
return this.getData(`Workers/${this.id}`, {filter}) return this.getData(`Workers/${this.id}`, {filter})
.then(res => this.entity = res.data); .then(res => this.entity = res.data);
} }
onUploadResponse() {
const timestamp = new Date().getTime();
const src = this.$rootScope.imagePath('user', '520x520', this.worker.id);
const zoomSrc = this.$rootScope.imagePath('user', '1600x900', this.worker.id);
const newSrc = `${src}&t=${timestamp}`;
const newZoomSrc = `${zoomSrc}&t=${timestamp}`;
this.$.photo.setAttribute('src', newSrc);
this.$.photo.setAttribute('zoom-image', newZoomSrc);
}
} }
Controller.$inject = ['$element', '$scope', '$rootScope'];
ngModule.vnComponent('vnWorkerDescriptor', { ngModule.vnComponent('vnWorkerDescriptor', {
template: require('./index.html'), template: require('./index.html'),
controller: Controller, controller: Controller,