{props.columns.map((col, i) => {
+ const value = col.handler ? col.handler(row[col.key]) : row[col.key];
return (
|
- {row[col.key]
- ? row[col.key]
+ {value
+ ? value
:
}
|
diff --git a/src/components/modals/asModal/index.js b/src/components/modals/asModal/index.js
new file mode 100644
index 0000000000..c29b6c300d
--- /dev/null
+++ b/src/components/modals/asModal/index.js
@@ -0,0 +1,106 @@
+///////////////////////////////////////////////////////
+// Takes a modal component and
+// a slug to match against a 'modal' URL param
+///////////////////////////////////////////////////////
+
+import React, { Component } from 'react';
+import { createPortal } from 'react-dom';
+import { connect } from 'react-redux';
+import { withRouter } from 'react-router';
+import queryString from 'qs';
+import { Close, Button } from 'payload/components';
+
+import './index.scss';
+
+const mapStateToProps = state => ({
+ searchParams: state.common.searchParams
+})
+
+const asModal = (PassedComponent, modalSlug) => {
+
+ class AsModal extends Component {
+
+ constructor(props) {
+ super(props);
+
+ this.state = {
+ open: false,
+ el: null
+ }
+ }
+
+ bindEsc = event => {
+ if (event.keyCode === 27) {
+ const params = { ...this.props.searchParams };
+ delete params.modal;
+
+ this.props.history.push({
+ search: queryString.stringify(params)
+ })
+ }
+ }
+
+ isOpen = () => {
+
+ // Slug can come from either a HOC or from a prop
+ const slug = this.props.modalSlug ? this.props.modalSlug : modalSlug;
+
+ if (this.props.searchParams.modal === slug) {
+ return true;
+ }
+
+ return false;
+ }
+
+ componentDidMount() {
+ document.addEventListener('keydown', this.bindEsc, false);
+
+ if (this.isOpen()) {
+ this.setState({ open: true })
+ }
+
+ // Slug can come from either a HOC or from a prop
+ const slug = this.props.modalSlug ? this.props.modalSlug : modalSlug;
+
+ this.setState({
+ el: document.querySelector(`#${slug}`)
+ })
+ }
+
+ componentWillUnmount() {
+ document.removeEventListener('keydown', this.bindEsc, false);
+ }
+
+ componentDidUpdate(prevProps, prevState) {
+
+ let open = this.isOpen();
+
+ if (open !== prevState.open && open) {
+ this.setState({ open: true })
+ } else if (open !== prevState.open) {
+ this.setState({ open: false })
+ }
+ }
+
+ render() {
+
+ // Slug can come from either a HOC or from a prop
+ const slug = this.props.modalSlug ? this.props.modalSlug : modalSlug;
+ const modalDomNode = document.getElementById('portal');
+
+ return createPortal(
+ ,
+ modalDomNode
+ );
+ }
+ }
+
+ return withRouter(connect(mapStateToProps)(AsModal));
+}
+
+export default asModal;
diff --git a/src/components/modals/asModal/index.scss b/src/components/modals/asModal/index.scss
new file mode 100644
index 0000000000..d3d25c9f48
--- /dev/null
+++ b/src/components/modals/asModal/index.scss
@@ -0,0 +1,21 @@
+@import '~payload/scss/styles';
+
+.modal {
+ transform: translateZ(0);
+ opacity: 0;
+ visibility: hidden;
+ position: fixed;
+ top: 0;
+ right: 0;
+ bottom: 0;
+ left: 0;
+ z-index: -1;
+ height: 100vh;
+ background-color: rgba(white, .96);
+
+ &.open {
+ opacity: 1;
+ visibility: visible;
+ z-index: $z-modal;
+ }
+}
diff --git a/src/components/modules/SearchableTable/index.js b/src/components/modules/SearchableTable/index.js
index 559f48f79f..960bdad0aa 100644
--- a/src/components/modules/SearchableTable/index.js
+++ b/src/components/modules/SearchableTable/index.js
@@ -16,8 +16,9 @@ class SearchableTable extends Component {
key: '_id',
label: 'ID'
}, {
- key: 'published',
- label: 'Published On'
+ key: 'createdAt',
+ label: 'Created At',
+ handler: time => new Date(time).toDateString()
}]
}
}
diff --git a/src/components/modules/UploadMedia/index.js b/src/components/modules/UploadMedia/index.js
new file mode 100644
index 0000000000..13dcb0c78c
--- /dev/null
+++ b/src/components/modules/UploadMedia/index.js
@@ -0,0 +1,166 @@
+import React, { Component } from 'react';
+import { createPortal } from 'react-dom';
+import { connect } from 'react-redux';
+import { Button } from 'payload/components';
+import api from 'payload/api';
+
+import './index.scss';
+
+const mapState = state => ({
+ config: state.common.config
+})
+
+class UploadMedia extends Component {
+
+ constructor() {
+ super();
+
+ this.state = {
+ dragging: false,
+ selectingFile: false,
+ files: null
+ }
+
+ this.dropRef = React.createRef();
+ this.dragCounter = 0;
+ }
+
+ componentDidMount() {
+ let div = this.dropRef.current
+ div.addEventListener('dragenter', this.handleDragIn)
+ div.addEventListener('dragleave', this.handleDragOut)
+ div.addEventListener('dragover', this.handleDrag)
+ div.addEventListener('drop', this.handleDrop)
+ }
+
+ componentWillUnmount() {
+ let div = this.dropRef.current
+ div.removeEventListener('dragenter', this.handleDragIn)
+ div.removeEventListener('dragleave', this.handleDragOut)
+ div.removeEventListener('dragover', this.handleDrag)
+ div.removeEventListener('drop', this.handleDrop)
+ }
+
+ handleDrag = e => {
+ e.preventDefault();
+ e.stopPropagation();
+ }
+
+ handleDragIn = e => {
+ e.preventDefault();
+ e.stopPropagation();
+ this.dragCounter++;
+ if (e.dataTransfer.items && e.dataTransfer.items.length > 0) {
+ this.setState({ dragging: true })
+ }
+ }
+
+ handleDragOut = e => {
+ e.preventDefault();
+ e.stopPropagation();
+ this.dragCounter--;
+ if (this.dragCounter > 0) return;
+ this.setState({ dragging: false })
+ }
+
+ handleDrop = e => {
+ e.preventDefault();
+ e.stopPropagation();
+ this.setState({ dragging: false })
+ if (e.dataTransfer.files && e.dataTransfer.files.length > 0) {
+ this.setState({
+ files: e.dataTransfer.files,
+ dragging: false
+ })
+
+ e.dataTransfer.clearData();
+ this.dragCounter = 0;
+ } else {
+ this.setState({ dragging: false })
+ }
+ }
+
+ handleSelectFile = e => {
+ e.preventDefault();
+ e.stopPropagation();
+ this.setState({
+ selectingFile: true
+ })
+ }
+
+ setSelectingFile = selectingFile => {
+ this.setState({ selectingFile })
+ }
+
+ render() {
+ return (
+
+ Drag and drop a file here
+ —or—
+
+
+
+ )
+ }
+}
+
+// Need to set up a separate component with a Portal
+// to make sure forms are not embedded within other forms
+class UploadMediaForm extends Component {
+
+ constructor() {
+ super();
+ this.state = {
+ submitted: false
+ }
+ this.formRef = React.createRef();
+ this.inputRef = React.createRef();
+ }
+
+ componentDidUpdate(prevProps) {
+ if (prevProps.files !== this.props.files && this.props.files) {
+ this.inputRef.current.files = this.props.files;
+ this.handleSubmit();
+ }
+
+ if (prevProps.selectingFile !== this.props.selectingFile && this.props.selectingFile) {
+ this.inputRef.current.click();
+ this.props.setSelectingFile(false);
+ }
+ }
+
+ componentDidMount() {
+ this.inputRef.current.addEventListener('change', e => {
+ this.props.setSelectingFile(false);
+
+ // If there are files, submit the form
+ if (this.inputRef.current.files[0]) {
+ this.handleSubmit();
+ }
+ }, false);
+ }
+
+ componentWillUnmount() {
+ this.inputRef.current.removeEventListener('change');
+ }
+
+ handleSubmit = () => {
+ const data = new FormData(this.formRef.current);
+ api.requests.post(`${this.props.config.serverUrl}/upload`, data).then(
+ res => console.log(res),
+ err => {
+ console.warn(err);
+ }
+ );
+ }
+
+ render() {
+ return createPortal(
+ ,
+ document.getElementById('portal'))
+ }
+}
+
+export default connect(mapState)(UploadMedia);
diff --git a/src/components/modules/UploadMedia/index.scss b/src/components/modules/UploadMedia/index.scss
new file mode 100644
index 0000000000..77b5197946
--- /dev/null
+++ b/src/components/modules/UploadMedia/index.scss
@@ -0,0 +1,22 @@
+@import '~payload/scss/styles';
+
+.upload-media {
+ padding: rem(1);
+ display: flex;
+ align-items: center;
+ flex-direction: column;
+ border: $stroke-width solid $primary;
+
+ &.dragging {
+ background: rgba($primary, .1);
+ }
+
+ .or {
+ @extend %uppercase-label;
+ display: block;
+ }
+
+ .btn {
+ margin: rem(.125) 0 0;
+ }
+}
diff --git a/src/components/views/MediaLibrary/index.js b/src/components/views/MediaLibrary/index.js
new file mode 100644
index 0000000000..8da8f88d12
--- /dev/null
+++ b/src/components/views/MediaLibrary/index.js
@@ -0,0 +1,16 @@
+import React from 'react';
+import { SetStepNav } from 'payload/components';
+
+const MediaLibrary = props => {
+
+ return (
+
+
+
Media Library
+
+ )
+}
+
+export default MediaLibrary;
diff --git a/src/controllers/uploads.controller.js b/src/controllers/uploads.controller.js
index 7b82be3901..804929f310 100644
--- a/src/controllers/uploads.controller.js
+++ b/src/controllers/uploads.controller.js
@@ -1,6 +1,6 @@
import mkdirp from 'mkdirp';
import { resize } from '../../src/utils/imageResizer';
-
+import Media from '../models/Media.model';
function upload(req, res, next, config) {
if (Object.keys(req.files).length === 0) {
@@ -21,7 +21,23 @@ function upload(req, res, next, config) {
if (req.files.file.mimetype.split('/')[0] === 'image') {
resize(config, req.files.file);
}
- res.status(200).send('File uploaded.');
+
+ Media.create({
+ name: req.files.file.name,
+ filename: req.files.file.name
+ }, (err, result) => {
+ if (err)
+ return res.status(500).json({ error: err });
+
+ return res.status(201)
+ .json({
+ message: 'success',
+ result: {
+ id: result.id,
+ name: result.name
+ }
+ });
+ });
})
}
diff --git a/src/index.js b/src/index.js
index 800699d116..5c41b62508 100644
--- a/src/index.js
+++ b/src/index.js
@@ -6,7 +6,8 @@ import methodOverride from 'method-override';
import jwtStrategy from './auth/jwt';
import User from '../demo/User/User.model';
import fileUpload from 'express-fileupload';
-import assetRoutes from './routes/uploads.routes'
+import mediaRoutes from './routes/Media.routes';
+import config from '../demo/payload.config';
import locale from './middleware/locale';
import expressGraphQL from 'express-graphql';
@@ -48,7 +49,7 @@ module.exports = {
});
}
- options.router.use('/upload', assetRoutes(options.config));
+ options.router.use(options.config.staticUrl, mediaRoutes(options.config));
options.app.use(express.json());
options.app.use(methodOverride('X-HTTP-Method-Override'));
diff --git a/src/models/Media.model.js b/src/models/Media.model.js
new file mode 100644
index 0000000000..77ed1dafeb
--- /dev/null
+++ b/src/models/Media.model.js
@@ -0,0 +1,15 @@
+import mongoose from 'mongoose';
+import buildQuery from '../plugins/buildQuery';
+import paginate from '../plugins/paginate';
+
+const MediaSchema = new mongoose.Schema({
+ name: { type: String },
+ caption: { type: String },
+ description: { type: String },
+ filename: { type: String },
+});
+
+MediaSchema.plugin(paginate);
+MediaSchema.plugin(buildQuery);
+
+export default mongoose.model('Media', MediaSchema);
diff --git a/src/reducers/common.js b/src/reducers/common.js
index ae8322a2fe..e9ea1a69a8 100644
--- a/src/reducers/common.js
+++ b/src/reducers/common.js
@@ -4,7 +4,6 @@ const defaultState = {
windowHeight: 900,
viewWidth: false,
viewHeight: false,
- modalState: false,
stepNav: [],
locale: null,
config: null,
@@ -39,13 +38,6 @@ export default (state = defaultState, action) => {
viewHeight: action.payload.height
};
- case 'SET_MODAL':
-
- return {
- ...state,
- modalStatus: action.payload
- };
-
case 'SET_STEP_NAV':
return {
diff --git a/src/requestHandlers/query.js b/src/requestHandlers/query.js
index e682203cf9..dff61082fd 100644
--- a/src/requestHandlers/query.js
+++ b/src/requestHandlers/query.js
@@ -9,7 +9,7 @@ const query = (req, res) => {
return res.json({
...result,
docs: result.docs.map(doc => {
- if (req.locale) {
+ if (req.locale && doc.setLocale) {
doc.setLocale(req.locale, req.query['fallback-locale']);
}
diff --git a/src/routes/media.routes.js b/src/routes/media.routes.js
new file mode 100644
index 0000000000..d2e13f1f63
--- /dev/null
+++ b/src/routes/media.routes.js
@@ -0,0 +1,29 @@
+import express from 'express';
+import passport from 'passport';
+import uploadsCtrl from '../controllers/uploads.controller';
+import { query } from '../requestHandlers';
+import bindModel from '../middleware/bindModel';
+import Media from '../models/Media.model';
+
+const router = express.Router();
+const mediaRoutes = config => {
+
+ router.all('*', bindModel(Media));
+
+ router
+ .route('')
+ .post(
+ passport.authenticate('jwt', { session: false }),
+ (req, res, next) => uploadsCtrl.upload(req, res, next, config)
+ );
+
+ router.route('')
+ .get(
+ passport.authenticate('jwt', { session: false }),
+ query
+ );
+
+ return router;
+};
+
+export default mediaRoutes;
diff --git a/src/routes/uploads.routes.js b/src/routes/uploads.routes.js
deleted file mode 100644
index a7993ccddf..0000000000
--- a/src/routes/uploads.routes.js
+++ /dev/null
@@ -1,17 +0,0 @@
-import express from 'express';
-import passport from 'passport';
-import uploadsCtrl from '../controllers/uploads.controller';
-
-const router = new express.Router();
-const uploadRouter = config => {
- router
- .route('')
- .post(
- passport.authenticate('jwt', { session: false }),
- (req, res, next) => uploadsCtrl.upload(req, res, next, config)
- );
-
- return router;
-};
-
-export default uploadRouter;
diff --git a/src/scss/styles.scss b/src/scss/styles.scss
index 75f113806e..11e73da5ae 100644
--- a/src/scss/styles.scss
+++ b/src/scss/styles.scss
@@ -1,3 +1,4 @@
+@import 'z-index';
@import 'vars';
@import 'structure';
@import 'type';
diff --git a/src/scss/z-index.scss b/src/scss/z-index.scss
new file mode 100644
index 0000000000..ec02636df2
--- /dev/null
+++ b/src/scss/z-index.scss
@@ -0,0 +1,9 @@
+/////////////////////////////
+// Z-INDEX CHART
+/////////////////////////////
+
+$z-bg: 10;
+$z-page: 20;
+$z-footer: 30;
+$z-modal: 40;
+$z-header: 50;