Refactor v2
gitea/salix/pipeline/head There was a failure building this commit Details

This commit is contained in:
Joan Sanchez 2020-12-18 17:23:04 +01:00
parent d7dde7e4eb
commit 42cd2356a2
109 changed files with 291 additions and 318 deletions

2
.gitignore vendored
View File

@ -1,7 +1,7 @@
coverage coverage
node_modules node_modules
dist dist
storage #storage
npm-debug.log npm-debug.log
.eslintcache .eslintcache
datasources.*.json datasources.*.json

View File

@ -1,5 +1,6 @@
const UserError = require('vn-loopback/util/user-error'); const UserError = require('vn-loopback/util/user-error');
const fs = require('fs-extra'); const fs = require('fs-extra');
const path = require('path');
module.exports = Self => { module.exports = Self => {
Self.remoteMethodCtx('updateFile', { Self.remoteMethodCtx('updateFile', {
@ -84,66 +85,46 @@ module.exports = Self => {
}; };
async function uploadNewFile(ctx, dms, myOptions) { async function uploadNewFile(ctx, dms, myOptions) {
const storageConnector = Self.app.dataSources.dmsStorage.connector;
const models = Self.app.models; const models = Self.app.models;
const TempContainer = models.TempContainer;
const DmsContainer = models.DmsContainer;
const fileOptions = {}; const fileOptions = {};
const tempContainer = await TempContainer.container('dms');
const tempContainer = await getContainer('temp'); const makeUpload = await TempContainer.upload(tempContainer.name, ctx.req, ctx.result, fileOptions);
const makeUpload = await models.Container.upload(tempContainer.name, ctx.req, ctx.result, fileOptions);
const keys = Object.values(makeUpload.files); const keys = Object.values(makeUpload.files);
const files = keys.map(file => file[0]); const files = keys.map(file => file[0]);
const file = files[0]; const uploadedFile = files[0];
if (file) { if (uploadedFile) {
const oldExtension = storageConnector.getFileExtension(dms.file); const oldExtension = DmsContainer.getFileExtension(dms.file);
const newExtension = storageConnector.getFileExtension(file.name); const newExtension = DmsContainer.getFileExtension(uploadedFile.name);
const fileName = `${dms.id}.${newExtension}`; const fileName = `${dms.id}.${newExtension}`;
try { try {
if (oldExtension != newExtension) { if (oldExtension != newExtension) {
const pathHash = storageConnector.getPathHash(dms.id); const pathHash = DmsContainer.getHash(dms.id);
await models.Container.removeFile(pathHash, dms.file); await DmsContainer.removeFile(pathHash, dms.file);
} }
} catch (err) {} } catch (err) {}
const updatedDms = await dms.updateAttributes({ const updatedDms = await dms.updateAttributes({
contentType: file.type, contentType: uploadedFile.type,
file: fileName file: fileName
}, myOptions); }, myOptions);
const pathHash = storageConnector.getPathHash(updatedDms.id); const file = await TempContainer.getFile(tempContainer.name, uploadedFile.name);
const container = await getContainer(pathHash); const srcFile = path.join(file.client.root, file.container, file.name);
const originPath = `${tempContainer.client.root}/${tempContainer.name}/${file.name}`; const pathHash = DmsContainer.getHash(updatedDms.id);
const destinationPath = `${container.client.root}/${pathHash}/${updatedDms.file}`; const dmsContainer = await DmsContainer.container(pathHash);
const dstFile = path.join(dmsContainer.client.root, pathHash, updatedDms.file);
fs.rename(originPath, destinationPath); await fs.move(srcFile, dstFile, {
overwrite: true
});
return updatedDms; return updatedDms;
} }
} }
/**
* 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.Container.getContainer(name);
} catch (err) {
if (err.code === 'ENOENT') {
container = await models.Container.createContainer({
name: name
});
} else throw err;
}
return container;
}
}; };

View File

@ -1,5 +1,6 @@
const UserError = require('vn-loopback/util/user-error'); const UserError = require('vn-loopback/util/user-error');
const fs = require('fs-extra'); const fs = require('fs-extra');
const path = require('path');
module.exports = Self => { module.exports = Self => {
Self.remoteMethodCtx('uploadFile', { Self.remoteMethodCtx('uploadFile', {
@ -46,8 +47,9 @@ module.exports = Self => {
}); });
Self.uploadFile = async(ctx, options) => { Self.uploadFile = async(ctx, options) => {
const storageConnector = Self.app.dataSources.dmsStorage.connector;
const models = Self.app.models; const models = Self.app.models;
const TempContainer = models.TempContainer;
const DmsContainer = models.DmsContainer;
const fileOptions = {}; const fileOptions = {};
const args = ctx.args; const args = ctx.args;
@ -62,28 +64,33 @@ module.exports = Self => {
myOptions.transaction = tx; myOptions.transaction = tx;
} }
let srcFile;
try { try {
const hasWriteRole = await models.DmsType.hasWriteRole(ctx, args.dmsTypeId, myOptions); const hasWriteRole = await models.DmsType.hasWriteRole(ctx, args.dmsTypeId, myOptions);
if (!hasWriteRole) if (!hasWriteRole)
throw new UserError(`You don't have enough privileges`); throw new UserError(`You don't have enough privileges`);
// Upload file to temporary path // Upload file to temporary path
const tempContainer = await getContainer('temp'); const tempContainer = await TempContainer.container('dms');
const uploaded = await models.Container.upload(tempContainer.name, ctx.req, ctx.result, fileOptions); const uploaded = await TempContainer.upload(tempContainer.name, ctx.req, ctx.result, fileOptions);
const files = Object.values(uploaded.files).map(file => { const files = Object.values(uploaded.files).map(file => {
return file[0]; return file[0];
}); });
const addedDms = []; const addedDms = [];
for (const file of files) { for (const uploadedFile of files) {
const newDms = await createDms(ctx, file, myOptions); const newDms = await createDms(ctx, uploadedFile, myOptions);
const pathHash = storageConnector.getPathHash(newDms.id); const pathHash = DmsContainer.getHash(newDms.id);
const container = await getContainer(pathHash);
const originPath = `${tempContainer.client.root}/${tempContainer.name}/${file.name}`; const file = await TempContainer.getFile(tempContainer.name, uploadedFile.name);
const destinationPath = `${container.client.root}/${pathHash}/${newDms.file}`; srcFile = path.join(file.client.root, file.container, file.name);
await fs.rename(originPath, destinationPath); const dmsContainer = await DmsContainer.container(pathHash);
const dstFile = path.join(dmsContainer.client.root, pathHash, newDms.file);
await fs.move(srcFile, dstFile, {
overwrite: true
});
addedDms.push(newDms); addedDms.push(newDms);
} }
@ -92,13 +99,16 @@ module.exports = Self => {
return addedDms; return addedDms;
} catch (e) { } catch (e) {
if (tx) await tx.rollback(); if (tx) await tx.rollback();
if (fs.existsSync(srcFile))
await fs.unlink(srcFile);
throw e; throw e;
} }
}; };
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.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;
@ -115,32 +125,9 @@ module.exports = Self => {
}, myOptions); }, myOptions);
let fileName = file.name; let fileName = file.name;
const extension = storageConnector.getFileExtension(fileName); const extension = models.DmsContainer.getFileExtension(fileName);
fileName = `${newDms.id}.${extension}`; fileName = `${newDms.id}.${extension}`;
return newDms.updateAttribute('file', fileName, myOptions); return newDms.updateAttribute('file', fileName, myOptions);
} }
/**
* 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.Container.getContainer(name);
} catch (err) {
if (err.code === 'ENOENT') {
container = await models.Container.createContainer({
name: name
});
} else throw err;
}
return container;
}
}; };

View File

@ -1,5 +1,6 @@
const UserError = require('vn-loopback/util/user-error'); const UserError = require('vn-loopback/util/user-error');
const fs = require('fs-extra'); const fs = require('fs-extra');
const path = require('path');
module.exports = Self => { module.exports = Self => {
Self.remoteMethodCtx('download', { Self.remoteMethodCtx('download', {
@ -69,8 +70,9 @@ module.exports = Self => {
const container = await models.ImageContainer.getContainer(collection); const container = await models.ImageContainer.getContainer(collection);
const rootPath = container.client.root; const rootPath = container.client.root;
const fileSrc = path.join(rootPath, collection, size);
const file = { const file = {
path: `${rootPath}/${collection}/${size}/${image.name}.png`, path: `${fileSrc}/${image.name}.png`,
contentType: 'image/png', contentType: 'image/png',
name: `${image.name}.png` name: `${image.name}.png`
}; };

View File

@ -29,8 +29,8 @@ describe('image upload()', () => {
expect(error.message).toEqual(`You don't have enough privileges`); expect(error.message).toEqual(`You don't have enough privileges`);
}); });
it('should call to the ImageContainer upload method for the collection "catalog"', async() => { it('should call to the TempContainer upload method for the collection "catalog"', async() => {
const containerModel = app.models.ImageContainer; const containerModel = app.models.TempContainer;
spyOn(containerModel, 'upload'); spyOn(containerModel, 'upload');
const ctx = {req: {accessToken: {userId: buyerId}}, const ctx = {req: {accessToken: {userId: buyerId}},
@ -73,8 +73,8 @@ describe('image upload()', () => {
const workerId = 106; const workerId = 106;
const itemId = 4; const itemId = 4;
it('should be able to call to the ImageContainer upload method for the collection "user"', async() => { it('should be able to call to the TempContainer upload method for the collection "user"', async() => {
const containerModel = app.models.ImageContainer; const containerModel = app.models.TempContainer;
spyOn(containerModel, 'upload'); spyOn(containerModel, 'upload');
const ctx = {req: {accessToken: {userId: marketingId}}, const ctx = {req: {accessToken: {userId: marketingId}},
@ -91,8 +91,8 @@ describe('image upload()', () => {
expect(containerModel.upload).toHaveBeenCalled(); expect(containerModel.upload).toHaveBeenCalled();
}); });
it('should be able to call to the ImageContainer upload method for the collection "catalog"', async() => { it('should be able to call to the TempContainer upload method for the collection "catalog"', async() => {
const containerModel = app.models.ImageContainer; const containerModel = app.models.TempContainer;
spyOn(containerModel, 'upload'); spyOn(containerModel, 'upload');
const ctx = {req: {accessToken: {userId: marketingId}}, const ctx = {req: {accessToken: {userId: marketingId}},
@ -115,8 +115,8 @@ describe('image upload()', () => {
const workerId = 106; const workerId = 106;
const itemId = 4; const itemId = 4;
it('should upload a file for the collection "user" and call to the ImageContainer upload method', async() => { it('should upload a file for the collection "user" and call to the TempContainer upload method', async() => {
const containerModel = app.models.ImageContainer; const containerModel = app.models.TempContainer;
spyOn(containerModel, 'upload'); spyOn(containerModel, 'upload');
const ctx = {req: {accessToken: {userId: hhrrId}}, const ctx = {req: {accessToken: {userId: hhrrId}},

View File

@ -1,5 +1,6 @@
const UserError = require('vn-loopback/util/user-error'); const UserError = require('vn-loopback/util/user-error');
const fs = require('fs-extra'); const fs = require('fs-extra');
const path = require('path');
module.exports = Self => { module.exports = Self => {
Self.remoteMethodCtx('upload', { Self.remoteMethodCtx('upload', {
@ -17,6 +18,12 @@ module.exports = Self => {
type: 'string', type: 'string',
description: 'The collection name', description: 'The collection name',
required: true required: true
},
{
arg: 'fileName',
type: 'string',
description: 'The file name',
required: true
}], }],
returns: { returns: {
type: 'Object', type: 'Object',
@ -30,9 +37,12 @@ module.exports = Self => {
Self.upload = async ctx => { Self.upload = async ctx => {
const models = Self.app.models; const models = Self.app.models;
const TempContainer = models.TempContainer;
const fileOptions = {}; const fileOptions = {};
const args = ctx.args; const args = ctx.args;
let srcFile;
try {
const hasWriteRole = await models.ImageCollection.hasWriteRole(ctx, args.collection); const hasWriteRole = await models.ImageCollection.hasWriteRole(ctx, args.collection);
if (!hasWriteRole) if (!hasWriteRole)
throw new UserError(`You don't have enough privileges`); throw new UserError(`You don't have enough privileges`);
@ -40,38 +50,23 @@ module.exports = Self => {
if (process.env.NODE_ENV == 'test') if (process.env.NODE_ENV == 'test')
throw new UserError(`You can't upload images on the test instance`); throw new UserError(`You can't upload images on the test instance`);
await TempContainer.allowedContentTypes();
// Upload file to temporary path // Upload file to temporary path
const container = await getContainer(args.collection); const tempContainer = await TempContainer.container(args.collection);
const uploaded = await models.ImageContainer.upload(container.name, ctx.req, ctx.result, fileOptions); const uploaded = await TempContainer.upload(tempContainer.name, ctx.req, ctx.result, fileOptions);
const [uploadedFile] = Object.values(uploaded.files).map(file => { const [uploadedFile] = Object.values(uploaded.files).map(file => {
return file[0]; return file[0];
}); });
const file = await TempContainer.getFile(tempContainer.name, uploadedFile.name);
srcFile = path.join(file.client.root, file.container, file.name);
const file = await models.ImageContainer.getFile(container.name, uploadedFile.name); await models.Image.registerImage(args.collection, srcFile, args.fileName, args.id);
const srcFile = `${file.client.root}/${file.container}/${file.name}`; } catch (e) {
await models.Image.registerImage(container.name, srcFile, args.id); if (fs.existsSync(srcFile))
await fs.unlink(srcFile);
throw e;
}
}; };
/**
* 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

@ -17,9 +17,6 @@
"Company": { "Company": {
"dataSource": "vn" "dataSource": "vn"
}, },
"Container": {
"dataSource": "dmsStorage"
},
"Continent": { "Continent": {
"dataSource": "vn" "dataSource": "vn"
}, },
@ -35,6 +32,9 @@
"Delivery": { "Delivery": {
"dataSource": "vn" "dataSource": "vn"
}, },
"DmsContainer": {
"dataSource": "dmsStorage"
},
"Image": { "Image": {
"dataSource": "vn" "dataSource": "vn"
}, },
@ -53,6 +53,9 @@
"Province": { "Province": {
"dataSource": "vn" "dataSource": "vn"
}, },
"TempContainer": {
"dataSource": "tempStorage"
},
"UserConfig": { "UserConfig": {
"dataSource": "vn" "dataSource": "vn"
}, },

View File

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

View File

@ -0,0 +1,10 @@
{
"name": "DmsContainer",
"base": "Container",
"acls": [{
"accessType": "READ",
"principalType": "ROLE",
"principalId": "$everyone",
"permission": "ALLOW"
}]
}

View File

@ -14,12 +14,12 @@ module.exports = Self => {
}; };
Self.getFile = async function(id) { Self.getFile = async function(id) {
const storageConnector = Self.app.dataSources.dmsStorage.connector;
const models = Self.app.models; const models = Self.app.models;
const DmsContainer = models.DmsContainer;
const dms = await Self.findById(id); const dms = await Self.findById(id);
const pathHash = storageConnector.getPathHash(dms.id); const pathHash = DmsContainer.getHash(dms.id);
try { try {
await models.Container.getFile(pathHash, dms.file); await DmsContainer.getFile(pathHash, dms.file);
} catch (e) { } catch (e) {
if (e.code != 'ENOENT') if (e.code != 'ENOENT')
throw e; throw e;
@ -30,7 +30,7 @@ module.exports = Self => {
throw error; throw error;
} }
const stream = models.Container.downloadStream(pathHash, dms.file); const stream = DmsContainer.downloadStream(pathHash, dms.file);
return [stream, dms.contentType, `filename="${dms.file}"`]; return [stream, dms.contentType, `filename="${dms.file}"`];
}; };

View File

@ -1,13 +1,10 @@
{ {
"name": "ImageContainer", "name": "ImageContainer",
"base": "VnModel", "base": "Container",
"idInjection": true, "acls": [{
"options": { "accessType": "READ",
"validateUpsert": true "principalType": "ROLE",
}, "principalId": "$everyone",
"properties": {}, "permission": "ALLOW"
"validations": [], }]
"relations": {}, }
"acls": [],
"methods": []
}

View File

@ -6,7 +6,7 @@ module.exports = Self => {
require('../methods/image/download')(Self); require('../methods/image/download')(Self);
require('../methods/image/upload')(Self); require('../methods/image/upload')(Self);
Self.registerImage = async(collectionName, srcFile, entityId) => { Self.registerImage = async(collectionName, srcFilePath, fileName, entityId) => {
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};
@ -30,13 +30,10 @@ module.exports = Self => {
} }
}, myOptions); }, myOptions);
const file = srcFile.split('/').pop();
const fileName = file.split('.')[0];
const data = { const data = {
name: fileName, name: fileName,
collectionFk: collectionName collectionFk: collectionName
}; };
const newImage = await Self.upsertWithWhere(data, { const newImage = await Self.upsertWithWhere(data, {
name: fileName, name: fileName,
collectionFk: collectionName, collectionFk: collectionName,
@ -44,9 +41,10 @@ module.exports = Self => {
}, myOptions); }, myOptions);
// Resizes and saves the image // Resizes and saves the image
const container = await models.ImageContainer.getContainer(collectionName); const container = await models.ImageContainer.container(collectionName);
const rootPath = container.client.root; const rootPath = container.client.root;
const collectionDir = path.join(rootPath, collectionName); const collectionDir = path.join(rootPath, collectionName);
const file = `${fileName}.png`;
const dstDir = path.join(collectionDir, 'full'); const dstDir = path.join(collectionDir, 'full');
const dstFile = path.join(dstDir, file); const dstFile = path.join(dstDir, file);
@ -56,7 +54,7 @@ module.exports = Self => {
}; };
await fs.mkdir(dstDir, {recursive: true}); await fs.mkdir(dstDir, {recursive: true});
await sharp(srcFile, {failOnError: false}) await sharp(srcFilePath, {failOnError: false})
.resize(collection.maxWidth, collection.maxHeight, resizeOpts) .resize(collection.maxWidth, collection.maxHeight, resizeOpts)
.png() .png()
.toFile(dstFile); .toFile(dstFile);
@ -71,7 +69,7 @@ module.exports = Self => {
}; };
await fs.mkdir(dstDir, {recursive: true}); await fs.mkdir(dstDir, {recursive: true});
await sharp(srcFile, {failOnError: false}) await sharp(srcFilePath, {failOnError: false})
.resize(size.width, size.height, resizeOpts) .resize(size.width, size.height, resizeOpts)
.png() .png()
.toFile(dstFile); .toFile(dstFile);
@ -91,8 +89,8 @@ module.exports = Self => {
); );
} }
if (fs.existsSync(srcFile)) if (fs.existsSync(srcFilePath))
await fs.unlink(srcFile); await fs.unlink(srcFilePath);
await tx.commit(); await tx.commit();
return newImage; return newImage;

View File

@ -0,0 +1,10 @@
{
"name": "TempContainer",
"base": "Container",
"acls": [{
"accessType": "READ",
"principalType": "ROLE",
"principalId": "$everyone",
"permission": "ALLOW"
}]
}

View File

@ -767,19 +767,19 @@ INSERT INTO `vn`.`intrastat`(`id`, `description`, `taxClassFk`, `taxCodeFk`)
INSERT INTO `vn`.`item`(`id`, `typeFk`, `size`, `inkFk`, `stems`, `originFk`, `description`, `producerFk`, `intrastatFk`, `isOnOffer`, `expenceFk`, `isBargain`, `comment`, `relevancy`, `image`, `taxClassFk`, `subName`, `minPrice`) INSERT INTO `vn`.`item`(`id`, `typeFk`, `size`, `inkFk`, `stems`, `originFk`, `description`, `producerFk`, `intrastatFk`, `isOnOffer`, `expenceFk`, `isBargain`, `comment`, `relevancy`, `image`, `taxClassFk`, `subName`, `minPrice`)
VALUES VALUES
(1, 2, 70, 'YEL', 1, 1, NULL, 1, 06021010, 0, 2000000000, 0, NULL, 0, '861e1ed0-3ab3-11eb-9ab8-27f6fc3b85fd', 1, NULL, 0), (1, 2, 70, 'YEL', 1, 1, NULL, 1, 06021010, 0, 2000000000, 0, NULL, 0, '1', 1, NULL, 0),
(2, 2, 70, 'BLU', 1, 2, NULL, 1, 06021010, 0, 2000000000, 0, NULL, 0, '9b0316c0-3ab3-11eb-9ab8-27f6fc3b85fd', 1, NULL, 0), (2, 2, 70, 'BLU', 1, 2, NULL, 1, 06021010, 0, 2000000000, 0, NULL, 0, '2', 1, NULL, 0),
(3, 1, 60, 'YEL', 1, 3, NULL, 1, 05080000, 0, 4751000000, 0, NULL, 0, 'a93bd600-3ab3-11eb-9ab8-27f6fc3b85fd', 1, NULL, 0), (3, 1, 60, 'YEL', 1, 3, NULL, 1, 05080000, 0, 4751000000, 0, NULL, 0, '3', 1, NULL, 0),
(4, 1, 60, 'YEL', 1, 1, 'Increases block', 1, 05080000, 1, 4751000000, 0, NULL, 0, '0e1c92d0-3ab4-11eb-9ab8-27f6fc3b85fd', 2, NULL, 0), (4, 1, 60, 'YEL', 1, 1, 'Increases block', 1, 05080000, 1, 4751000000, 0, NULL, 0, '4', 2, NULL, 0),
(5, 3, 30, 'RED', 1, 2, NULL, 2, 06021010, 1, 4751000000, 0, NULL, 0, '189081e0-3ab4-11eb-9ab8-27f6fc3b85fd', 2, NULL, 0), (5, 3, 30, 'RED', 1, 2, NULL, 2, 06021010, 1, 4751000000, 0, NULL, 0, '5', 2, NULL, 0),
(6, 5, 30, 'RED', 1, 2, NULL, NULL, 06021010, 0, 4751000000, 0, NULL, 0, '25238060-3ab4-11eb-9ab8-27f6fc3b85fd', 2, NULL, 0), (6, 5, 30, 'RED', 1, 2, NULL, NULL, 06021010, 0, 4751000000, 0, NULL, 0, '6', 2, NULL, 0),
(7, 5, 90, 'BLU', 1, 2, NULL, NULL, 06021010, 0, 4751000000, 0, NULL, 0, 'c4097310-3ab4-11eb-9ab8-27f6fc3b85fd', 2, NULL, 0), (7, 5, 90, 'BLU', 1, 2, NULL, NULL, 06021010, 0, 4751000000, 0, NULL, 0, '7', 2, NULL, 0),
(8, 2, 70, 'YEL', 1, 1, NULL, 1, 06021010, 0, 2000000000, 0, NULL, 0, 'd3bf8380-3ab4-11eb-9ab8-27f6fc3b85fd', 1, NULL, 0), (8, 2, 70, 'YEL', 1, 1, NULL, 1, 06021010, 0, 2000000000, 0, NULL, 0, '8', 1, NULL, 0),
(9, 2, 70, 'BLU', 1, 2, NULL, 1, 06021010, 0, 2000000000, 0, NULL, 0, '20e9fff0-3ab5-11eb-9ab8-27f6fc3b85fd', 1, NULL, 0), (9, 2, 70, 'BLU', 1, 2, NULL, 1, 06021010, 0, 2000000000, 0, NULL, 0, '9', 1, NULL, 0),
(10, 1, 60, 'YEL', 1, 3, NULL, 1, 05080000, 0, 4751000000, 0, NULL, 0, '2c484f00-3ab5-11eb-9ab8-27f6fc3b85fd', 1, NULL, 0), (10, 1, 60, 'YEL', 1, 3, NULL, 1, 05080000, 0, 4751000000, 0, NULL, 0, '10', 1, NULL, 0),
(11, 1, 60, 'YEL', 1, 1, NULL, 1, 05080000, 1, 4751000000, 0, NULL, 0, 'a3d9bea0-3ab5-11eb-9ab8-27f6fc3b85fd', 2, NULL, 0), (11, 1, 60, 'YEL', 1, 1, NULL, 1, 05080000, 1, 4751000000, 0, NULL, 0, '11', 2, NULL, 0),
(12, 3, 30, 'RED', 1, 2, NULL, 2, 06021010, 1, 4751000000, 0, NULL, 0, 'e48b2d30-3ab5-11eb-9ab8-27f6fc3b85fd', 2, NULL, 0), (12, 3, 30, 'RED', 1, 2, NULL, 2, 06021010, 1, 4751000000, 0, NULL, 0, '12', 2, NULL, 0),
(13, 5, 30, 'RED', 1, 2, NULL, NULL, 06021010, 0, 4751000000, 0, NULL, 0, 'fb5cd9f0-3ab5-11eb-9ab8-27f6fc3b85fd', 2, NULL, 0), (13, 5, 30, 'RED', 1, 2, NULL, NULL, 06021010, 0, 4751000000, 0, NULL, 0, '13', 2, NULL, 0),
(14, 5, 90, 'BLU', 1, 2, NULL, NULL, 06021010, 0, 4751000000, 0, NULL, 0, '', 2, NULL, 0), (14, 5, 90, 'BLU', 1, 2, NULL, NULL, 06021010, 0, 4751000000, 0, NULL, 0, '', 2, NULL, 0),
(15, 4, NULL, NULL, NULL, 1, NULL, NULL, 06021010, 0, 4751000000, 0, NULL, 0, '', 2, NULL, 0), (15, 4, NULL, NULL, NULL, 1, NULL, NULL, 06021010, 0, 4751000000, 0, NULL, 0, '', 2, NULL, 0),
(16, 4, NULL, NULL, NULL, 1, NULL, NULL, 06021010, 0, 4751000000, 0, NULL, 0, '', 2, NULL, 0), (16, 4, NULL, NULL, NULL, 1, NULL, NULL, 06021010, 0, 4751000000, 0, NULL, 0, '', 2, NULL, 0),

View File

@ -6,13 +6,29 @@
<vn-horizontal ng-show="file.value" class="photo vn-mb-md"> <vn-horizontal ng-show="file.value" class="photo vn-mb-md">
<div><img vn-id="photo" ng-src=""/></div> <div><img vn-id="photo" ng-src=""/></div>
</vn-horizontal> </vn-horizontal>
<vn-horizontal>
<vn-textfield
vn-one
label="File name"
ng-model="$ctrl.newPhoto.fileName"
required="true">
</vn-input-file>
</vn-horizontal>
<vn-horizontal> <vn-horizontal>
<vn-input-file vn-id="file" <vn-input-file vn-id="file"
vn-one vn-one
label="File" label="File"
ng-model="$ctrl.newPhoto.files" ng-model="$ctrl.newPhoto.files"
on-change="$ctrl.updatePhotoPreview(value)" on-change="$ctrl.updatePhotoPreview(value)"
accept="{{$ctrl.allowedContentTypes}}"
required="true"> required="true">
<append>
<vn-icon vn-none
color-marginal
title="{{$ctrl.contentTypesInfo}}"
icon="info">
</vn-icon>
</append>
</vn-input-file> </vn-input-file>
</vn-horizontal> </vn-horizontal>
</tpl-body> </tpl-body>

View File

@ -12,8 +12,26 @@ export default class UploadPhoto extends Component {
* @param {*} id - Entity id * @param {*} id - Entity id
*/ */
show(collection, id) { show(collection, id) {
this.newPhoto = {id, collection}; this.newPhoto = {
id: id,
collection: collection,
fileName: id
};
this.$.dialog.show(); this.$.dialog.show();
this.getAllowedContentTypes();
}
getAllowedContentTypes() {
this.$http.get('ImageContainers/allowedContentTypes').then(res => {
const contentTypes = res.data.join(', ');
this.allowedContentTypes = contentTypes;
});
}
get contentTypesInfo() {
return this.$t('ContentTypesInfo', {
allowedContentTypes: this.allowedContentTypes
});
} }
/** /**

View File

@ -1,2 +1,3 @@
Upload new photo: Subir una nueva foto Upload new photo: Subir una nueva foto
Select an image: Selecciona una imagen Select an image: Selecciona una imagen
File name: Nombre del fichero

View File

@ -0,0 +1,57 @@
const md5 = require('md5');
module.exports = function(Self) {
Self.setup = function() {
Self.super_.setup.call(this);
this.remoteMethod('allowedContentTypes', {
description: 'Returns a list of allowed contentTypes',
accessType: 'READ',
returns: {
type: ['Object'],
root: true
},
http: {
path: `/allowedContentTypes`,
verb: 'GET'
}
});
};
/**
* Returns a container instance
* If doesn't exists creates a new one
*
* @param {String} name Container name
* @return {Object} Container instance
*/
Self.container = async function(name) {
const models = Self.app.models;
let container;
try {
container = await models[this.modelName].getContainer(name);
} catch (err) {
if (err.code === 'ENOENT') {
container = await models[this.modelName].createContainer({
name: name
});
} else throw err;
}
return container;
};
Self.getHash = function(id) {
return md5(id.toString()).substring(0, 3);
};
Self.getFileExtension = function(fileName) {
return fileName.split('.').pop().toLowerCase();
};
Self.allowedContentTypes = async function() {
const connector = this.dataSource.connector;
const allowedContentTypes = connector.allowedContentTypes;
return allowedContentTypes;
};
};

View File

@ -0,0 +1,12 @@
{
"name": "Container",
"base": "VnModel",
"acls": [
{
"property": "status",
"principalType": "ROLE",
"principalId": "$everyone",
"permission": "ALLOW"
}
]
}

View File

@ -1,31 +0,0 @@
const uuid = require('uuid/v1');
const md5 = require('md5');
module.exports = app => {
const storageConnector = app.dataSources.dmsStorage.connector;
storageConnector.getFilename = function(file) {
return `${uuid()}.${storageConnector.getFileExtension(file.name)}`;
};
storageConnector.getFileExtension = function(fileName) {
return fileName.split('.').pop().toLowerCase();
};
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);
}; */
};

View File

@ -17,6 +17,26 @@
"connectTimeout": 40000, "connectTimeout": 40000,
"acquireTimeout": 20000 "acquireTimeout": 20000
}, },
"tempStorage": {
"name": "tempStorage",
"connector": "loopback-component-storage",
"provider": "filesystem",
"root": "./storage/tmp",
"maxFileSize": "262144000",
"allowedContentTypes": [
"application/x-7z-compressed",
"application/x-zip-compressed",
"application/x-rar-compressed",
"application/octet-stream",
"application/pdf",
"application/zip",
"application/rar",
"multipart/x-zip",
"image/png",
"image/jpeg",
"image/jpg"
]
},
"dmsStorage": { "dmsStorage": {
"name": "dmsStorage", "name": "dmsStorage",
"connector": "loopback-component-storage", "connector": "loopback-component-storage",

View File

@ -49,5 +49,8 @@
}, },
"Application": { "Application": {
"dataSource": "vn" "dataSource": "vn"
},
"Container": {
"dataSource": "vn"
} }
} }

View File

@ -1,23 +0,0 @@
module.exports = Self => {
Self.remoteMethodCtx('allowedContentTypes', {
description: 'Returns a list of allowed contentTypes',
accessType: 'READ',
returns: {
type: ['Object'],
root: true
},
http: {
path: `/allowedContentTypes`,
verb: 'GET'
}
});
Self.allowedContentTypes = async() => {
const storageConnector = Self.app.dataSources.dmsStorage.connector;
const allowedContentTypes = storageConnector.allowedContentTypes;
const modelAllowedContentTypes = Self.definition.settings.allowedContentTypes;
return modelAllowedContentTypes || allowedContentTypes;
};
};

View File

@ -1,4 +1,3 @@
module.exports = Self => { module.exports = Self => {
require('../methods/client-dms/removeFile')(Self); require('../methods/client-dms/removeFile')(Self);
require('../methods/client-dms/allowedContentTypes')(Self);
}; };

View File

@ -26,7 +26,7 @@ class Controller extends Section {
} }
getAllowedContentTypes() { getAllowedContentTypes() {
this.$http.get('clientDms/allowedContentTypes').then(res => { this.$http.get('DmsContainers/allowedContentTypes').then(res => {
const contentTypes = res.data.join(', '); const contentTypes = res.data.join(', ');
this.allowedContentTypes = contentTypes; this.allowedContentTypes = contentTypes;
}); });

View File

@ -17,7 +17,7 @@ class Controller extends Section {
} }
getAllowedContentTypes() { getAllowedContentTypes() {
this.$http.get('clientDms/allowedContentTypes').then(res => { this.$http.get('DmsContainers/allowedContentTypes').then(res => {
const contentTypes = res.data.join(', '); const contentTypes = res.data.join(', ');
this.allowedContentTypes = contentTypes; this.allowedContentTypes = contentTypes;
}); });

View File

@ -33,8 +33,10 @@ module.exports = Self => {
// Exit loop // Exit loop
if (!image) return clearInterval(timer); if (!image) return clearInterval(timer);
const fileName = `${image.itemFk}.png`; const srcFile = image.url.split('/').pop();
const filePath = path.join(tempPath, fileName); const fileName = srcFile.split('.')[0];
const file = `${fileName}.png`;
const filePath = path.join(tempPath, file);
const writeStream = fs.createWriteStream(filePath); const writeStream = fs.createWriteStream(filePath);
writeStream.on('open', () => { writeStream.on('open', () => {
@ -57,7 +59,7 @@ module.exports = Self => {
writeStream.on('finish', async function() { writeStream.on('finish', async function() {
try { try {
await models.Image.registerImage('catalog', filePath, image.itemFk); await models.Image.registerImage('catalog', filePath, fileName, 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,23 +0,0 @@
module.exports = Self => {
Self.remoteMethodCtx('allowedContentTypes', {
description: 'Returns a list of allowed contentTypes',
accessType: 'READ',
returns: {
type: ['Object'],
root: true
},
http: {
path: `/allowedContentTypes`,
verb: 'GET'
}
});
Self.allowedContentTypes = async() => {
const storageConnector = Self.app.dataSources.dmsStorage.connector;
const allowedContentTypes = storageConnector.allowedContentTypes;
const modelAllowedContentTypes = Self.definition.settings.allowedContentTypes;
return modelAllowedContentTypes || allowedContentTypes;
};
};

View File

@ -1,4 +1,3 @@
module.exports = Self => { module.exports = Self => {
require('../methods/ticket-dms/removeFile')(Self); require('../methods/ticket-dms/removeFile')(Self);
require('../methods/ticket-dms/allowedContentTypes')(Self);
}; };

View File

@ -25,7 +25,7 @@ class Controller extends Section {
} }
getAllowedContentTypes() { getAllowedContentTypes() {
this.$http.get('ticketDms/allowedContentTypes').then(res => { this.$http.get('DmsContainers/allowedContentTypes').then(res => {
const contentTypes = res.data.join(', '); const contentTypes = res.data.join(', ');
this.allowedContentTypes = contentTypes; this.allowedContentTypes = contentTypes;
}); });

View File

@ -16,7 +16,7 @@ class Controller extends Section {
} }
getAllowedContentTypes() { getAllowedContentTypes() {
this.$http.get('ticketDms/allowedContentTypes').then(res => { this.$http.get('DmsContainers/allowedContentTypes').then(res => {
const contentTypes = res.data.join(', '); const contentTypes = res.data.join(', ');
this.allowedContentTypes = contentTypes; this.allowedContentTypes = contentTypes;
}); });

View File

@ -1,23 +0,0 @@
module.exports = Self => {
Self.remoteMethodCtx('allowedContentTypes', {
description: 'Returns a list of allowed contentTypes',
accessType: 'READ',
returns: {
type: ['Object'],
root: true
},
http: {
path: `/allowedContentTypes`,
verb: 'GET'
}
});
Self.allowedContentTypes = async() => {
const storageConnector = Self.app.dataSources.dmsStorage.connector;
const allowedContentTypes = storageConnector.allowedContentTypes;
const modelAllowedContentTypes = Self.definition.settings.allowedContentTypes;
return modelAllowedContentTypes || allowedContentTypes;
};
};

View File

@ -1,5 +1,4 @@
module.exports = Self => { module.exports = Self => {
require('../methods/travel-thermograph/allowedContentTypes')(Self);
require('../methods/travel-thermograph/getThermographTemperatures')(Self); require('../methods/travel-thermograph/getThermographTemperatures')(Self);
}; };

View File

@ -21,7 +21,7 @@ class Controller extends Section {
} }
getAllowedContentTypes() { getAllowedContentTypes() {
this.$http.get('TravelThermographs/allowedContentTypes').then(res => { this.$http.get('DmsContainers/allowedContentTypes').then(res => {
const contentTypes = res.data.join(', '); const contentTypes = res.data.join(', ');
this.allowedContentTypes = contentTypes; this.allowedContentTypes = contentTypes;
}); });

View File

@ -17,7 +17,7 @@ class Controller extends Section {
} }
getAllowedContentTypes() { getAllowedContentTypes() {
this.$http.get('TravelThermographs/allowedContentTypes').then(res => { this.$http.get('DmsContainers/allowedContentTypes').then(res => {
const contentTypes = res.data.join(', '); const contentTypes = res.data.join(', ');
this.allowedContentTypes = contentTypes; this.allowedContentTypes = contentTypes;
}); });

View File

@ -1,23 +0,0 @@
module.exports = Self => {
Self.remoteMethodCtx('allowedContentTypes', {
description: 'Returns a list of allowed contentTypes',
accessType: 'READ',
returns: {
type: ['Object'],
root: true
},
http: {
path: `/allowedContentTypes`,
verb: 'GET'
}
});
Self.allowedContentTypes = async() => {
const storageConnector = Self.app.dataSources.dmsStorage.connector;
const allowedContentTypes = storageConnector.allowedContentTypes;
const modelAllowedContentTypes = Self.definition.settings.allowedContentTypes;
return modelAllowedContentTypes || allowedContentTypes;
};
};

View File

@ -1,7 +1,6 @@
module.exports = Self => { module.exports = Self => {
require('../methods/worker-dms/downloadFile')(Self); require('../methods/worker-dms/downloadFile')(Self);
require('../methods/worker-dms/removeFile')(Self); require('../methods/worker-dms/removeFile')(Self);
require('../methods/worker-dms/allowedContentTypes')(Self);
require('../methods/worker-dms/filter')(Self); require('../methods/worker-dms/filter')(Self);
Self.isMine = async function(ctx, dmsId) { Self.isMine = async function(ctx, dmsId) {

View File

@ -26,7 +26,7 @@ class Controller extends Section {
} }
getAllowedContentTypes() { getAllowedContentTypes() {
this.$http.get('workerDms/allowedContentTypes').then(res => { this.$http.get('DmsContainers/allowedContentTypes').then(res => {
const contentTypes = res.data.join(', '); const contentTypes = res.data.join(', ');
this.allowedContentTypes = contentTypes; this.allowedContentTypes = contentTypes;
}); });

View File

@ -17,7 +17,7 @@ class Controller extends Section {
} }
getAllowedContentTypes() { getAllowedContentTypes() {
this.$http.get('WorkerDms/allowedContentTypes').then(res => { this.$http.get('DmsContainers/allowedContentTypes').then(res => {
const contentTypes = res.data.join(', '); const contentTypes = res.data.join(', ');
this.allowedContentTypes = contentTypes; this.allowedContentTypes = contentTypes;
}); });

View File

@ -60,7 +60,7 @@
</vn-icon-button> </vn-icon-button>
</vn-td> </vn-td>
<vn-td shrink> <vn-td shrink>
<vn-icon-button ui-sref="worker.card.edit({dmsId: {{::document.dmsFk}}})" <vn-icon-button ui-sref="worker.card.dms.edit({dmsId: {{::document.dmsFk}}})"
icon="edit" icon="edit"
title="{{'Edit file' | translate}}"> title="{{'Edit file' | translate}}">
</vn-icon-button> </vn-icon-button>

View File

@ -43,6 +43,6 @@
"pool": true "pool": true
}, },
"storage": { "storage": {
"root": "./e2e/dms" "root": "./storage/dms"
} }
} }

View File

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 23 KiB

View File

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 30 KiB

View File

Before

Width:  |  Height:  |  Size: 1.4 MiB

After

Width:  |  Height:  |  Size: 1.4 MiB

View File

Before

Width:  |  Height:  |  Size: 1.4 MiB

After

Width:  |  Height:  |  Size: 1.4 MiB

View File

Before

Width:  |  Height:  |  Size: 86 KiB

After

Width:  |  Height:  |  Size: 86 KiB

View File

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 25 KiB

View File

Before

Width:  |  Height:  |  Size: 81 KiB

After

Width:  |  Height:  |  Size: 81 KiB

View File

Before

Width:  |  Height:  |  Size: 74 KiB

After

Width:  |  Height:  |  Size: 74 KiB

View File

Before

Width:  |  Height:  |  Size: 33 KiB

After

Width:  |  Height:  |  Size: 33 KiB

View File

Before

Width:  |  Height:  |  Size: 98 KiB

After

Width:  |  Height:  |  Size: 98 KiB

View File

Before

Width:  |  Height:  |  Size: 492 KiB

After

Width:  |  Height:  |  Size: 492 KiB

View File

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 18 KiB

View File

Before

Width:  |  Height:  |  Size: 48 KiB

After

Width:  |  Height:  |  Size: 48 KiB

View File

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 23 KiB

View File

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 30 KiB

View File

Before

Width:  |  Height:  |  Size: 67 KiB

After

Width:  |  Height:  |  Size: 67 KiB

View File

Before

Width:  |  Height:  |  Size: 65 KiB

After

Width:  |  Height:  |  Size: 65 KiB

View File

Before

Width:  |  Height:  |  Size: 86 KiB

After

Width:  |  Height:  |  Size: 86 KiB

View File

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 25 KiB

View File

Before

Width:  |  Height:  |  Size: 81 KiB

After

Width:  |  Height:  |  Size: 81 KiB

View File

Before

Width:  |  Height:  |  Size: 74 KiB

After

Width:  |  Height:  |  Size: 74 KiB

View File

Before

Width:  |  Height:  |  Size: 33 KiB

After

Width:  |  Height:  |  Size: 33 KiB

View File

Before

Width:  |  Height:  |  Size: 98 KiB

After

Width:  |  Height:  |  Size: 98 KiB

View File

Before

Width:  |  Height:  |  Size: 38 KiB

After

Width:  |  Height:  |  Size: 38 KiB

View File

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 18 KiB

View File

Before

Width:  |  Height:  |  Size: 48 KiB

After

Width:  |  Height:  |  Size: 48 KiB

View File

Before

Width:  |  Height:  |  Size: 3.6 KiB

After

Width:  |  Height:  |  Size: 3.6 KiB

View File

Before

Width:  |  Height:  |  Size: 3.1 KiB

After

Width:  |  Height:  |  Size: 3.1 KiB

View File

Before

Width:  |  Height:  |  Size: 5.5 KiB

After

Width:  |  Height:  |  Size: 5.5 KiB

View File

Before

Width:  |  Height:  |  Size: 5.6 KiB

After

Width:  |  Height:  |  Size: 5.6 KiB

View File

Before

Width:  |  Height:  |  Size: 6.4 KiB

After

Width:  |  Height:  |  Size: 6.4 KiB

View File

Before

Width:  |  Height:  |  Size: 2.6 KiB

After

Width:  |  Height:  |  Size: 2.6 KiB

View File

Before

Width:  |  Height:  |  Size: 6.1 KiB

After

Width:  |  Height:  |  Size: 6.1 KiB

View File

Before

Width:  |  Height:  |  Size: 5.8 KiB

After

Width:  |  Height:  |  Size: 5.8 KiB

View File

Before

Width:  |  Height:  |  Size: 3.2 KiB

After

Width:  |  Height:  |  Size: 3.2 KiB

View File

Before

Width:  |  Height:  |  Size: 6.5 KiB

After

Width:  |  Height:  |  Size: 6.5 KiB

View File

Before

Width:  |  Height:  |  Size: 3.7 KiB

After

Width:  |  Height:  |  Size: 3.7 KiB

View File

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

Before

Width:  |  Height:  |  Size: 4.1 KiB

After

Width:  |  Height:  |  Size: 4.1 KiB

View File

Before

Width:  |  Height:  |  Size: 5.7 KiB

After

Width:  |  Height:  |  Size: 5.7 KiB

View File

Before

Width:  |  Height:  |  Size: 5.5 KiB

After

Width:  |  Height:  |  Size: 5.5 KiB

View File

Before

Width:  |  Height:  |  Size: 9.8 KiB

After

Width:  |  Height:  |  Size: 9.8 KiB

View File

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 10 KiB

View File

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

View File

Before

Width:  |  Height:  |  Size: 4.5 KiB

After

Width:  |  Height:  |  Size: 4.5 KiB

View File

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

View File

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 10 KiB

View File

Before

Width:  |  Height:  |  Size: 5.5 KiB

After

Width:  |  Height:  |  Size: 5.5 KiB

View File

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 13 KiB

View File

Before

Width:  |  Height:  |  Size: 6.3 KiB

After

Width:  |  Height:  |  Size: 6.3 KiB

View File

Before

Width:  |  Height:  |  Size: 3.0 KiB

After

Width:  |  Height:  |  Size: 3.0 KiB

View File

Before

Width:  |  Height:  |  Size: 7.2 KiB

After

Width:  |  Height:  |  Size: 7.2 KiB

View File

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 23 KiB

View File

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 30 KiB

View File

Before

Width:  |  Height:  |  Size: 3.7 MiB

After

Width:  |  Height:  |  Size: 3.7 MiB

View File

Before

Width:  |  Height:  |  Size: 1.7 MiB

After

Width:  |  Height:  |  Size: 1.7 MiB

View File

Before

Width:  |  Height:  |  Size: 86 KiB

After

Width:  |  Height:  |  Size: 86 KiB

Some files were not shown because too many files have changed in this diff Show More