Skip to content

Commit

Permalink
Handle flagging deleted cards + matt's suggestions
Browse files Browse the repository at this point in the history
  • Loading branch information
Tyler-Brenneman committed Mar 28, 2017
1 parent f178f52 commit 98bc96c
Show file tree
Hide file tree
Showing 6 changed files with 77 additions and 82 deletions.
13 changes: 7 additions & 6 deletions Cue/app/actions/library.js
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ function rateDeck(uuid: string, user_rating: number): Action {
}
}

type Conflict = {
export type Conflict = {
localDeck: ?Deck,
serverDeck: ?Deck,
change: {},
Expand Down Expand Up @@ -117,7 +117,7 @@ async function syncDeck(change) : PromiseAction {
// check if any other changes need to be synced
for (let key in change) {
if((key === 'cards' && change.cards.length) || (change[key] != updatedDeck[key] && !key.match("^(?:cards|uuid|action)$"))) {
serverDeck = await LibraryApi.editDeck({
updatedDeck = await LibraryApi.editDeck({
...change,
uuid: updatedDeck.uuid,
parent_deck_version: updatedDeck.user_data_version,
Expand All @@ -130,15 +130,16 @@ async function syncDeck(change) : PromiseAction {
} else if (change.action === "delete") {
await LibraryApi.deleteDeck(change.uuid)
} else if (change.action === "flag") {
let response = await LibraryApi.flagCard(change).catch(e=>{
if (e.response && e.response.status === 404) {
//server deck deleted
let response = await LibraryApi.flagCard(change).catch(e => {
if (e.response && (e.response.status === 404 || e.response.status === 400)) {
// flagging private deleted deck returns 404
// flagging remote deleted deck returns 400
return {uuid: change.uuid, deleted: true}
} else {
throw e
}
})
change = {...change, user_data_version: response.user_data_version}
change = {...change, ...response}
} else if (change.action === "rate") {
await LibraryApi.rateDeck(change.uuid, change.user_rating)
}
Expand Down
18 changes: 9 additions & 9 deletions Cue/app/actions/types.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,25 +3,25 @@
import type {Deck, DeckMetadata} from '../api/types';

export type Action =
{ type: 'LOADED_LIBRARY', decks: Array<Deck> }
| { type: 'LOADED_USERNAME', name: string }
{ type: 'LOADED_LIBRARY', decks: Array<Deck> }
| { type: 'LOADED_USERNAME', name: string }
| { type: 'LOGGED_IN', data: { userId: string; accessToken: string; } }
| { type: 'LOGGED_OUT' }
| { type: 'SWITCH_TAB', tab: 'library' | 'discover' | 'search' }
| { type: 'DECK_CREATED', deck: {} }
| { type: 'DECK_DELETED', uuid: string }
| { type: 'DECK_EDITED', change: {} }
| { type: 'DECK_EDITED', change: {} }
| { type: 'DECK_SYNCED', updatedDeck: Deck, change: {} }
| { type: 'DECK_CONFLICT_RESOLVED', updatedDeck: Deck, change: {} }
| { type: 'DECK_CONFLICT_RESOLVED', updatedDeck: Deck, change: {} }
| { type: 'SHARE_CODE_GENERATED', uuid: string, code: string }
| { type: 'SEARCHED_DECKS', searchResults: Array<DeckMetadata>, searchString: string }
| { type: 'DISCOVERED_DECKS', newDecks: Array<DeckMetadata>, topDecks: Array<DeckMetadata> }
| { type: 'CARD_FLAGGED', change: {uuid: string, cards:[{uuid:string, needs_review: Boolean, action: 'edit'}]} }
| { type: 'DECK_ALREADY_IN_LIBRARY'}
| { type: 'DECK_ADDED_TO_LIBRARY', addedDeck: Deck}
| { type: 'CLEAR_INACCESSIBLE_DECKS'}
| { type: 'DECK_RATED', change: {uuid: string, userRating: number}}
;
| { type: 'DECK_ALREADY_IN_LIBRARY'}
| { type: 'DECK_ADDED_TO_LIBRARY', addedDeck: Deck}
| { type: 'CLEAR_INACCESSIBLE_DECKS'}
| { type: 'DECK_RATED', change: {uuid: string, userRating: number}}
;

export type Dispatch = (action: Action | ThunkAction | PromiseAction | Array<Action>) => any;
export type GetState = () => Object;
Expand Down
14 changes: 9 additions & 5 deletions Cue/app/common/SelectableTextTableRow.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ const styles = {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
paddingVertical: Platform.OS === 'android' ? 16 : undefined,
paddingVertical: Platform.OS === 'android' ? 8 : undefined,
},
text: {
color: CueColors.primaryText,
Expand All @@ -30,9 +30,11 @@ const styles = {
fontSize: Platform.OS === 'android' ? 13 : 14,
},
icon: {
flex: 0,
tintColor: CueColors.primaryTint,
},
iconDisabled: {
flex: 0,
tintColor: CueColors.mediumGrey,
}
}
Expand All @@ -47,14 +49,16 @@ export default class SelectableTextTableRow extends React.Component {
}

_renderText = () => {
let subText
if (this.props.subText) {
subText = <Text style={styles.textSecondary}>{this.props.subText}</Text>
}
return (
<View>
<View style={{flex: 1}}>
<Text style={this.props.disabled ? styles.textDisabled : styles.text}>
{this.props.text}
</Text>
<Text style={styles.textSecondary}>
{this.props.subText}
</Text>
{ subText }
</View>
)
}
Expand Down
104 changes: 45 additions & 59 deletions Cue/app/common/SyncConflict.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
// @flow

import React from 'react'
import { View, Text, ListView, Dimensions, Navigator, Platform, Alert} from 'react-native'
import { View, Text, ListView, Dimensions, Navigator, Platform, Alert, ActivityIndicator } from 'react-native'
import type { Deck } from '../api/types';
import type { Conflict } from '../actions/library'
import { connect } from 'react-redux'
import CueIcons from './CueIcons'
import CueColors from './CueColors'
Expand All @@ -12,13 +13,6 @@ import SelectableTextTableRow from './SelectableTextTableRow'
import LibraryApi from '../api/Library'
import TableHeader from './TableHeader'

type Conflict = {
localDeck: ?Deck,
serverDeck: ?Deck,
change: {},
useServerDeck: ?boolean,
}

type Props = {
failedSyncs: Array<{}>,
navigator: Navigator,
Expand Down Expand Up @@ -48,7 +42,8 @@ class SyncConflict extends React.Component {

state: {
dataSource: ListView.dataSource,
conflicts: Array<Conflict>
conflicts: Array<Conflict>,
loading: boolean,
}

constructor(props: Props) {
Expand All @@ -60,19 +55,18 @@ class SyncConflict extends React.Component {
this.state = {
dataSource: ds.cloneWithRows(conflicts),
conflicts,
loading: true,
}
}

_internetAlert = () => {
_internetAlert = (error) => {
Alert.alert (
Platform.OS === "android" ? 'Failed to resolve conflicts' : 'Failed to Resolve Conflicts',
'Check your Internet connection and try again.')
error.recoveryMessage
)
}

componentDidMount() {
let ds = new ListView.DataSource({
rowHasChanged: (r1, r2) => r1 != r2
})
let conflicts = [];
let promises = []
this.props.failedSyncs.forEach(change => {
Expand All @@ -91,9 +85,8 @@ class SyncConflict extends React.Component {
localDeck: this.props.localDecks.find(deck => deck.uuid==change.uuid),
useServerDeck: undefined
}
} else {
throw e
}
throw e
}))
})
Promise.all(promises).then(conflicts =>{
Expand All @@ -103,9 +96,10 @@ class SyncConflict extends React.Component {
this.setState({
dataSource: ds.cloneWithRows(conflicts),
conflicts,
loading: false,
})
}).catch(e => {
this._internetAlert()
this._internetAlert(e)
this.props.navigator.pop()
})
}
Expand All @@ -130,11 +124,8 @@ class SyncConflict extends React.Component {
promises.push(this.props.resolveConflict(conflict))
})
Promise.all(promises)
.catch(e => this._internetAlert(e))
.then(this.props.navigator.pop())
.catch(e => {
this._internetAlert()
this.props.navigator.pop()
})
}
}]
}
Expand All @@ -158,46 +149,39 @@ class SyncConflict extends React.Component {

_timeSince(date: Date) {
let seconds = Math.floor((new Date() - date) / 1000);
let interval = Math.floor(seconds / 31536000);

if (interval > 1) {
return interval + " years";
}
interval = Math.floor(seconds / 2592000);
if (interval > 1) {
return interval + " months";
}
interval = Math.floor(seconds / 86400);
if (interval > 1) {
return interval + " days";
let interval = Math.floor(seconds / 86400);
if (interval >= 1) {
return interval + ' day' + (interval > 1 ? 's' : '') + ' ago'
}
interval = Math.floor(seconds / 3600);
if (interval > 1) {
return interval + " hours";
if (interval >= 1) {
return interval + ' hour' + (interval > 1 ? 's' : '') + ' ago'
}
interval = Math.floor(seconds / 60);
if (interval > 1) {
return interval + " minutes";
if (interval >= 1) {
return interval + ' minute' + (interval > 1 ? 's' : '') + ' ago'
}
return Math.floor(seconds) + " seconds";
return "just now"
}

_renderRow = (conflict) => {
_renderRow = (conflict: Conflict) => {
let serverText
if (conflict.serverDeck) {
serverText = conflict.serverDeck.last_update_device
? 'Keep changes from "' + conflict.serverDeck.last_update_device + '"'
? 'Keep changes from ' + conflict.serverDeck.last_update_device + ''
: 'Keep server copy'
} else {
serverText = 'Keep deleted server change'
serverText = 'Delete this deck'
}
let serverSubText = conflict.serverDeck && conflict.serverDeck.last_update
? 'Modified ' + this._timeSince(new Date(conflict.serverDeck.last_update)) + ' ago'
: undefined
? 'Modified ' + this._timeSince(new Date(conflict.serverDeck.last_update))
: 'Deleted on another device'
let localText = 'Keep changes from this device'
let localSubText = conflict.localDeck && conflict.localDeck.last_update
? 'Modified ' + this._timeSince(new Date(conflict.localDeck.last_update)) + ' ago'
? 'Modified ' + this._timeSince(new Date(conflict.localDeck.last_update))
: undefined

return (
<View>
<TableHeader
Expand All @@ -211,7 +195,6 @@ class SyncConflict extends React.Component {
<SelectableTextTableRow
text={localText}
subText={localSubText}
key={conflict.useServerDeck}
selected={conflict.useServerDeck == undefined ? undefined : !conflict.useServerDeck}
onPress={() => { this._setSelection(conflict.change.uuid, false) }}
/>
Expand All @@ -230,31 +213,34 @@ class SyncConflict extends React.Component {
}

let options = [
{
label: 'Use server copy',
value: true
},
{
label: 'Use local copy',
value: false
}
{ label: 'Use server copy', value: true },
{ label: 'Use local copy', value: false }
]
let headerText = 'Changes were made to the following deck'
+ (this.state.conflicts.length > 1 ? 's' : '')
+ ' at the same time on multiple devices.'
+ ' Choose which version of each deck to keep.'
+ ' at the same time on multiple devices. Choose which version'
+ (this.state.conflicts.length > 1 ? ' of each deck' : '') + ' to keep.'

let content
if (this.state.loading) {
content = <ActivityIndicator/>
} else {
content =
<View>
<Text style={styles.headerText}>{headerText}</Text>
<ListView
dataSource={this.state.dataSource}
renderRow={this._renderRow}/>
</View>
}

return (
<View style={styles.container}>
<CueHeader
title={title}
leftItem={this._getLeftItem()}
rightItems={this._getRightItems()} />
<Text style={styles.headerText}>{headerText}</Text>
<ListView
key={this.state.conflicts}
dataSource={this.state.dataSource}
renderRow={this._renderRow}/>
{ content }
</View>
)
}
Expand Down
7 changes: 5 additions & 2 deletions Cue/app/reducers/library.js
Original file line number Diff line number Diff line change
Expand Up @@ -164,8 +164,11 @@ function library(state: State = initialState, action: Action): State {
if (decks[deckIndex]) {
if (change.user_data_version) {
decks[deckIndex] = {...decks[deckIndex], user_data_version: change.user_data_version}
} else if (updatedDeck.deleted) {
decks.splice(deckIndex,1)
} else if (change.deleted) {
if (decks[deckIndex].accession !== "private") {
inaccessibleDecks.push(decks[deckIndex])
}
decks.splice(deckIndex,1)
} else {
decks[deckIndex] = updatedDeck
}
Expand Down
3 changes: 2 additions & 1 deletion Cue/app/tabs/library/LibraryHome.js
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,8 @@ class LibraryHome extends React.Component {
[
{text: 'Remove', style: 'destructive'},
{text: 'Copy', onPress: () => this.props.onCopyDeck(deck)},
]
],
{ cancelable: false }
)
})

Expand Down

0 comments on commit 98bc96c

Please sign in to comment.