Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Media Library #554

Merged
merged 1 commit into from
Nov 8, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion .babelrc
Original file line number Diff line number Diff line change
@@ -1,11 +1,21 @@
{
"presets": [["env", { "modules": false }], "stage-1", "react"],
"presets": [
["env", {
"modules": false
}],
"stage-1",
"react"
],
"plugins": [
"react-hot-loader/babel",
"lodash",
["babel-plugin-transform-builtin-extend", {
"globals": ["Error"]
}],
["transform-runtime", {
"useBuiltIns": true,
"useESModules": true
}]
],
"env": {
"test": {
Expand Down
4 changes: 3 additions & 1 deletion example/config.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
backend:
name: test-repo
name: github
repo: netlify/netlify-cms
branch: pr-554-backend

media_folder: "assets/uploads"

Expand Down
5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@
"babel-loader": "^7.0.0",
"babel-plugin-lodash": "^3.2.0",
"babel-plugin-transform-builtin-extend": "^1.1.0",
"babel-plugin-transform-runtime": "^6.23.0",
"babel-preset-env": "^1.6.0",
"babel-preset-react": "^6.23.0",
"babel-preset-stage-1": "^6.22.0",
Expand All @@ -108,8 +109,8 @@
"postcss-cssnext": "^3.0.2",
"postcss-import": "^11.0.0",
"postcss-loader": "^2.0.7",
"react-test-renderer": "^16.0.0",
"react-hot-loader": "^3.0.0-beta.7",
"react-test-renderer": "^16.0.0",
"style-loader": "^0.18.2",
"stylefmt": "^4.3.1",
"stylelint": "^7.9.0",
Expand All @@ -126,6 +127,7 @@
"dependencies": {
"classnames": "^2.2.5",
"create-react-class": "^15.6.0",
"focus-trap-react": "^3.0.3",
"fuzzy": "^0.1.1",
"gotrue-js": "^0.9.11",
"gray-matter": "^3.0.6",
Expand Down Expand Up @@ -184,6 +186,7 @@
"unified": "^6.1.4",
"unist-builder": "^1.0.2",
"unist-util-visit-parents": "^1.1.1",
"url": "^0.11.0",
"uuid": "^3.1.0"
},
"optionalDependencies": {
Expand Down
1 change: 1 addition & 0 deletions src/actions/editorialWorkflow.js
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ function unpublishedEntryPersistedFail(error, transactionID) {
type: UNPUBLISHED_ENTRY_PERSIST_FAILURE,
payload: { error },
optimist: { type: REVERT, id: transactionID },
error,
};
}

Expand Down
183 changes: 183 additions & 0 deletions src/actions/mediaLibrary.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
import { actions as notifActions } from 'redux-notifications';
import { currentBackend } from '../backends/backend';
import { createAssetProxy } from '../valueObjects/AssetProxy';
import { getAsset, selectIntegration } from '../reducers';
import { addAsset } from './media';
import { getIntegrationProvider } from '../integrations';

const { notifSend } = notifActions;

export const MEDIA_LIBRARY_OPEN = 'MEDIA_LIBRARY_OPEN';
export const MEDIA_LIBRARY_CLOSE = 'MEDIA_LIBRARY_CLOSE';
export const MEDIA_INSERT = 'MEDIA_INSERT';
export const MEDIA_LOAD_REQUEST = 'MEDIA_LOAD_REQUEST';
export const MEDIA_LOAD_SUCCESS = 'MEDIA_LOAD_SUCCESS';
export const MEDIA_LOAD_FAILURE = 'MEDIA_LOAD_FAILURE';
export const MEDIA_PERSIST_REQUEST = 'MEDIA_PERSIST_REQUEST';
export const MEDIA_PERSIST_SUCCESS = 'MEDIA_PERSIST_SUCCESS';
export const MEDIA_PERSIST_FAILURE = 'MEDIA_PERSIST_FAILURE';
export const MEDIA_DELETE_REQUEST = 'MEDIA_DELETE_REQUEST';
export const MEDIA_DELETE_SUCCESS = 'MEDIA_DELETE_SUCCESS';
export const MEDIA_DELETE_FAILURE = 'MEDIA_DELETE_FAILURE';

export function openMediaLibrary(payload) {
return { type: MEDIA_LIBRARY_OPEN, payload };
}

export function closeMediaLibrary() {
return { type: MEDIA_LIBRARY_CLOSE };
}

export function insertMedia(mediaPath) {
return { type: MEDIA_INSERT, payload: { mediaPath } };
}

export function loadMedia(opts = {}) {
const { delay = 0, query = '', page = 1 } = opts;
return async (dispatch, getState) => {
const state = getState();
const backend = currentBackend(state.config);
const integration = selectIntegration(state, null, 'assetStore');
if (integration) {
const provider = getIntegrationProvider(state.integrations, backend.getToken, integration);
dispatch(mediaLoading(page));
try {
const files = await provider.retrieve(query, page);
const mediaLoadedOpts = {
page,
canPaginate: true,
dynamicSearch: true,
dynamicSearchQuery: query
};
return dispatch(mediaLoaded(files, mediaLoadedOpts));
}
catch(error) {
return dispatch(mediaLoadFailed());
}
}
dispatch(mediaLoading(page));
return new Promise(resolve => {
setTimeout(() => resolve(
backend.getMedia()
.then(files => dispatch(mediaLoaded(files)))
.catch((error) => dispatch(error.status === 404 ? mediaLoaded() : mediaLoadFailed()))
));
}, delay);
};
}

export function persistMedia(file, privateUpload) {
return async (dispatch, getState) => {
const state = getState();
const backend = currentBackend(state.config);
const integration = selectIntegration(state, null, 'assetStore');

dispatch(mediaPersisting());

try {
const assetProxy = await createAssetProxy(file.name.toLowerCase(), file, false, privateUpload);
dispatch(addAsset(assetProxy));
if (!integration) {
const asset = await backend.persistMedia(assetProxy);
return dispatch(mediaPersisted(asset));
}
return dispatch(mediaPersisted(assetProxy.asset));
}
catch(error) {
console.error(error);
dispatch(notifSend({
message: `Failed to persist media: ${ error }`,
kind: 'danger',
dismissAfter: 8000,
}));
return dispatch(mediaPersistFailed());
}
};
}

export function deleteMedia(file) {
return (dispatch, getState) => {
const state = getState();
const backend = currentBackend(state.config);
const integration = selectIntegration(state, null, 'assetStore');
if (integration) {
const provider = getIntegrationProvider(state.integrations, backend.getToken, integration);
dispatch(mediaDeleting());
return provider.delete(file.id)
.then(() => {
return dispatch(mediaDeleted(file));
})
.catch(error => {
console.error(error);
dispatch(notifSend({
message: `Failed to delete media: ${ error.message }`,
kind: 'danger',
dismissAfter: 8000,
}));
return dispatch(mediaDeleteFailed());
});
}
dispatch(mediaDeleting());
return backend.deleteMedia(file.path)
.then(() => {
return dispatch(mediaDeleted(file));
})
.catch(error => {
console.error(error);
dispatch(notifSend({
message: `Failed to delete media: ${ error.message }`,
kind: 'danger',
dismissAfter: 8000,
}));
return dispatch(mediaDeleteFailed());
});
};
}

export function mediaLoading(page) {
return {
type: MEDIA_LOAD_REQUEST,
payload: { page },
}
}

export function mediaLoaded(files, opts = {}) {
return {
type: MEDIA_LOAD_SUCCESS,
payload: { files, ...opts }
};
}

export function mediaLoadFailed(error) {
return { type: MEDIA_LOAD_FAILURE };
}

export function mediaPersisting() {
return { type: MEDIA_PERSIST_REQUEST };
}

export function mediaPersisted(asset) {
return {
type: MEDIA_PERSIST_SUCCESS,
payload: { file: asset },
};
}

export function mediaPersistFailed(error) {
return { type: MEDIA_PERSIST_FAILURE };
}

export function mediaDeleting() {
return { type: MEDIA_DELETE_REQUEST };
}

export function mediaDeleted(file) {
return {
type: MEDIA_DELETE_SUCCESS,
payload: { file },
};
}

export function mediaDeleteFailed(error) {
return { type: MEDIA_DELETE_FAILURE };
}
16 changes: 16 additions & 0 deletions src/backends/backend.js
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,10 @@ class Backend {
);
}

getMedia() {
return this.implementation.getMedia();
}

entryWithFormat(collectionOrEntity) {
return (entry) => {
const format = resolveFormat(collectionOrEntity, entry);
Expand Down Expand Up @@ -244,6 +248,13 @@ class Backend {
});
}

persistMedia(file) {
const options = {
commitMessage: `Upload ${file.path}`,
};
return this.implementation.persistMedia(file, options);
}

deleteEntry(config, collection, slug) {
const path = selectEntryPath(collection, slug);

Expand All @@ -255,6 +266,11 @@ class Backend {
return this.implementation.deleteFile(path, commitMessage);
}

deleteMedia(path) {
const commitMessage = `Delete ${path}`;
return this.implementation.deleteFile(path, commitMessage);
}

persistUnpublishedEntry(config, collection, entryDraft, MediaFiles) {
return this.persistEntry(config, collection, entryDraft, MediaFiles, { unpublished: true });
}
Expand Down
6 changes: 6 additions & 0 deletions src/backends/git-gateway/API.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import GithubAPI from "../github/API";
import { APIError } from "../../valueObjects/errors";

export default class API extends GithubAPI {
constructor(config) {
Expand Down Expand Up @@ -44,15 +45,20 @@ export default class API extends GithubAPI {

request(path, options = {}) {
const url = this.urlFor(path, options);
let responseStatus;
return this.getRequestHeaders(options.headers || {})
.then(headers => fetch(url, { ...options, headers }))
.then((response) => {
responseStatus = response.status;
const contentType = response.headers.get("Content-Type");
if (contentType && contentType.match(/json/)) {
return this.parseJsonResponse(response);
}

return response.text();
})
.catch(error => {
throw new APIError(error.message, responseStatus, 'Git Gateway');
});
}

Expand Down
Loading