Commit c2bc3ec5 authored by charlie-ablett's avatar charlie-ablett
Browse files

Favourites

parent 6ec0e703
......@@ -286,6 +286,126 @@ export function collapseStepFileList(stepId, chainId, recipeId) {
};
}
///////////////////////////////////////////////////////////////
////////////////// un/favourite recipe ////////////////////////
///////////////////////////////////////////////////////////////
export function setRecipeFavourite(favourite, recipeId) {
return {
type: actions.SET_RECIPE_FAVOURITE,
recipeId: recipeId,
favourite: favourite
};
}
export function favouriteRecipe(recipeId, signedIn, authToken, tokenType, client, expiry, uid) {
return function(dispatch) {
let theResponse;
dispatch(
{
type: actions.FAVOURITE_RECIPE_REQUEST,
recipeId: recipeId
}
);
fetch(`${settings.apiBaseUrl}/api/recipes/${recipeId}/favourite`, {
method: 'GET',
headers: {
'Accept': settings.apiVersionHeader,
'Content-Type': 'application/json',
'Access-Token': authToken,
'Client': client,
'Token-Type': tokenType,
'Expiry': expiry,
'uid': uid
}
})
.then(response => {
theResponse = response;
return response.json();
})
.then(json => {
checkStatus(theResponse, dispatch, signedIn);
if(_.isUndefined(json.favourite)) {
let error = new Error(theResponse.statusText);
error.response = json.errors;
throw error;
}
else {
dispatch(setRecipeFavourite(json.favourite, recipeId));
}
})
.catch(error => {
dispatch(rejectFavouriteRecipe(error));
});
return null;
};
}
export function rejectFavouriteRecipe(error) {
return {
type: actions.FAVOURITE_RECIPE_FAILURE,
error: error.toString()
};
}
export function unfavouriteRecipe(recipeId, signedIn, authToken, tokenType, client, expiry, uid) {
if(signedIn === false || signedIn === null || signedIn === undefined) {
return { type: actions.UNFAVOURITE_RECIPE_REQUEST };
}
return function(dispatch) {
let theResponse;
dispatch(
{
type: actions.UNFAVOURITE_RECIPE_REQUEST,
recipeId: recipeId
}
);
fetch(`${settings.apiBaseUrl}/api/recipes/${recipeId}/unfavourite`, {
method: 'GET',
headers: {
'Accept': settings.apiVersionHeader,
'Content-Type': 'application/json',
'Access-Token': authToken,
'Client': client,
'Token-Type': tokenType,
'Expiry': expiry,
'uid': uid
}
})
.then(response => {
theResponse = response;
return response.json();
})
.then(json => {
checkStatus(theResponse, dispatch, signedIn);
if(_.isUndefined(json.favourite)) {
let error = new Error(theResponse.statusText);
error.response = json.errors;
throw error;
}
else {
console.log("unf recipeId", recipeId)
dispatch(setRecipeFavourite(json.favourite, recipeId));
}
})
.catch(error => {
dispatch(rejectUnfavouriteRecipe(error));
});
return null;
};
}
export function rejectUnfavouriteRecipe(error) {
return {
type: actions.UNFAVOURITE_RECIPE_FAILURE
};
}
///////////////////////////////////////////////////////////////
///////////////// new recipe - step class list ////////////////
///////////////////////////////////////////////////////////////
......
......@@ -86,7 +86,7 @@ export class ExecutionFileList extends Component {
}
renderFileList(isOutput, fileList, chainId, collapsed) {
if(_.isEmpty(fileList)) {
if(_.isEmpty(fileList) || _.isNull(fileList)) {
return(<span/>);
}
return(
......
import React, { Component, PropTypes } from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import { favouriteRecipe, unfavouriteRecipe } from '../actions/recipeActions.js';
import * as actions from '../actions/recipeActions';
export class FavouriteWidget extends Component {
favouriteRecipe = (e, recipeId) => {
e.preventDefault();
const { dispatch, appState } = this.props;
const { authToken, tokenType, client, expiry, uid } = appState.session;
const signedIn = (authToken != null);
dispatch(actions.favouriteRecipe(recipeId, signedIn, authToken, tokenType, client, expiry, uid));
}
unfavouriteRecipe = (e, recipeId) => {
e.preventDefault();
const { dispatch, appState } = this.props;
const { authToken, tokenType, client, expiry, uid } = appState.session;
const signedIn = (authToken != null);
dispatch(actions.unfavouriteRecipe(recipeId, signedIn, authToken, tokenType, client, expiry, uid));
}
render() {
let { recipe } = this.props;
let starClass = recipe.favourite == true ? "fa-star" : "fa-star-o";
if(recipe.pendingFavourite == true) {
return(
<span className={`fa ${starClass} silver`} />
);
}
if(recipe.favourite == true) {
return(
<a href={`#id=${recipe.id}`} onClick={e => this.unfavouriteRecipe(e, recipe.id)}><span className={`favourite fa ${starClass}`} /></a>
);
}
else {
return(
<a href={`#id=${recipe.id}`} onClick={e => this.favouriteRecipe(e, recipe.id)}><span className={`favourite fa ${starClass}`} /></a>
);
}
}
}
function mapStateToProps(state) {
return { appState: state.appState };
}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators(actions, dispatch),
dispatch: dispatch
};
}
FavouriteWidget.propTypes = {
recipe: PropTypes.object.isRequired,
appState: PropTypes.object,
dispatch: PropTypes.func
};
export default connect(
mapStateToProps,
mapDispatchToProps
)(FavouriteWidget);
......@@ -4,6 +4,8 @@ import { bindActionCreators } from 'redux';
import { Link } from 'react-router';
import * as actions from '../actions/recipeActions';
import FavouriteWidget from './FavouriteWidget.js';
import _ from 'lodash';
export class RecipeListItem extends Component {
......@@ -31,7 +33,7 @@ export class RecipeListItem extends Component {
<Link to={`/recipes/${recipe.id}`}>
<li className={this.itemClass(recipe.public)}>
<div className="recipe-item__header recipe-item__unselected">
{recipe.name}
{recipe.name} <FavouriteWidget recipe={recipe} />
</div>
<div className="recipe-item__body">
{this.renderDescription(recipe.description)}
......
......@@ -56,6 +56,14 @@ export const DELETE_RECIPE_REQUEST = "DELETE_RECIPE_REQUEST";
export const DELETE_RECIPE_SUCCESS = "DELETE_RECIPE_SUCCESS";
export const DELETE_RECIPE_FAILURE = "DELETE_RECIPE_FAILURE";
export const SET_RECIPE_FAVOURITE = "SET_RECIPE_FAVOURITE";
export const FAVOURITE_RECIPE_REQUEST = "FAVOURITE_RECIPE_REQUEST";
export const FAVOURITE_RECIPE_FAILURE = "FAVOURITE_RECIPE_FAILURE";
export const UNFAVOURITE_RECIPE_REQUEST = "UNFAVOURITE_RECIPE_REQUEST";
export const UNFAVOURITE_RECIPE_FAILURE = "UNFAVOURITE_RECIPE_FAILURE";
//////////////////// downloading //////////////////////
export const DOWNLOAD_FILE_REQUEST = "DOWNLOAD_FILE_REQUEST";
......
......@@ -17,6 +17,7 @@ import AlertList from '../../components/AlertList';
import ExecutionFileForm from '../../components/ExecutionFileForm';
import RecipeStepDetail from '../../components/RecipeStepDetail';
import ExecutionList from '../../components/ExecutionList';
import FavouriteWidget from '../../components/FavouriteWidget';
class RecipeShowPage extends Component {
static propTypes = {
......@@ -83,6 +84,7 @@ class RecipeShowPage extends Component {
let recipe = _.find(appState.recipes, _.matchesProperty('id', recipeId));
if(!_.isNull(recipe) && !_.isUndefined(recipe)) {
dispatch(actions.selectRecipe(recipe.id));
return(recipe);
}
}
......@@ -180,7 +182,7 @@ class RecipeShowPage extends Component {
alerts={appState.alerts} />
<div className="recipe-detail-view">
<h1>Recipe: {recipe.name}</h1>
<h1>Recipe: {recipe.name} <FavouriteWidget recipe={recipe} /></h1>
<h4>{recipe.description}</h4>
{this.renderEditLink(recipe)}
......
......@@ -353,6 +353,61 @@ export default function appState(state = initialState, action) {
return newState;
}
////////////////////// recipe favourites //////////////////////////////////
case actionTypes.SET_RECIPE_FAVOURITE:
{
let newState = objectAssign({}, state);
let recipe = _.find(newState.recipes, _.matchesProperty('id', action.recipeId));
recipe.favourite = action.favourite;
recipe.pendingFavourite = false;
return newState;
}
case actionTypes.FAVOURITE_RECIPE_REQUEST:
{
let newState = objectAssign({}, state);
newState.alerts = [];
let recipe = _.find(newState.recipes, _.matchesProperty('id', action.recipeId));
recipe.pendingFavourite = true;
return newState;
}
case actionTypes.FAVOURITE_RECIPE_FAILURE:
{
let newState = objectAssign({}, state);
let recipe = _.find(newState.recipes, _.matchesProperty('id', action.recipeId));
recipe.pendingFavourite = false;
return newState;
}
case actionTypes.UNFAVOURITE_RECIPE_REQUEST:
{
let newState = objectAssign({}, state);
newState.alerts = [];
let recipe = _.find(newState.recipes, _.matchesProperty('id', action.recipeId));
recipe.pendingFavourite = true;
return newState;
}
case actionTypes.UNFAVOURITE_RECIPE_FAILURE:
{
let newState = objectAssign({}, state);
let recipe = _.find(newState.recipes, _.matchesProperty('id', action.recipeId));
recipe.pendingFavourite = false;
return newState;
}
////////////////////// create recipe //////////////////////////////////
case actionTypes.CREATE_RECIPE_REQUEST:
......
......@@ -21,6 +21,7 @@ $fa-font-path: "~font-awesome/fonts"
@import "molecules/account-item.sass"
@import "molecules/alerts.sass"
@import "molecules/breadcrumbs.sass"
@import "molecules/favourite.sass"
@import "molecules/gem-item.sass"
@import "molecules/env-header.sass"
@import "molecules/execution-result.sass"
......
.favourite
color: gold
.favourite-loading
color: silver
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment