2576 - Photo upload component
gitea/salix/pipeline/head There was a failure building this commit
Details
gitea/salix/pipeline/head There was a failure building this commit
Details
This commit is contained in:
parent
34c1115954
commit
1ba1d27ba0
|
@ -1,11 +1,11 @@
|
|||
coverage
|
||||
node_modules
|
||||
dist
|
||||
e2e/dms/*/
|
||||
!e2e/dms/c4c
|
||||
!e2e/dms/c81
|
||||
!e2e/dms/ecc
|
||||
!e2e/dms/a87
|
||||
storage
|
||||
!storage/dms/c4c
|
||||
!storage/dms/c81
|
||||
!storage/dms/ecc
|
||||
!storage/dms/a87
|
||||
npm-debug.log
|
||||
.eslintcache
|
||||
datasources.*.json
|
||||
|
|
|
@ -84,7 +84,7 @@ module.exports = Self => {
|
|||
};
|
||||
|
||||
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 fileOptions = {};
|
||||
|
||||
|
|
|
@ -46,7 +46,7 @@ module.exports = Self => {
|
|||
});
|
||||
|
||||
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 fileOptions = {};
|
||||
const args = ctx.args;
|
||||
|
@ -98,7 +98,7 @@ module.exports = Self => {
|
|||
|
||||
async function createDms(ctx, file, myOptions) {
|
||||
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 myWorker = await models.Worker.findOne({where: {userFk: myUserId}}, myOptions);
|
||||
const args = ctx.args;
|
||||
|
@ -121,7 +121,6 @@ module.exports = Self => {
|
|||
return newDms.updateAttribute('file', fileName, myOptions);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns a container instance
|
||||
* If doesn't exists creates a new one
|
||||
|
|
|
@ -2,7 +2,7 @@ const UserError = require('vn-loopback/util/user-error');
|
|||
const fs = require('fs-extra');
|
||||
|
||||
module.exports = Self => {
|
||||
Self.remoteMethod('download', {
|
||||
Self.remoteMethodCtx('download', {
|
||||
description: 'Get the user image',
|
||||
accessType: 'READ',
|
||||
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 filter = {
|
||||
where: {
|
||||
name: collection},
|
||||
include: {
|
||||
relation: 'readRole'
|
||||
}
|
||||
};
|
||||
const filter = {where: {name: collection}};
|
||||
const imageCollection = await models.ImageCollection.findOne(filter);
|
||||
const entity = await models[imageCollection.model].findById(id, {
|
||||
fields: ['id', imageCollection.property]
|
||||
|
@ -69,28 +63,22 @@ module.exports = Self => {
|
|||
|
||||
if (!image) return false;
|
||||
|
||||
const imageRole = imageCollection.readRole().name;
|
||||
const hasRole = await models.Account.hasRole(id, imageRole);
|
||||
if (!hasRole)
|
||||
const hasReadRole = models.ImageCollection.hasReadRole(ctx, collection);
|
||||
if (!hasReadRole)
|
||||
throw new UserError(`You don't have enough privileges`);
|
||||
|
||||
let file;
|
||||
let env = process.env.NODE_ENV;
|
||||
if (env && env != 'development') {
|
||||
file = {
|
||||
path: `/var/lib/salix/image/${collection}/${size}/${image.name}.png`,
|
||||
contentType: 'image/png',
|
||||
name: `${image.name}.png`
|
||||
};
|
||||
} else {
|
||||
file = {
|
||||
path: `${process.cwd()}/storage/image/${collection}/${size}/${image.name}.png`,
|
||||
contentType: 'image/png',
|
||||
name: `${image.name}.png`
|
||||
};
|
||||
}
|
||||
const container = await models.ImageContainer.getContainer(collection);
|
||||
const rootPath = container.client.root;
|
||||
const file = {
|
||||
path: `${rootPath}/${collection}/${size}/${image.name}.png`,
|
||||
contentType: 'image/png',
|
||||
name: `${image.name}.png`
|
||||
};
|
||||
|
||||
if (!fs.existsSync(file.path)) return [];
|
||||
|
||||
await fs.access(file.path);
|
||||
let stream = fs.createReadStream(file.path);
|
||||
const stream = fs.createReadStream(file.path);
|
||||
return [stream, file.contentType, `filename="${file.name}"`];
|
||||
};
|
||||
};
|
||||
|
|
|
@ -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);
|
||||
};
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
};
|
|
@ -18,7 +18,7 @@
|
|||
"dataSource": "vn"
|
||||
},
|
||||
"Container": {
|
||||
"dataSource": "storage"
|
||||
"dataSource": "dmsStorage"
|
||||
},
|
||||
"Continent": {
|
||||
"dataSource": "vn"
|
||||
|
@ -44,6 +44,9 @@
|
|||
"ImageCollectionSize": {
|
||||
"dataSource": "vn"
|
||||
},
|
||||
"ImageContainer": {
|
||||
"dataSource": "imageStorage"
|
||||
},
|
||||
"Language": {
|
||||
"dataSource": "vn"
|
||||
},
|
||||
|
|
|
@ -14,7 +14,7 @@ module.exports = Self => {
|
|||
};
|
||||
|
||||
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 dms = await Self.findById(id);
|
||||
const pathHash = storageConnector.getPathHash(dms.id);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
};
|
|
@ -48,7 +48,12 @@
|
|||
"type": "belongsTo",
|
||||
"model": "Role",
|
||||
"foreignKey": "readRoleFk"
|
||||
}
|
||||
},
|
||||
"writeRole": {
|
||||
"type": "belongsTo",
|
||||
"model": "Role",
|
||||
"foreignKey": "writeRoleFk"
|
||||
}
|
||||
},
|
||||
"acls": [
|
||||
{
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"name": "ImageContainer",
|
||||
"base": "VnModel",
|
||||
"idInjection": true,
|
||||
"options": {
|
||||
"validateUpsert": true
|
||||
},
|
||||
"properties": {},
|
||||
"validations": [],
|
||||
"relations": {},
|
||||
"acls": [],
|
||||
"methods": []
|
||||
}
|
|
@ -4,12 +4,9 @@ const path = require('path');
|
|||
|
||||
module.exports = Self => {
|
||||
require('../methods/image/download')(Self);
|
||||
require('../methods/image/upload')(Self);
|
||||
|
||||
Self.getPath = function() {
|
||||
return '/var/lib/salix/image';
|
||||
};
|
||||
|
||||
Self.registerImage = async(collectionName, file, srcFilePath) => {
|
||||
Self.registerImage = async(collectionName, srcFile, entityId) => {
|
||||
const models = Self.app.models;
|
||||
const tx = await Self.beginTransaction({});
|
||||
const myOptions = {transaction: tx};
|
||||
|
@ -33,8 +30,8 @@ module.exports = Self => {
|
|||
}
|
||||
}, myOptions);
|
||||
|
||||
const file = srcFile.split('/').pop();
|
||||
const fileName = file.split('.')[0];
|
||||
const rootPath = Self.getPath();
|
||||
const data = {
|
||||
name: fileName,
|
||||
collectionFk: collectionName
|
||||
|
@ -47,6 +44,8 @@ module.exports = Self => {
|
|||
}, myOptions);
|
||||
|
||||
// Resizes and saves the image
|
||||
const container = await models.ImageContainer.getContainer(collectionName);
|
||||
const rootPath = container.client.root;
|
||||
const collectionDir = path.join(rootPath, collectionName);
|
||||
const dstDir = path.join(collectionDir, 'full');
|
||||
const dstFile = path.join(dstDir, file);
|
||||
|
@ -57,7 +56,7 @@ module.exports = Self => {
|
|||
};
|
||||
|
||||
await fs.mkdir(dstDir, {recursive: true});
|
||||
await sharp(srcFilePath, {failOnError: false})
|
||||
await sharp(srcFile, {failOnError: false})
|
||||
.resize(collection.maxWidth, collection.maxHeight, resizeOpts)
|
||||
.png()
|
||||
.toFile(dstFile);
|
||||
|
@ -72,7 +71,7 @@ module.exports = Self => {
|
|||
};
|
||||
|
||||
await fs.mkdir(dstDir, {recursive: true});
|
||||
await sharp(srcFilePath, {failOnError: false})
|
||||
await sharp(srcFile, {failOnError: false})
|
||||
.resize(size.width, size.height, resizeOpts)
|
||||
.png()
|
||||
.toFile(dstFile);
|
||||
|
@ -83,7 +82,7 @@ module.exports = Self => {
|
|||
if (!model)
|
||||
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) {
|
||||
await item.updateAttribute(
|
||||
collection.property,
|
||||
|
@ -92,8 +91,8 @@ module.exports = Self => {
|
|||
);
|
||||
}
|
||||
|
||||
if (fs.existsSync(srcFilePath))
|
||||
await fs.unlink(srcFilePath);
|
||||
if (fs.existsSync(srcFile))
|
||||
await fs.unlink(srcFile);
|
||||
|
||||
await tx.commit();
|
||||
return newImage;
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
INSERT INTO salix.ACL (model, property, accessType, permission, principalType, principalId) VALUES ('Image', '*', 'WRITE', 'ALLOW', 'ROLE', 'employee')
|
|
@ -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;
|
||||
|
|
@ -447,6 +447,25 @@ INSERT INTO `imageCollection` VALUES (1,'catalog','Artículo',3840,2160,'Item','
|
|||
/*!40000 ALTER TABLE `imageCollection` ENABLE KEYS */;
|
||||
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`
|
||||
--
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
File: 4.txt. It works!
|
|
@ -1 +0,0 @@
|
|||
It works!
|
|
@ -1 +0,0 @@
|
|||
It works!
|
Binary file not shown.
Before Width: | Height: | Size: 13 KiB |
|
@ -1 +0,0 @@
|
|||
It works!
|
|
@ -46,7 +46,7 @@ export function directive($timeout) {
|
|||
$element.on('click', function(event) {
|
||||
if (event.defaultPrevented) return;
|
||||
|
||||
let src = $attrs.zoomImage || $attrs.src;
|
||||
let src = $element[0].getAttribute('zoom-image') || $element[0].src;
|
||||
if (src)
|
||||
createContainers(src);
|
||||
else
|
||||
|
|
|
@ -5,6 +5,24 @@
|
|||
vn-descriptor-content {
|
||||
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 {
|
||||
display: block;
|
||||
height: 40px;
|
||||
|
|
|
@ -13,3 +13,4 @@ import './section';
|
|||
import './summary';
|
||||
import './topbar/topbar';
|
||||
import './user-popover';
|
||||
import './upload-photo';
|
||||
|
|
|
@ -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>
|
|
@ -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: '<'
|
||||
}
|
||||
});
|
|
@ -0,0 +1,2 @@
|
|||
Upload new photo: Subir una nueva foto
|
||||
Select an image: Selecciona una imagen
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -14,7 +14,7 @@
|
|||
<vn-vertical class="user-popover vn-pa-md">
|
||||
<div class="profile-card vn-pb-md">
|
||||
<img
|
||||
ng-src="{{$ctrl.getImageUrl($root.user.id)}}"
|
||||
ng-src="{{::$root.imagePath('user', '160x160', $root.user.id)}}"
|
||||
on-error-src/>
|
||||
<div class="vn-pl-sm">
|
||||
<div>
|
||||
|
|
|
@ -78,10 +78,6 @@ class Controller {
|
|||
this.$.companies.refresh();
|
||||
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'];
|
||||
|
||||
|
|
|
@ -7,9 +7,14 @@ export const appName = 'salix';
|
|||
const ngModule = ng.module('salix', ['vnCore']);
|
||||
export default ngModule;
|
||||
|
||||
run.$inject = ['$window', '$rootScope', 'vnAuth', 'vnApp', '$state'];
|
||||
export function run($window, $rootScope, vnAuth, vnApp, $state) {
|
||||
$rootScope.imagePath = appConfig.imagePath;
|
||||
run.$inject = ['$window', '$rootScope', 'vnAuth', 'vnApp', 'vnToken', '$state'];
|
||||
export function run($window, $rootScope, vnAuth, vnApp, vnToken, $state) {
|
||||
$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 = {};
|
||||
vnApp.name = appName;
|
||||
|
|
|
@ -160,5 +160,6 @@
|
|||
"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",
|
||||
"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"
|
||||
}
|
|
@ -2,7 +2,7 @@ const uuid = require('uuid/v1');
|
|||
const md5 = require('md5');
|
||||
|
||||
module.exports = app => {
|
||||
const storageConnector = app.dataSources.storage.connector;
|
||||
const storageConnector = app.dataSources.dmsStorage.connector;
|
||||
|
||||
storageConnector.getFilename = function(file) {
|
||||
return `${uuid()}.${storageConnector.getFileExtension(file.name)}`;
|
||||
|
@ -15,4 +15,17 @@ module.exports = app => {
|
|||
storageConnector.getPathHash = function(id) {
|
||||
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);
|
||||
}; */
|
||||
};
|
||||
|
|
|
@ -17,11 +17,11 @@
|
|||
"connectTimeout": 40000,
|
||||
"acquireTimeout": 20000
|
||||
},
|
||||
"storage": {
|
||||
"name": "storage",
|
||||
"dmsStorage": {
|
||||
"name": "dmsStorage",
|
||||
"connector": "loopback-component-storage",
|
||||
"provider": "filesystem",
|
||||
"root": "./e2e/dms",
|
||||
"root": "./storage/dms",
|
||||
"maxFileSize": "262144000",
|
||||
"allowedContentTypes": [
|
||||
"application/x-7z-compressed",
|
||||
|
@ -36,5 +36,17 @@
|
|||
"image/jpeg",
|
||||
"image/jpg"
|
||||
]
|
||||
},
|
||||
"imageStorage": {
|
||||
"name": "imageStorage",
|
||||
"connector": "loopback-component-storage",
|
||||
"provider": "filesystem",
|
||||
"root": "./storage/image",
|
||||
"maxFileSize": "52428800",
|
||||
"allowedContentTypes": [
|
||||
"image/png",
|
||||
"image/jpeg",
|
||||
"image/jpg"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,7 +13,7 @@ module.exports = Self => {
|
|||
});
|
||||
|
||||
Self.allowedContentTypes = async() => {
|
||||
const storageConnector = Self.app.dataSources.storage.connector;
|
||||
const storageConnector = Self.app.dataSources.dmsStorage.connector;
|
||||
const allowedContentTypes = storageConnector.allowedContentTypes;
|
||||
const modelAllowedContentTypes = Self.definition.settings.allowedContentTypes;
|
||||
|
||||
|
|
|
@ -13,7 +13,7 @@ module.exports = Self => {
|
|||
});
|
||||
|
||||
Self.allowedContentTypes = async() => {
|
||||
const storageConnector = Self.app.dataSources.storage.connector;
|
||||
const storageConnector = Self.app.dataSources.dmsStorage.connector;
|
||||
const allowedContentTypes = storageConnector.allowedContentTypes;
|
||||
const modelAllowedContentTypes = Self.definition.settings.allowedContentTypes;
|
||||
|
||||
|
|
|
@ -70,8 +70,8 @@
|
|||
</vn-td>
|
||||
<vn-td shrink >
|
||||
<img
|
||||
ng-src="{{::$root.imagePath}}/catalog/50x50/{{::buy.image}}"
|
||||
zoom-image="{{::$root.imagePath}}/catalog/1600x900/{{::buy.image}}"
|
||||
ng-src="{{::$root.imagePath('catalog', '50x50', buy.itemFk)}}"
|
||||
zoom-image="{{::$root.imagePath('catalog', '1600x900', buy.itemFk)}}"
|
||||
vn-click-stop
|
||||
on-error-src/>
|
||||
</vn-td>
|
||||
|
|
|
@ -57,7 +57,7 @@ module.exports = Self => {
|
|||
|
||||
writeStream.on('finish', async function() {
|
||||
try {
|
||||
await models.Image.registerImage('catalog', fileName, filePath);
|
||||
await models.Image.registerImage('catalog', filePath, image.itemFk);
|
||||
await image.destroy();
|
||||
} catch (error) {
|
||||
await errorHandler(image.itemFk, error, filePath);
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<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-portal>
|
||||
<ui-view></ui-view>
|
||||
|
|
|
@ -16,19 +16,15 @@
|
|||
</vn-item>
|
||||
</slot-menu>
|
||||
<slot-before>
|
||||
<div style="position: relative" text-center>
|
||||
<img
|
||||
ng-src="{{::$root.imagePath}}/catalog/200x200/{{$ctrl.item.image}}"
|
||||
zoom-image="{{::$root.imagePath}}/catalog/1600x900/{{$ctrl.item.image}}"
|
||||
on-error-src
|
||||
/>
|
||||
<a href="//verdnatura.es/#!form=admin/items&filter={{$ctrl.item.id}}" target="_blank">
|
||||
<vn-float-button
|
||||
icon="edit"
|
||||
style="position: absolute; margin: 1em; bottom: 0; right: 0;"
|
||||
vn-visible-by="marketing, buyer">
|
||||
</vn-float-button>
|
||||
</a>
|
||||
<div class="photo" text-center>
|
||||
<img vn-id="photo"
|
||||
ng-src="{{$root.imagePath('catalog', '200x200', $ctrl.item.id)}}"
|
||||
zoom-image="{{$root.imagePath('catalog', '1600x900', $ctrl.item.id)}}"
|
||||
on-error-src/>
|
||||
<vn-float-button ng-click="uploadPhoto.show('catalog', $ctrl.item.id)"
|
||||
icon="edit"
|
||||
vn-visible-by="marketing, buyer">
|
||||
</vn-float-button>
|
||||
</div>
|
||||
<vn-horizontal class="item-state">
|
||||
<vn-one>
|
||||
|
@ -102,4 +98,10 @@
|
|||
</vn-confirm>
|
||||
<vn-worker-descriptor-popover
|
||||
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>
|
|
@ -3,6 +3,11 @@ import Descriptor from 'salix/components/descriptor';
|
|||
import './style.scss';
|
||||
|
||||
class Controller extends Descriptor {
|
||||
constructor($element, $, $rootScope) {
|
||||
super($element, $);
|
||||
this.$rootScope = $rootScope;
|
||||
}
|
||||
|
||||
get item() {
|
||||
return this.entity;
|
||||
}
|
||||
|
@ -65,13 +70,27 @@ class Controller extends Descriptor {
|
|||
this.$http.post(`Items/${this.item.id}/clone`)
|
||||
.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', {
|
||||
template: require('./index.html'),
|
||||
controller: Controller,
|
||||
bindings: {
|
||||
item: '<',
|
||||
dated: '<'
|
||||
dated: '<',
|
||||
cardReload: '&'
|
||||
}
|
||||
});
|
||||
|
|
|
@ -35,8 +35,8 @@
|
|||
ui-sref="item.card.summary({id: item.id})">
|
||||
<vn-td shrink>
|
||||
<img
|
||||
ng-src="{{::$root.imagePath}}/catalog/50x50/{{::item.image}}"
|
||||
zoom-image="{{::$root.imagePath}}/catalog/1600x900/{{::item.image}}"
|
||||
ng-src="{{::$root.imagePath('catalog', '50x50', item.id)}}"
|
||||
zoom-image="{{::$root.imagePath('catalog', '1600x900', item.id)}}"
|
||||
vn-click-stop
|
||||
on-error-src/>
|
||||
</vn-td>
|
||||
|
@ -44,7 +44,7 @@
|
|||
<span
|
||||
vn-click-stop="itemDescriptor.show($event, item.id)"
|
||||
class="link">
|
||||
{{::item.id | zeroFill:6}}
|
||||
{{::item.id}}
|
||||
</span>
|
||||
</vn-td>
|
||||
<vn-td shrink>{{::item.grouping | dashIfEmpty}}</vn-td>
|
||||
|
|
|
@ -11,8 +11,8 @@
|
|||
<vn-horizontal>
|
||||
<vn-one>
|
||||
<img style="width: 100%; display: block;"
|
||||
ng-src="{{::$root.imagePath}}/catalog/200x200/{{$ctrl.item.image}}"
|
||||
zoom-image="{{::$root.imagePath}}/catalog/1600x900/{{$ctrl.item.image}}" on-error-src/>
|
||||
ng-src="{{$root.imagePath('catalog', '200x200', $ctrl.item.id)}}"
|
||||
zoom-image="{{$root.imagePath('catalog', '1600x900', $ctrl.item.id)}}" on-error-src/>
|
||||
<vn-horizontal class="item-state">
|
||||
<vn-one>
|
||||
<p translate>Visible</p>
|
||||
|
|
|
@ -5,11 +5,11 @@
|
|||
<vn-card>
|
||||
<div class="image">
|
||||
<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>
|
||||
<img
|
||||
ng-src="{{::$root.imagePath}}/catalog/200x200/{{::item.image}}"
|
||||
zoom-image="{{::$root.imagePath}}/catalog/1600x900/{{::item.image}}"
|
||||
ng-src="{{::$root.imagePath('catalog', '200x200', item.id)}}"
|
||||
zoom-image="{{::$root.imagePath('catalog', '1600x900', item.id)}}"
|
||||
on-error-src/>
|
||||
</div>
|
||||
<div class="description">
|
||||
|
|
|
@ -32,8 +32,8 @@
|
|||
<vn-tr ng-repeat="row in $ctrl.rows">
|
||||
<vn-td shrink>
|
||||
<img
|
||||
ng-src="{{::$root.imagePath}}/catalog/50x50/{{::row.item.image}}"
|
||||
zoom-image="{{::$root.imagePath}}/catalog/1600x900/{{::row.item.image}}"
|
||||
ng-src="{{::$root.imagePath('catalog', '50x50', row.item.id)}}"
|
||||
zoom-image="{{::$root.imagePath('catalog', '1600x900', row.item.id)}}"
|
||||
on-error-src/>
|
||||
</vn-td>
|
||||
<vn-td number>
|
||||
|
|
|
@ -13,7 +13,7 @@ module.exports = Self => {
|
|||
});
|
||||
|
||||
Self.allowedContentTypes = async() => {
|
||||
const storageConnector = Self.app.dataSources.storage.connector;
|
||||
const storageConnector = Self.app.dataSources.dmsStorage.connector;
|
||||
const allowedContentTypes = storageConnector.allowedContentTypes;
|
||||
const modelAllowedContentTypes = Self.definition.settings.allowedContentTypes;
|
||||
|
||||
|
|
|
@ -19,8 +19,8 @@
|
|||
<vn-card>
|
||||
<div class="image">
|
||||
<img
|
||||
ng-src="{{::$root.imagePath}}/catalog/200x200/{{::sale.item.image}}"
|
||||
zoom-image="{{::$root.imagePath}}/catalog/1600x900/{{::sale.item.image}}"
|
||||
ng-src="{{::$root.imagePath('catalog', '200x200', sale.itemFk)}}"
|
||||
zoom-image="{{::$root.imagePath('catalog', '1600x900', sale.itemFk)}}"
|
||||
on-error-src/>
|
||||
</div>
|
||||
<div class="description">
|
||||
|
|
|
@ -93,8 +93,8 @@
|
|||
</vn-td>
|
||||
<vn-td shrink>
|
||||
<img
|
||||
ng-src="{{::$root.imagePath}}/catalog/50x50/{{sale.image}}"
|
||||
zoom-image="{{::$root.imagePath}}/catalog/1600x900/{{sale.image}}"
|
||||
ng-src="{{::$root.imagePath('catalog', '50x50', sale.itemFk)}}"
|
||||
zoom-image="{{::$root.imagePath('catalog', '1600x900', sale.itemFk)}}"
|
||||
on-error-src/>
|
||||
</vn-td>
|
||||
<vn-td number>
|
||||
|
|
|
@ -13,7 +13,7 @@ module.exports = Self => {
|
|||
});
|
||||
|
||||
Self.allowedContentTypes = async() => {
|
||||
const storageConnector = Self.app.dataSources.storage.connector;
|
||||
const storageConnector = Self.app.dataSources.dmsStorage.connector;
|
||||
const allowedContentTypes = storageConnector.allowedContentTypes;
|
||||
const modelAllowedContentTypes = Self.definition.settings.allowedContentTypes;
|
||||
|
||||
|
|
|
@ -46,7 +46,7 @@ class Controller extends Section {
|
|||
warehouseId: warehouseId,
|
||||
companyId: companyId,
|
||||
dmsTypeId: dmsTypeId,
|
||||
description: this.$t('FileDescription', {
|
||||
description: this.$t('TravelFileDescription', {
|
||||
travelId: this.travel.id
|
||||
}).toUpperCase()
|
||||
};
|
||||
|
|
|
@ -8,7 +8,7 @@ Upload file: Subir fichero
|
|||
Edit file: Editar fichero
|
||||
Upload: Subir
|
||||
File: Fichero
|
||||
FileDescription: Travel id {{travelId}}
|
||||
TravelFileDescription: Travel id {{travelId}}
|
||||
ContentTypesInfo: 'Tipos de archivo permitidos: {{allowedContentTypes}}'
|
||||
Are you sure you want to continue?: ¿Seguro que quieres continuar?
|
||||
Add thermograph: Añadir termógrafo
|
||||
|
|
|
@ -13,7 +13,7 @@ module.exports = Self => {
|
|||
});
|
||||
|
||||
Self.allowedContentTypes = async() => {
|
||||
const storageConnector = Self.app.dataSources.storage.connector;
|
||||
const storageConnector = Self.app.dataSources.dmsStorage.connector;
|
||||
const allowedContentTypes = storageConnector.allowedContentTypes;
|
||||
const modelAllowedContentTypes = Self.definition.settings.allowedContentTypes;
|
||||
|
||||
|
|
|
@ -1,6 +1,18 @@
|
|||
<vn-descriptor-content
|
||||
module="worker"
|
||||
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>
|
||||
<div class="attributes">
|
||||
<vn-label-value
|
||||
|
@ -42,4 +54,10 @@
|
|||
<div ng-transclude="btnThree"></div>
|
||||
</div>
|
||||
</slot-body>
|
||||
</vn-descriptor-content>
|
||||
</vn-descriptor-content>
|
||||
|
||||
<!-- Upload photo dialog -->
|
||||
<vn-upload-photo
|
||||
vn-id="uploadPhoto"
|
||||
on-response="$ctrl.onUploadResponse()">
|
||||
</vn-upload-photo>
|
|
@ -2,6 +2,11 @@ import ngModule from '../module';
|
|||
import Descriptor from 'salix/components/descriptor';
|
||||
|
||||
class Controller extends Descriptor {
|
||||
constructor($element, $, $rootScope) {
|
||||
super($element, $);
|
||||
this.$rootScope = $rootScope;
|
||||
}
|
||||
|
||||
get worker() {
|
||||
return this.entity;
|
||||
}
|
||||
|
@ -48,8 +53,21 @@ class Controller extends Descriptor {
|
|||
return this.getData(`Workers/${this.id}`, {filter})
|
||||
.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', {
|
||||
template: require('./index.html'),
|
||||
controller: Controller,
|
||||
|
|
Loading…
Reference in New Issue