diff --git a/demo/collections/File.js b/demo/collections/File.js
index d3b1891a43..b3c02f19d9 100644
--- a/demo/collections/File.js
+++ b/demo/collections/File.js
@@ -1,3 +1,5 @@
+const path = require('path');
+
module.exports = {
slug: 'files',
labels: {
@@ -6,7 +8,7 @@ module.exports = {
},
upload: {
staticURL: '/files',
- staticDir: 'demo/files',
+ staticDir: path.resolve(__dirname, '../files'),
},
useAsTitle: 'filename',
fields: [
diff --git a/demo/collections/Media.js b/demo/collections/Media.js
index bde1d14cba..dfa4d53716 100644
--- a/demo/collections/Media.js
+++ b/demo/collections/Media.js
@@ -1,3 +1,5 @@
+const path = require('path');
+
module.exports = {
slug: 'media',
labels: {
@@ -10,7 +12,7 @@ module.exports = {
},
upload: {
staticURL: '/media',
- staticDir: 'demo/media',
+ staticDir: path.resolve(__dirname, '../media'),
adminThumbnail: 'mobile',
imageSizes: [
{
diff --git a/src/client/components/elements/FileDetails/index.js b/src/client/components/elements/FileDetails/index.js
index f537f0efb1..359cc8b4e9 100644
--- a/src/client/components/elements/FileDetails/index.js
+++ b/src/client/components/elements/FileDetails/index.js
@@ -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 (
);
};
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;
diff --git a/src/client/components/elements/FileDetails/index.scss b/src/client/components/elements/FileDetails/index.scss
index e69de29bb2..908d7378a1 100644
--- a/src/client/components/elements/FileDetails/index.scss
+++ b/src/client/components/elements/FileDetails/index.scss
@@ -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;
+ }
+}
diff --git a/src/client/components/forms/Form/index.js b/src/client/components/forms/Form/index.js
index 2ce01f3345..c1e3a30909 100644
--- a/src/client/components/forms/Form/index.js
+++ b/src/client/components/forms/Form/index.js
@@ -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]);
diff --git a/src/client/components/forms/field-types/File/index.js b/src/client/components/forms/field-types/File/index.js
index 045ccb4911..3601b2b50e 100644
--- a/src/client/components/forms/field-types/File/index.js
+++ b/src/client/components/forms/field-types/File/index.js
@@ -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 (
{filename && (
-
+
)}
{!filename && (
@@ -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;
diff --git a/src/client/components/views/collections/Edit/formatFields.js b/src/client/components/views/collections/Edit/formatFields.js
index 0ef19ea95e..43ec0e3e53 100644
--- a/src/client/components/views/collections/Edit/formatFields.js
+++ b/src/client/components/views/collections/Edit/formatFields.js
@@ -17,6 +17,7 @@ const formatFields = (config, isEditing) => {
const uploadFields = [
{
type: 'file',
+ ...config.upload,
},
];
diff --git a/src/collections/operations/create.js b/src/collections/operations/create.js
index e2901cc56d..fe8ddc8260 100644
--- a/src/collections/operations/create.js
+++ b/src/collections/operations/create.js
@@ -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,
diff --git a/src/collections/operations/update.js b/src/collections/operations/update.js
index de03a21732..eb84532067 100644
--- a/src/collections/operations/update.js
+++ b/src/collections/operations/update.js
@@ -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 = {
diff --git a/src/express/static.js b/src/express/static.js
index 9f6925ea9c..bd336e3e2a 100644
--- a/src/express/static.js
+++ b/src/express/static.js
@@ -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));
}
});
}
diff --git a/src/uploads/formatFilesize.js b/src/uploads/formatFilesize.js
new file mode 100644
index 0000000000..1b2d92c174
--- /dev/null
+++ b/src/uploads/formatFilesize.js
@@ -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;
diff --git a/src/uploads/getImageSize.js b/src/uploads/getImageSize.js
new file mode 100644
index 0000000000..c19b7ada75
--- /dev/null
+++ b/src/uploads/getImageSize.js
@@ -0,0 +1,6 @@
+const imageSize = require('image-size');
+const { promisify } = require('util');
+
+const getImageSize = promisify(imageSize);
+
+module.exports = getImageSize;
diff --git a/src/uploads/getThumbnail.js b/src/uploads/getThumbnail.js
new file mode 100644
index 0000000000..61b9285760
--- /dev/null
+++ b/src/uploads/getThumbnail.js
@@ -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;
diff --git a/src/uploads/imageMIMETypes.js b/src/uploads/imageMIMETypes.js
new file mode 100644
index 0000000000..192a293c43
--- /dev/null
+++ b/src/uploads/imageMIMETypes.js
@@ -0,0 +1,3 @@
+const types = ['image/jpeg', 'image/png', 'image/gif', 'image/svg+xml'];
+
+module.exports = types;
diff --git a/src/uploads/imageResizer.js b/src/uploads/imageResizer.js
index 7d05277842..b94b62e413 100644
--- a/src/uploads/imageResizer.js
+++ b/src/uploads/imageResizer.js
@@ -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) => {