adds width and height to uploads that match proper mime type, continues build to FileDetails

This commit is contained in:
James
2020-06-24 18:55:03 -04:00
parent b5f8178de2
commit a27f804b9c
15 changed files with 191 additions and 31 deletions

View File

@@ -1,30 +1,93 @@
import React from 'react';
import React, { useState } from 'react';
import PropTypes from 'prop-types';
import FileGraphic from '../../graphics/File';
import config from '../../../config';
import getThumbnail from '../../../../uploads/getThumbnail';
import Button from '../Button';
import formatFilesize from '../../../../uploads/formatFilesize';
import './index.scss';
const { serverURL } = config;
const baseClass = 'file-details';
const FileDetails = (props) => {
const { filename, mimeType, filesize } = props;
const {
filename, mimeType, filesize, staticURL, adminThumbnail, sizes, handleRemove, width, height,
} = props;
const [moreInfoOpen, setMoreInfoOpen] = useState(false);
const thumbnail = getThumbnail(mimeType, staticURL, filename, sizes, adminThumbnail);
return (
<div className={baseClass}>
<FileGraphic />
<header>
{!thumbnail && (
<FileGraphic />
)}
{thumbnail && (
<div className={`${baseClass}__thumbnail`}>
<img
src={`${serverURL}${thumbnail}`}
alt={filename}
/>
</div>
)}
<div className={`${baseClass}__main-detail`}>
<div className={`${baseClass}__url`}>
<a
href={`${serverURL}${staticURL}/${filename}`}
target="_blank"
rel="noopener noreferrer"
>
{filename}
</a>
</div>
<div className={`${baseClass}__meta`}>
{formatFilesize(filesize)}
{(width && height) && (
<>
&nbsp;-&nbsp;
{width}
x
{height}
</>
)}
{mimeType && (
<>
&nbsp;-&nbsp;
{mimeType}
</>
)}
</div>
</div>
<Button
icon="x"
round
buttonStyle="icon-label"
iconStyle="with-border"
onClick={handleRemove}
/>
</header>
<div className={`${baseClass}__more-info`}>
test
</div>
</div>
);
};
FileDetails.defaultProps = {
sizes: null,
adminThumbnail: undefined,
};
FileDetails.propTypes = {
filename: PropTypes.string.isRequired,
mimeType: PropTypes.string.isRequired,
filesize: PropTypes.number.isRequired,
sizes: PropTypes.shape({}),
staticURL: PropTypes.string.isRequired,
adminThumbnail: PropTypes.string,
};
export default FileDetails;

View File

@@ -0,0 +1,29 @@
@import '../../../scss/styles.scss';
.file-details {
header {
display: flex;
align-items: flex-start;
.btn {
margin: $baseline;
}
}
&__thumbnail {
width: base(6);
height: base(4.8);
img {
width: 100%;
height: 100%;
object-fit: cover;
}
}
&__main-detail {
padding: $baseline;
width: auto;
flex-grow: 1;
}
}

View File

@@ -17,7 +17,7 @@ import './index.scss';
const baseClass = 'form';
const reduceFieldsToValues = (fields) => {
const reduceFieldsToValues = (fields, flatten) => {
const data = {};
Object.keys(fields).forEach((key) => {
@@ -26,8 +26,11 @@ const reduceFieldsToValues = (fields) => {
}
});
const unflattened = unflatten(data, { safe: true });
return unflattened;
if (flatten) {
return unflatten(data, { safe: true });
}
return data;
};
const Form = (props) => {
@@ -62,7 +65,7 @@ const Form = (props) => {
}, [fields]);
const getData = useCallback(() => {
return reduceFieldsToValues(fields);
return reduceFieldsToValues(fields, true);
}, [fields]);
const getSiblingData = useCallback((path) => {
@@ -84,7 +87,7 @@ const Form = (props) => {
}, {});
}
return reduceFieldsToValues(siblingFields);
return reduceFieldsToValues(siblingFields, true);
}, [fields]);
const getDataByPath = useCallback((path) => {
@@ -102,7 +105,7 @@ const Form = (props) => {
return matchedData;
}, {});
const values = reduceFieldsToValues(data);
const values = reduceFieldsToValues(data, true);
const unflattenedData = unflatten(values);
return unflattenedData?.[name];
}, [fields]);

View File

@@ -30,7 +30,10 @@ const File = (props) => {
const [selectingFile, setSelectingFile] = useState(false);
const [dragging, setDragging] = useState(false);
const [dragCounter, setDragCounter] = useState(0);
const { initialData = {} } = props;
const {
initialData = {}, adminThumbnail, imageSizes, staticURL,
} = props;
const { filename } = initialData;
const {
@@ -129,7 +132,12 @@ const File = (props) => {
return (
<div className={classes}>
{filename && (
<FileDetails {...initialData} />
<FileDetails
{...initialData}
staticURL={staticURL}
imageSizes={imageSizes}
adminThumbnail={adminThumbnail}
/>
)}
{!filename && (
<div className={`${baseClass}__upload`}>
@@ -178,6 +186,7 @@ const File = (props) => {
File.defaultProps = {
initialData: undefined,
adminThumbnail: undefined,
};
File.propTypes = {
@@ -187,6 +196,8 @@ File.propTypes = {
mimeType: PropTypes.string,
filesize: PropTypes.number,
}),
staticURL: PropTypes.string.isRequired,
adminThumbnail: PropTypes.string,
};
export default File;

View File

@@ -17,6 +17,7 @@ const formatFields = (config, isEditing) => {
const uploadFields = [
{
type: 'file',
...config.upload,
},
];

View File

@@ -5,6 +5,8 @@ const executePolicy = require('../../auth/executePolicy');
const { MissingFile } = require('../../errors');
const resizeAndSave = require('../../uploads/imageResizer');
const getSafeFilename = require('../../uploads/getSafeFilename');
const getImageSize = require('../../uploads/getImageSize');
const imageMIMETypes = require('../../uploads/imageMIMETypes');
const performFieldOperations = require('../../fields/performFieldOperations');
@@ -39,7 +41,7 @@ const create = async (args) => {
// /////////////////////////////////////
if (args.config.upload) {
const { staticDir, imageSizes } = options.req.collection.config.upload;
const { staticDir } = options.req.collection.config.upload;
const fileData = {};
@@ -53,14 +55,18 @@ const create = async (args) => {
await options.req.files.file.mv(`${staticDir}/${fsSafeName}`);
if (imageMIMETypes.indexOf(options.req.files.file.mimetype) > -1) {
const dimensions = await getImageSize(`${staticDir}/${fsSafeName}`);
fileData.width = dimensions.width;
fileData.height = dimensions.height;
fileData.sizes = await resizeAndSave(options.config, fsSafeName, fileData.mimeType);
}
fileData.filename = fsSafeName;
fileData.filesize = options.req.files.file.size;
fileData.mimeType = options.req.files.file.mimetype;
if (imageSizes) {
fileData.sizes = await resizeAndSave(options.config, fsSafeName, fileData.mimeType);
}
options.data = {
...options.data,
...fileData,

View File

@@ -3,6 +3,9 @@ const overwriteMerge = require('../../utilities/overwriteMerge');
const executePolicy = require('../../auth/executePolicy');
const { NotFound, Forbidden } = require('../../errors');
const performFieldOperations = require('../../fields/performFieldOperations');
const imageMIMETypes = require('../../uploads/imageMIMETypes');
const getImageSize = require('../../uploads/getImageSize');
const getSafeFilename = require('../../uploads/getSafeFilename');
const resizeAndSave = require('../../uploads/imageResizer');
@@ -79,15 +82,21 @@ const update = async (args) => {
if (args.config.upload) {
const fileData = {};
const { staticDir, imageSizes } = args.config.upload;
const { staticDir } = args.config.upload;
if (args.req.files || args.req.files.file) {
await options.req.files.file.mv(`${staticDir}/${options.req.files.file.name}`);
const fsSafeName = await getSafeFilename(staticDir, options.req.files.file.name);
fileData.filename = options.req.files.file.name;
await options.req.files.file.mv(`${staticDir}/${fsSafeName}`);
if (imageSizes) {
fileData.sizes = await resizeAndSave(options.config, options.req.files.file.name);
fileData.filename = fsSafeName;
if (imageMIMETypes.indexOf(options.req.files.file.mimetype) > -1) {
const dimensions = await getImageSize(`${staticDir}/${fsSafeName}`);
fileData.width = dimensions.width;
fileData.height = dimensions.height;
fileData.sizes = await resizeAndSave(options.config, fsSafeName, fileData.mimeType);
}
options.data = {

View File

@@ -3,7 +3,7 @@ const express = require('express');
function initStatic() {
this.config.collections.forEach((collection) => {
if (collection.upload) {
this.express.use(`${collection.upload.staticURL}*`, express.static(collection.upload.staticDir));
this.express.use(`${collection.upload.staticURL}`, express.static(collection.upload.staticDir));
}
});
}

View File

@@ -0,0 +1,13 @@
function formatBytes(bytes, decimals = 0) {
if (bytes === 0) return '0 Bytes';
const k = 1024;
const dm = decimals < 0 ? 0 : decimals;
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return `${parseFloat((bytes / (k ** i)).toFixed(dm))} ${sizes[i]}`;
}
module.exports = formatBytes;

View File

@@ -0,0 +1,6 @@
const imageSize = require('image-size');
const { promisify } = require('util');
const getImageSize = promisify(imageSize);
module.exports = getImageSize;

View File

@@ -0,0 +1,15 @@
const imageMIMETypes = require('./imageMIMETypes');
const getThumbnail = (mimeType, staticURL, filename, sizes, adminThumbnail) => {
if (imageMIMETypes.indexOf(mimeType) > -1) {
if (sizes?.[adminThumbnail]?.filename) {
return `${staticURL}/${sizes[adminThumbnail].filename}`;
}
return `${staticURL}/${filename}`;
}
return false;
};
module.exports = getThumbnail;

View File

@@ -0,0 +1,3 @@
const types = ['image/jpeg', 'image/png', 'image/gif', 'image/svg+xml'];
module.exports = types;

View File

@@ -1,12 +1,9 @@
const fs = require('fs');
const sharp = require('sharp');
const sanitize = require('sanitize-filename');
const { promisify } = require('util');
const imageSize = require('image-size');
const getImageSize = require('./getImageSize');
const fileExists = require('./fileExists');
const sizeOf = promisify(imageSize);
function getOutputImage(sourceImage, size) {
const extension = sourceImage.split('.').pop();
const name = sanitize(sourceImage.substr(0, sourceImage.lastIndexOf('.')) || sourceImage);
@@ -33,7 +30,7 @@ module.exports = async function resizeAndSave(config, savedFilename, mimeType) {
const sourceImage = `${staticDir}/${savedFilename}`;
let sizes;
try {
const dimensions = await sizeOf(sourceImage);
const dimensions = await getImageSize(sourceImage);
sizes = imageSizes
.filter(desiredSize => desiredSize.width < dimensions.width)
.map(async (desiredSize) => {