From 14ce6df7af5b088746beae5c489ad829dbaf2fba Mon Sep 17 00:00:00 2001 From: John Shaver Date: Thu, 20 Jul 2017 15:32:17 -0700 Subject: [PATCH] Subscriptions are now working and updating some parts of the view. --- src/App.js | 3 +- src/components/Achievement.js | 29 ++++- src/components/AchievementList.js | 69 ++++++++--- src/components/Adventure.js | 31 +++-- src/components/AdventureList.js | 89 +++++++++----- src/components/ScoutOverview.js | 53 +++++--- src/containers/CompleteAchievement.js | 13 ++ src/containers/GetAchievementsForAdventure.js | 114 +++++++++++++++--- src/containers/GetAllAdventures.js | 94 +++++++++++++-- src/containers/GetScoutData.js | 62 +++++++--- 10 files changed, 430 insertions(+), 127 deletions(-) create mode 100644 src/containers/CompleteAchievement.js diff --git a/src/App.js b/src/App.js index 771bea3..80a5ba0 100644 --- a/src/App.js +++ b/src/App.js @@ -3,10 +3,11 @@ import { getQueryParamByName } from './utils.js'; import ScoutOverviewCont from './containers/GetScoutData.js'; import ScoutOverview from './components/ScoutOverview.js'; +const ScoutView = ScoutOverviewCont(ScoutOverview); + class App extends Component { render() { const scoutID = getQueryParamByName("id", this.props.slug); - const ScoutView = ScoutOverviewCont(ScoutOverview); return (
diff --git a/src/components/Achievement.js b/src/components/Achievement.js index 7aa9e8a..a3f9c58 100644 --- a/src/components/Achievement.js +++ b/src/components/Achievement.js @@ -1,5 +1,30 @@ import React from 'react'; +import { gql, graphql } from 'react-apollo'; -export default({id, number, description, additionalText}) => { - return
{number}
; +export default({ + id, + number, + completed, + letter, + description, + additionalText, + scoutID, + mutate +}) => { + if(completed) { + return
+ {number}{letter} +
; + } else { + + const variables = { + scoutID, + id, + date: (new Date(Date.now())).toISOString() + }; + + return ; + } }; diff --git a/src/components/AchievementList.js b/src/components/AchievementList.js index 56080d1..76fd7b6 100644 --- a/src/components/AchievementList.js +++ b/src/components/AchievementList.js @@ -1,22 +1,55 @@ -import React from 'react' +import React from 'react'; +import Achievement from './Achievement.js'; +import CompleteAchievement from '../containers/CompleteAchievement.js'; -import Achievement from "./Achievement.js"; +const AchievementButton = CompleteAchievement(Achievement); -export default ({achievements, id, completed}) => { - console.log("completed: ", completed); - if(!achievements) { - return
Loading...
+export default class AchievementList extends React.Component { + + componentWillMount() { + this.unsubscribe = this.props.subscribeToAchievements({ + scoutID: this.props.scoutID, + adventureID: this.props.adventureID + }); } - - const list = [...achievements].sort((a, b) => { - if (a.number === b.number) - return a.letter > b.letter; - return a.number > b.number; - }).map(achievement => ( - - )); - - return
- {list} -
+ + render() { + const { + allAchievements, + completedAchievements, + scoutID + } = this.props; + + const completedMap = completedAchievements.reduce((map, ach) => { + map[ach.achievement.id] = ach; + return map + }, {}); + + if(!allAchievements || !allAchievements.length) { + return
Loading...
+ } + + const list = [...allAchievements].sort((a, b) => { + if (a.number === b.number) + return a.letter > b.letter; + return a.number > b.number; + }).map(achievement => { + const props = { + key: achievement.id, + completed: completedMap[achievement.id], + scoutID, + ...achievement + }; + return + }); + + return
+ {list} +
+ } + + componentWillUnmount() { + this.unsubscribe(); + } + } diff --git a/src/components/Adventure.js b/src/components/Adventure.js index d2f6727..da4b043 100644 --- a/src/components/Adventure.js +++ b/src/components/Adventure.js @@ -1,15 +1,28 @@ import React from 'react' -import GetAchievements from "../containers/GetAchievementsForAdventure.js"; -import AchievementList from "./AchievementList.js"; +import GetAchievements from '../containers/GetAchievementsForAdventure.js'; +import AchievementList from './AchievementList.js'; +import {Card, CardHeader} from 'material-ui/Card'; -export default ({id, name, number, isRequired, achievements, - completedAdventures, completedAchievements}) => { +const AchievementListComp = GetAchievements(AchievementList); - const AchievementListComp = GetAchievements(AchievementList); - return
-
{name}
- -
+export default ({ + id, + name, + number, + isRequired, + achievements, + completed, + scoutID +}) => { + + const className = `adventure ${completed ? 'completed-adventure' :''}`; + return + + + } diff --git a/src/components/AdventureList.js b/src/components/AdventureList.js index 76439f8..180f9b9 100644 --- a/src/components/AdventureList.js +++ b/src/components/AdventureList.js @@ -1,39 +1,74 @@ -import React from "react" +import React from 'react'; import { splitFilter } from '../utils.js'; import Adventure from './Adventure.js'; -export default ({ - allAdventures, - completedAdventures -}) => { - if(!allAdventures) { - return
Loading...
+export default class AdventureList extends React.Component { + + componentWillMount() { + this.unsubscribe = this.props.subscribeToAdventures({ + scoutID: this.props.scoutID + }); } - const sortedAdventures = [...allAdventures].sort((a,b) => (a.number > b.number)); + render() { + const { + scoutID, + allAdventures, + completedAdventures + } = this.props; + console.log("completed!: ", completedAdventures); - const [ - requiredAdventures, - optionalAdventures - ] = splitFilter(sortedAdventures, a => a.required); + if(!allAdventures || !allAdventures.length) { + return
Loading...
+ } - const requiredAdventureList = requiredAdventures.map(adventure => { - return ; - }); + const completedMap = completedAdventures.reduce((map, adv) => { + map[adv.adventure.id] = adv; + return map; + }, {}); + + const sortedAdventures = [...allAdventures].sort((a,b) => (a.number > b.number)); - const optionalAdventureList = optionalAdventures.map(adventure => { - return ; - }); + const [ + requiredAdventures, + optionalAdventures + ] = splitFilter(sortedAdventures, a => a.required); - return
-
- {requiredAdventureList} -
-
- { - //optionalAdventureList + const requiredAdventureList = requiredAdventures.map(adventure => { + const props = { + key: adventure.id, + scoutID: scoutID, + completed: completedMap[adventure.id], + ...adventure } -
-
; + return ; + }); + + const optionalAdventureList = optionalAdventures.map(adventure => { + console.log("ADVENTURE!: ", adventure); + const props = { + key: adventure.id, + scoutID: scoutID, + completed: completedMap[adventure.id], + ...adventure + } + return ; + }); + + return
+
+ {requiredAdventureList} +
+
+ { + //optionalAdventureList + } +
+
; + } + + componentWillUnmount() { + this.unsubscribe(); + } } diff --git a/src/components/ScoutOverview.js b/src/components/ScoutOverview.js index b969bd0..dc6cfe2 100644 --- a/src/components/ScoutOverview.js +++ b/src/components/ScoutOverview.js @@ -3,25 +3,38 @@ import AllAdventures from '../containers/GetAllAdventures.js' import AdventureList from './AdventureList.js'; import AppBar from 'material-ui/AppBar'; -export default ({ - scoutID, - displayName, - advancementDeadline, - completedAdventures, - completedAchievements -}) => { - const appBarProps = { - title: displayName, - showMenuIconButton: false +const AdventureListComp = AllAdventures(AdventureList); + +export default class ScoutOverview extends React.Component { + + componentWillMount() { + this.unsubscribe = this.props.subscribeToData({ + scoutID: this.props.scoutID + }); } - const AdventureListComp = AllAdventures(AdventureList); - console.log(displayName) - return ( -
- - -
- ); + + render() { + const { + scoutID, + displayName, + advancementDeadline, + } = this.props; + const appBarProps = { + title: displayName, + showMenuIconButton: false + } + return ( +
+ + +
+ ); + } + + componentWillUnmount() { + this.unsubscribe(); + } + } diff --git a/src/containers/CompleteAchievement.js b/src/containers/CompleteAchievement.js new file mode 100644 index 0000000..a289431 --- /dev/null +++ b/src/containers/CompleteAchievement.js @@ -0,0 +1,13 @@ +import { gql, graphql } from 'react-apollo'; + +export default graphql(gql` + mutation MarkAchievementComplete($id:ID!, $scoutID:ID!, $date:DateTime!){ + createCompletedAchievement( + completedAt: $date + achievementId: $id + scoutId: $scoutID + ) { + id + } + } +`) diff --git a/src/containers/GetAchievementsForAdventure.js b/src/containers/GetAchievementsForAdventure.js index 2df18ea..40423ce 100644 --- a/src/containers/GetAchievementsForAdventure.js +++ b/src/containers/GetAchievementsForAdventure.js @@ -1,32 +1,106 @@ import { gql, graphql } from 'react-apollo'; export default graphql(gql` - query GetAchievementsForAdventure($adventureID:ID!){ - allAchievements(filter: { - adventure: { - id: $adventureID + query GetAchievementsForAdventure($adventureID:ID! $scoutID:ID!){ + all: allAchievements(filter: { + adventure: {id: $adventureID} + }) { + id + number + letter + description + additionalText + } + completed: allCompletedAchievements(filter:{ + scout: {id: $scoutID} + achievement: { + adventure: {id: $adventureID} } }) { - id, - number, - letter, - description, - additionalText + id + completedAt + achievement { + id + } } } `,{ - options: ({adventureID}) => { - return { - variables: { - adventureID - } - }; - }, + options: ({adventureID, scoutID}) => ({variables: { adventureID, scoutID }}), props: ({ownProps, data}) => { - return data.allAchievements ? { - achievements: data.allAchievements, - ...ownProps - } : {...ownProps} + const { + subscribeToMore, + all, + completed + } = data; + const subscribeToAchievements = params => { + return subscribeToMore({ + document: gql` + subscription subscribeToCompAch($scoutID:ID!, $adventureID:ID!){ + CompletedAchievement(filter: { + mutation_in: [UPDATED, CREATED, DELETED] + node: { + scout: { + id: $scoutID + } + achievement: { + adventure: { + id: $adventureID + } + } + } + }){ + mutation + node { + id + completedAt + achievement { + id + } + } + previousValues { + id + } + } + } + `, + variables: { + scoutID: params.scoutID, + adventureID: params.adventureID + }, + updateQuery: (prev, response) => { + const subscriptionData = response.subscriptionData.data.CompletedAchievement; + console.log("UPDATE! :", response); + + let updatedAchievements; + const oldAchievements = prev.completed + + if(subscriptionData.mutation === 'DELETED') { + updatedAchievements = oldAchievements.filter(ach => ( + ach.id !== subscriptionData.previousValues.id + )); + } else { + const updatedID = subscriptionData.node.id; + updatedAchievements = [...oldAchievements]; + let index = oldAchievements.findIndex(adventure => ( + adventure.id === updatedID + )); + if(index === -1) index = updatedAchievements.length; + updatedAchievements[index] = subscriptionData.node; + } + + return Object.assign({}, prev, { + completed: updatedAchievements + }); + } + }); + }; + + return { + allAchievements: all || [], + completedAchievements: completed || [], + subscribeToAchievements, + ...ownProps + }; } }); diff --git a/src/containers/GetAllAdventures.js b/src/containers/GetAllAdventures.js index 504de45..2d0e6bf 100644 --- a/src/containers/GetAllAdventures.js +++ b/src/containers/GetAllAdventures.js @@ -1,22 +1,90 @@ import { gql, graphql } from 'react-apollo'; export default graphql(gql` - query GetAllAdventures{ - allAdventures { - name, - number, - required, + query GetAllAdventures($scoutID:ID!){ + allAdventures: allAdventures { + name + number + required id } + completedAdventures: allCompletedAdventures(filter:{ + scout: { id: $scoutID } + }) { + id + signer { id } + adventure { id } + } } `,{ - props: ({ownProps, data}) => ( - data.allAdventures ? { - allAdventures: data.allAdventures, + options: ({scoutID}) => ({variables: {scoutID}}), + props: ({ownProps, data}) => { + const { + subscribeToMore, + completedAdventures, + allAdventures + } = data; + const subscribeToAdventures = params => { + return subscribeToMore({ + document: gql` + subscription subscribeToCompletedAdventures($scoutID:ID!){ + CompletedAdventure(filter: { + mutation_in: [UPDATED, CREATED, DELETED] + node: { + scout: { + id: $scoutID + } + } + }){ + mutation + node { + id + signer { id } + adventure { id } + } + } + } + `, + variables: { + scoutID: params.scoutID + }, + updateQuery: (prev, {subscriptionData}) => { + console.log("SubscriptionData: ", subscriptionData); + if(!subscriptionData.data) { + return prev; + } + + let updatedAdventures; + const updatedID = subscriptionData.node.id; + const oldAdventures = prev.completedAdventures; + + if(subscriptionData.mutation === "DELETE") { + updatedAdventures = oldAdventures.filter(adventure => ( + adventure.id !== updatedID + )); + } else { + updatedAdventures = [...oldAdventures]; + let index = oldAdventures.findIndex(adventure => ( + adventure.id === updatedID + )); + if(index === -1) index = updatedAdventures.length; + updatedAdventures[index] = subscriptionData; + } + + return Object.assign({}, prev, { + completedAdventures: updatedAdventures + }); + + } + }); + + }; + + return { + allAdventures: allAdventures || [], + completedAdventures: completedAdventures || [], + subscribeToAdventures, ...ownProps - } : {...ownProps}) + }; + } }); - - - - diff --git a/src/containers/GetScoutData.js b/src/containers/GetScoutData.js index 8f71292..575a189 100644 --- a/src/containers/GetScoutData.js +++ b/src/containers/GetScoutData.js @@ -5,29 +5,57 @@ export default graphql(gql` Scout(id: $scoutID) { displayName advancementDeadline - completedAdventures { - id - name - } - completedAchievements { - id - number - letter - } } } `,{ options : ({scoutID}) => ({variables: {scoutID}}), - props: ({ ownProps, data }) => ( - data.Scout ? { + name: 'scoutData', + props: ({ ownProps, data, scoutData }) => { + const subscribeToData = params => { + return scoutData.subscribeToMore({ + document: gql` + subscription subscribeToScoutData($scoutID:ID!){ + Scout(filter: { + mutation_in: [UPDATED] + node: { + id: $scoutID + } + }) { + mutation + node { + displayName + advancementDeadline + } + } + } + `, + variables: { + scoutID: params.scoutID + }, + updateQuery: (prev, {subscriptionData}) => { + if(!subscriptionData.data) { + console.log('no data!'); + return prev; + } + console.log('data! : ', prev, ':', subscriptionData); + return Object.assign({}, prev, { + Scout: subscriptionData.data.Scout.node + }); + } + }); + }; + return scoutData.Scout ? { scoutID: ownProps.scoutID, - displayName: data.Scout.displayName, - advancementDeadline: data.Scout.advancementDeadline, - completedAdventures: data.Scout.completedAdventures, - completedAchievements: data.Scout.completedAchievements + displayName: scoutData.Scout.displayName, + advancementDeadline: scoutData.Scout.advancementDeadline, + subscribeToData, + ...ownProps } : { - scoutID: ownProps.scoutID + scoutID: ownProps.scoutID, + subscribeToData, + ...ownProps } - ) + } }); +