diff --git a/controllers/AttendeesListViewController.js b/controllers/AttendeesListViewController.js
new file mode 100644
index 0000000..ec1e4e6
--- /dev/null
+++ b/controllers/AttendeesListViewController.js
@@ -0,0 +1,34 @@
+/**
+ * AttendeesListViewController
+ *
+ * Yeah, I do it iOS style. Handles fetching and displaying upcoming tournaments.
+ *
+ * @copyright Ryan McGrath 2018.
+ */
+
+import moment from 'moment';
+import React from 'react';
+import {FlatList, View, ActivityIndicator} from 'react-native';
+import {inject, observer} from 'mobx-react/native';
+import {SearchBar} from 'react-native-elements';
+
+import styles from '../styles';
+import Constants from '../utils/Constants';
+import MemeleeViewController from './MemeleeViewController';
+import TournamentRow from './components/TournamentRow';
+
+const keyExtractor = (item, index) => item.id;
+
+export default class AttendeesListViewController extends MemeleeViewController {
+ renderItem = ({item}) => ()
+
+ render() {
+ const props = {
+ data: this.props.data,
+ keyExtractor: keyExtractor,
+ renderItem: this.renderItem
+ };
+
+ return
+ }
+}
diff --git a/controllers/BracketViewController.js b/controllers/BracketViewController.js
index 968ec47..9247047 100644
--- a/controllers/BracketViewController.js
+++ b/controllers/BracketViewController.js
@@ -9,9 +9,8 @@
import moment from 'moment';
import React from 'react';
import {ScrollView, Text, View} from 'react-native';
-import {v4} from 'uuid';
+import {inject, observer} from 'mobx-react/native';
-//import SmashGG from '../store';
import MemeleeViewController from './MemeleeViewController';
const Match = ({set, ...rest}) => (
@@ -22,32 +21,17 @@ const Match = ({set, ...rest}) => (
);
+@inject('Events') @observer
export default class BracketViewController extends MemeleeViewController {
- state = {
- brackets: {
- winners: [],
- losers: [],
- grandFinals: []
- }
- };
-
componentWillMount() {
- const evtSlugs = this.props.evt.slug.split('/');
- const evtSlug = evtSlugs.length > 0 ? evtSlugs[evtSlugs.length - 1] : null;
- const tournamentSlug = this.props.tournament.slugs[0].replace('tournament/', '');
-
- SmashGG.fetchBracketData(tournamentSlug, evtSlug, this.props.bracket.id).then(this.updateBracketsData).catch(console.error);
+ this.props.Events.fetchBracketData(this.props.bracket.id);
}
-
- updateBracketsData = (brackets) => {
- this.setState({brackets: brackets});
- }
-
+
render() {
return (
{['winners', 'losers'].map(key => (
- {this.state.brackets[key].map(bracket => (
+ {this.props.Events.bracketData[key].map(bracket => (
{bracket.title}
{bracket.sets.map(set => )}
diff --git a/controllers/ContactViewController.js b/controllers/ContactViewController.js
new file mode 100644
index 0000000..ec1e4e6
--- /dev/null
+++ b/controllers/ContactViewController.js
@@ -0,0 +1,34 @@
+/**
+ * AttendeesListViewController
+ *
+ * Yeah, I do it iOS style. Handles fetching and displaying upcoming tournaments.
+ *
+ * @copyright Ryan McGrath 2018.
+ */
+
+import moment from 'moment';
+import React from 'react';
+import {FlatList, View, ActivityIndicator} from 'react-native';
+import {inject, observer} from 'mobx-react/native';
+import {SearchBar} from 'react-native-elements';
+
+import styles from '../styles';
+import Constants from '../utils/Constants';
+import MemeleeViewController from './MemeleeViewController';
+import TournamentRow from './components/TournamentRow';
+
+const keyExtractor = (item, index) => item.id;
+
+export default class AttendeesListViewController extends MemeleeViewController {
+ renderItem = ({item}) => ()
+
+ render() {
+ const props = {
+ data: this.props.data,
+ keyExtractor: keyExtractor,
+ renderItem: this.renderItem
+ };
+
+ return
+ }
+}
diff --git a/controllers/EventInfoViewController.js b/controllers/EventInfoViewController.js
index 35ae812..506572b 100644
--- a/controllers/EventInfoViewController.js
+++ b/controllers/EventInfoViewController.js
@@ -8,40 +8,61 @@
import moment from 'moment';
import React from 'react';
-import {ScrollView, Image, Text, View, TouchableOpacity} from 'react-native';
+import {ScrollView, StyleSheet, ActivityIndicator, Image, Text, View, TouchableOpacity} from 'react-native';
+import {inject, observer} from 'mobx-react/native';
import Markdown from 'react-native-simple-markdown'
-//import SmashGG from '../store';
+import SegmentedControlTab from 'react-native-segmented-control-tab';
+import SettingsList, {Header, Item} from 'react-native-settings-list';
+import styles from '../styles';
+import Constants from '../utils/Constants';
import MemeleeViewController from './MemeleeViewController';
+const Loading = (props) => (
+
+
+
+);
+
+const Standings = (props) => (
+ props.error ?
+ No Standings Found
+ Matches may not have been played yet.
+ :
+
+ Players
+ Losses
+
+
+ {props.standings.map(standing => (
+
+
+ {standing.standing}
+
+ {standing.name}
+ {standing.losses.map(loss => {loss})}
+
+ ))}
+
+
+);
+
+const s = StyleSheet.flatten(styles.tournamentDetailsEventWrapper);
+const Brackets = (props) => (
+
+ {props.brackets.map(bracket => (
+ - props.onPress(bracket)} />
+ ))}
+
+);
+
+@inject('Events') @observer
export default class EventInfoViewController extends MemeleeViewController {
- state = {
- standings: [],
- brackets: []
- };
-
- componentWillMount() {
- const evtSlugs = this.props.evt.slug.split('/');
- const evtSlug = evtSlugs.length > 0 ? evtSlugs[evtSlugs.length - 1] : null;
- const tournamentSlug = this.props.tournament.slugs[0].replace('tournament/', '');
-
- if((evtSlug && evtSlug !== '') && (tournamentSlug && tournamentSlug != '')) {
- SmashGG.fetchEventExpanded(tournamentSlug, evtSlug).then(this.updateBracketsData).catch(console.error);
- SmashGG.fetchEventStandings(tournamentSlug, evtSlug).then(this.updateStandingsData).catch(console.error);
- }
- }
-
- updateBracketsData = (data) => {
- this.setState({brackets: data});
- }
-
- updateStandingsData = (data) => {
- this.setState({standings: data});
- }
+ state = {selectedIndex: 0};
onBracketPress = (bracket) => {
this.props.navigator.push({
- screen: 'memelee.bracket',
+ screen: Constants.Screens.Bracket,
title: bracket.name,
backButtonTitle: 'Back',
passProps: {
@@ -53,29 +74,25 @@ export default class EventInfoViewController extends MemeleeViewController {
});
}
- render() {
- return (
- Brackets
- {this.state.brackets.map(bracket => (
- this.onBracketPress(bracket)}>
- {bracket.name}
-
- ))}
+ swapIndex = (index) => {
+ this.setState({selectedIndex: index});
+ }
- Standings
-
- Players
- Losses
-
- {this.state.standings.map(standing => (
-
-
- {standing.finalPlacement}
-
- {standing.name}
- {standing.losses.map(loss => {loss})}
-
- ))}
- );
+ render() {
+ return (
+
+
+ {this.state.selectedIndex === 0 ?
+ this.props.Events.fetchingStandingData ?
+ :
+ : null
+ }
+
+ {this.state.selectedIndex === 1 ?
+ this.props.Events.fetchingBracketData ?
+ :
+ : null
+ }
+ );
}
}
diff --git a/controllers/GenericInfoViewController.js b/controllers/GenericInfoViewController.js
new file mode 100644
index 0000000..17f7b0d
--- /dev/null
+++ b/controllers/GenericInfoViewController.js
@@ -0,0 +1,28 @@
+/**
+ * AttendeesListViewController
+ *
+ * Yeah, I do it iOS style. Handles fetching and displaying upcoming tournaments.
+ *
+ * @copyright Ryan McGrath 2018.
+ */
+
+import React from 'react';
+import {ScrollView, View} from 'react-native';
+import Markdown from 'react-native-markdown-renderer';
+import Hyperlink from 'react-native-hyperlink';
+
+import styles from '../styles';
+import Constants from '../utils/Constants';
+import MemeleeViewController from './MemeleeViewController';
+
+export default class GenericInfoViewController extends MemeleeViewController {
+ render = () => (
+
+
+
+ {this.props.info && this.props.info !== '' ? this.props.info : ''}
+
+
+
+ );
+}
diff --git a/controllers/LocationViewController.js b/controllers/LocationViewController.js
new file mode 100644
index 0000000..ec1e4e6
--- /dev/null
+++ b/controllers/LocationViewController.js
@@ -0,0 +1,34 @@
+/**
+ * AttendeesListViewController
+ *
+ * Yeah, I do it iOS style. Handles fetching and displaying upcoming tournaments.
+ *
+ * @copyright Ryan McGrath 2018.
+ */
+
+import moment from 'moment';
+import React from 'react';
+import {FlatList, View, ActivityIndicator} from 'react-native';
+import {inject, observer} from 'mobx-react/native';
+import {SearchBar} from 'react-native-elements';
+
+import styles from '../styles';
+import Constants from '../utils/Constants';
+import MemeleeViewController from './MemeleeViewController';
+import TournamentRow from './components/TournamentRow';
+
+const keyExtractor = (item, index) => item.id;
+
+export default class AttendeesListViewController extends MemeleeViewController {
+ renderItem = ({item}) => ()
+
+ render() {
+ const props = {
+ data: this.props.data,
+ keyExtractor: keyExtractor,
+ renderItem: this.renderItem
+ };
+
+ return
+ }
+}
diff --git a/controllers/TournamentInfoViewController.js b/controllers/TournamentInfoViewController.js
index 0a9f8b7..85538bd 100644
--- a/controllers/TournamentInfoViewController.js
+++ b/controllers/TournamentInfoViewController.js
@@ -9,12 +9,15 @@
import moment from 'moment';
import React from 'react';
import {ScrollView, StyleSheet, Image, Text, View, TouchableOpacity, Dimensions} from 'react-native';
-//import Markdown from 'react-native-simple-markdown'
-import Markdown from 'react-native-markdown-renderer';
+import Markdown, {PluginContainer} from 'react-native-markdown-renderer';
import SegmentedControlTab from 'react-native-segmented-control-tab';
import SettingsList, {Header, Item} from 'react-native-settings-list';
+import linkify from 'linkify-it';
import styles from '../styles';
+import Constants from '../utils/Constants';
+import {openURL, parseSlugs} from '../utils';
+import EventsStore from '../stores/TournamentEventStore';
import MemeleeViewController from './MemeleeViewController';
const w = Dimensions.get('screen').width;
@@ -31,41 +34,126 @@ const w = Dimensions.get('screen').width;
export default class TournamentInfoViewController extends MemeleeViewController {
state = {
- selectedIndex: 0
+ selectedIndex: 0,
+ tabs: []
};
+ componentWillMount = () => {
+ const tabs = [
+ {slug: 'attendees', name: 'Attendees', screen: Constants.Screens.Attendees, adminOnly: false},
+ {slug: 'location', name: 'Location', screen: Constants.Screens.Location, adminOnly: false},
+ {slug: 'contact', name: 'Contact', screen: Constants.Screens.Contact, adminOnly: false}
+ ];
+
+ if(this.props.tournament.rules && this.props.tournament.rules !== '') {
+ if(this.props.tournament.rules.startsWith('http://') || this.props.tournament.rules.startsWith('https://')) {
+ tabs.push({slug: 'rules', name: 'Rules', url: this.props.tournament.rules, adminOnly: false});
+ } else {
+ tabs.push({slug: 'rules', name: 'Rules', info: this.props.tournament.rules, adminOnly: false});
+ }
+ }
+
+ if(this.props.tournament.prizes && this.props.tournament.prizes !== '') {
+ if(this.props.tournament.prizes.startsWith('http://') || this.props.tournament.prizes.startsWith('https://')) {
+ tabs.push({slug: 'prizes', name: 'Prizes', url: this.props.tournament.prizes, adminOnly: false});
+ } else {
+ tabs.push({slug: 'prizes', name: 'Prizes', info: this.props.tournament.prizes, adminOnly: false});
+ }
+ }
+
+ if(this.props.tournament.publishing && this.props.tournament.publishing.fantasy)
+ tabs.push({
+ slug: 'fantasy',
+ name: 'Fantasy',
+ url: 'https://smash.gg/tournament/' + parseSlugs(this.props.tournament, null).tournament + '/fantasy/',
+ adminOnly: false
+ });
+
+ const generatedTabs = this.props.tournament.generatedTabs;
+ if(generatedTabs) {
+ const objs = Object.keys(generatedTabs).map(key => generatedTabs[key]);
+
+ objs.forEach(tab => {
+ Object.keys(tab).map(key => ({
+ slug: key,
+ name: tab[key].name,
+ adminOnly: tab[key].adminOnly
+ })).filter(tab => !tab.adminOnly || tab.adminOnly === false).forEach(tab => tabs.push(tab));
+ });
+ }
+
+ this.setState({tabs: tabs});
+ }
+
onEventTapped = (evt) => {
+ EventsStore.loadEventData(this.props.tournament, evt);
this.props.navigator.push({
- screen: 'memelee.tournamentEventInfoScreen',
+ screen: Constants.Screens.TournamentEventInfoScreen,
title: evt.name,
passProps: {tournament: this.props.tournament, evt: evt},
navigatorStyle: {tabBarHidden: true}
});
}
+ handleTab = (tab) => {
+ if(tab.screen)
+ return this.props.navigator.push({
+ screen: tab.screen,
+ title: tab.name,
+ passProps: {data: []},
+ navigatorStyle: {tabBarHidden: true}
+ });
+
+ if(tab.url)
+ return openURL(tab.url);
+
+ if(tab.info)
+ return this.props.navigator.push({
+ screen: Constants.Screens.Info,
+ title: tab.name,
+ passProps: {info: tab.info},
+ navigatorStyle: {tabBarHidden: true}
+ });
+ }
+
swapIndex = (index) => {
this.setState({
selectedIndex: index
});
}
+
+ plugins = []
render() {
const s = StyleSheet.flatten(styles.tournamentDetailsEventWrapper);
+ const ss = {
+ itemWidth: 50,
+ backgroundColor: s.backgroundColor,
+ style: styles.tournamentDetailsEventWrapper,
+ titleStyle: styles.tournamentDetailsEventItem
+ };
return (
- {this.state.selectedIndex === 0 ? (
-
- {this.props.tournament.details && this.props.tournament.details !== '' ? this.props.tournament.details : ''}
-
- ) : null}
+ {this.state.selectedIndex === 0 ? (
+
+
+ {this.props.tournament.details && this.props.tournament.details !== '' ? this.props.tournament.details : ''}
+
+
+
+
+ {this.state.tabs.map(tab => - this.handleTab(tab)} />)}
+
+
+ ) : null}
{this.state.selectedIndex === 1 ? (
{this.props.tournament.memeleeEvents.map(evt => (
-
+ - this.onEventTapped(evt)} />
))}
) : null}
);
diff --git a/controllers/TournamentsListViewController.js b/controllers/TournamentsListViewController.js
index afd64e7..5c70af4 100644
--- a/controllers/TournamentsListViewController.js
+++ b/controllers/TournamentsListViewController.js
@@ -13,6 +13,7 @@ import {inject, observer} from 'mobx-react/native';
import {SearchBar} from 'react-native-elements';
import styles from '../styles';
+import Constants from '../utils/Constants';
import MemeleeViewController from './MemeleeViewController';
import TournamentRow from './components/TournamentRow';
@@ -26,7 +27,7 @@ export default class UpcomingTournamentsViewController extends MemeleeViewContro
onTap = (tournament) => {
this.props.navigator.push({
- screen: 'memelee.tournamentInfoScreen',
+ screen: Constants.Screens.TournamentInfoScreen,
title: tournament.name,
passProps: {tournament: tournament},
navigatorStyle: {tabBarHidden: true}
diff --git a/controllers/components/TournamentRow.js b/controllers/components/TournamentRow.js
index a23541f..a6c6f70 100644
--- a/controllers/components/TournamentRow.js
+++ b/controllers/components/TournamentRow.js
@@ -37,8 +37,7 @@ export default class TournamentRow extends React.Component {
{this.props.tournament.hasOnlineEvents && (!this.props.tournament.city || this.props.tournament.city === '') ? 'Online' : (this.props.tournament.city ? this.props.tournament.city + ', ' : '') + this.props.tournament.addrState}
-
- {this.props.tournament.memeleeEventsCount} Events
+ {this.props.tournament.memeleeEventsCount} Events
{this.props.tournament.attendeeCount} Attendees
diff --git a/index.js b/index.js
index 0810099..d72af51 100644
--- a/index.js
+++ b/index.js
@@ -13,30 +13,28 @@ import Icon from 'react-native-vector-icons/MaterialCommunityIcons';
import styles from './styles';
import Stores from './stores';
+import Constants from './utils/Constants';
import Provider from './utils/MobxRnnProvider';
import TournamentsListViewController from './controllers/TournamentsListViewController';
import TournamentInfoViewController from './controllers/TournamentInfoViewController';
+import AttendeesListViewController from './controllers/AttendeesListViewController';
+import LocationViewController from './controllers/LocationViewController';
+import ContactViewController from './controllers/ContactViewController';
+import GenericInfoViewController from './controllers/GenericInfoViewController';
import EventInfoViewController from './controllers/EventInfoViewController';
import BracketViewController from './controllers//BracketViewController';
import BookmarksViewController from './controllers/BookmarksViewController';
import SettingsViewController from './controllers/settings';
-const Constants = {
- Screens: {
- TournamentsList: 'memelee.tournamentsList',
- TournamentInfoScreen: 'memelee.tournamentInfoScreen',
- TournamentEventInfoScreen: 'memelee.tournamentEventInfoScreen',
- Bracket: 'memelee.tournamentBracket',
- Bookmarks: 'memelee.bookmarks',
- Settings: 'memelee.settings'
- }
-};
-
Navigation.registerComponent(Constants.Screens.TournamentsList, () => TournamentsListViewController, Stores, Provider);
Navigation.registerComponent(Constants.Screens.TournamentInfoScreen, () => TournamentInfoViewController, Stores, Provider);
Navigation.registerComponent(Constants.Screens.TournamentEventInfoScreen, () => EventInfoViewController, Stores, Provider);
-Navigation.registerComponent(Constants.Screens.TournamentEventBracket, () => BracketViewController, Stores, Provider);
+Navigation.registerComponent(Constants.Screens.Attendees, () => AttendeesListViewController, Stores, Provider);
+Navigation.registerComponent(Constants.Screens.Location, () => LocationViewController, Stores, Provider);
+Navigation.registerComponent(Constants.Screens.Contact, () => ContactViewController, Stores, Provider);
+Navigation.registerComponent(Constants.Screens.Info, () => GenericInfoViewController, Stores, Provider);
+Navigation.registerComponent(Constants.Screens.Bracket, () => BracketViewController, Stores, Provider);
Navigation.registerComponent(Constants.Screens.Bookmarks, () => BookmarksViewController, Stores, Provider);
Navigation.registerComponent(Constants.Screens.Settings, () => SettingsViewController, Stores, Provider);
diff --git a/ios/memelee.xcodeproj/project.xcworkspace/xcuserdata/laika.xcuserdatad/UserInterfaceState.xcuserstate b/ios/memelee.xcodeproj/project.xcworkspace/xcuserdata/laika.xcuserdatad/UserInterfaceState.xcuserstate
index e17fd6d..04176fb 100644
Binary files a/ios/memelee.xcodeproj/project.xcworkspace/xcuserdata/laika.xcuserdatad/UserInterfaceState.xcuserstate and b/ios/memelee.xcodeproj/project.xcworkspace/xcuserdata/laika.xcuserdatad/UserInterfaceState.xcuserstate differ
diff --git a/ios/memelee/Info.plist b/ios/memelee/Info.plist
index 41b99e1..593408c 100644
--- a/ios/memelee/Info.plist
+++ b/ios/memelee/Info.plist
@@ -26,14 +26,10 @@
NSAppTransportSecurity
- NSExceptionDomains
-
- localhost
-
- NSExceptionAllowsInsecureHTTPLoads
-
-
-
+ NSThirdPartyExceptionRequiresForwardSecrecy
+
+ NSAllowsArbitraryLoads
+
NSLocationWhenInUseUsageDescription
diff --git a/package.json b/package.json
index 75daf48..1714b5b 100644
--- a/package.json
+++ b/package.json
@@ -20,6 +20,7 @@
"axios": "^0.18.0",
"babel-preset-react-native-stage-0": "^1.0.1",
"jsog": "^1.0.7",
+ "linkify-it": "^2.0.3",
"mobx": "^4.1.1",
"mobx-persist": "^0.4.1",
"mobx-react": "^5.0.0",
@@ -29,6 +30,7 @@
"react-controllables": "^0.6.0",
"react-native": "0.52.0",
"react-native-elements": "^0.19.0",
+ "react-native-hyperlink": "0.0.12",
"react-native-markdown-renderer": "^3.1.0",
"react-native-navigation": "^1.1.426",
"react-native-segmented-control-tab": "^3.2.2",
diff --git a/stores/TournamentEventStore.js b/stores/TournamentEventStore.js
index 5d97dde..e44618b 100644
--- a/stores/TournamentEventStore.js
+++ b/stores/TournamentEventStore.js
@@ -6,166 +6,222 @@
* @copyright Ryan McGrath 2018
*/
+import {v4} from 'uuid';
import {observable, action, runInAction} from 'mobx';
+import {parseSlugs} from '../utils';
class Store {
- @observable data;
+ @observable.ref phases;
+ @observable.ref bracketData;
@observable fetchingData;
+
+ @observable.ref standings;
+ @observable fetchingStandingData;
+ @observable standingsError;
constructor() {
- this.data = {};
+ this.phases = [];
+ this.standings = [];
+ this.bracketData = {winners: [], losers: []};
+ this.tournamentSlug = null;
+ this.evtSlug = null;
+ this.fetchingData = false;
+ this.fetchingStandingData = false;
+ this.standingsError = false;
}
- fetchEventExpanded = async (tournamentSlug, eventSlug, opts) => {
- const api = 'https://smash.gg/api/-/gg_api./tournament/' + tournamentSlug + '/event/' + eventSlug + ';';
- const args = Object.assign({
+ parseSlugs = (tournament, evt) => {
+ const slugs = parseSlugs(tournament, evt);
+ this.tournamentSlug = slugs.tournament;
+ this.evtSlug = slugs.evt;
+ }
+
+ loadEventData = async(tournament, evt) => {
+ this.phases = [];
+ this.standings = [];
+ this.bracketData = {winners: [], losers: []};
+ this.tournamentSlug = null;
+ this.evtSlug = null;
+
+ this.parseSlugs(tournament, evt);
+
+ if(this.tournamentSlug && this.evtSlug) {
+ this.fetchPhases();
+ this.fetchStandings();
+ }
+ }
+
+ /**
+ * fetchPhases
+ *
+ * Given a set of slugs, fetches event phases.
+ */
+ fetchPhases = () => {
+ const api = 'https://smash.gg/api/-/gg_api./tournament/' + this.tournamentSlug + '/event/' + this.evtSlug + ';';
+ const args = {
expand: JSON.stringify(['phase', 'groups']),
reset: false,
- slug: tournamentSlug,
- eventSlug: eventSlug
- }, opts || {});
+ slug: this.tournamentSlug,
+ eventSlug: this.evtSlug
+ };
- return fetch(
- api + Object.keys(args).map(key => `${key}=${args[key]}`).join(';') + '?returnMeta=true'
- ).then(response => response.json()).then(data => {
+ fetch(api + Object.keys(args).map(key => `${key}=${args[key]}`).join(';') + '?returnMeta=true').then(
+ response => response.json()
+ ).then(data => {
if(typeof data.success !== 'undefined' && !data.success)
throw new Error(data.message);
- return Promise.resolve(data.entities.phase ? data.entities.phase.map(phase => phase) : []);
+ runInAction('Parse out Phases...', () => {
+ if(data.entities.phase)
+ this.phases = data.entities.phase.map(phase => phase);
+ });
});
}
-/**
- * fetchStandings
- *
- * Given a set of slugs, fetches event standings.
- *
- * @arg tournamentSlug {String} Slug for the tournament (e.g, "the-mango").
- * @arg eventSlug {String} Slug for the event (e.g, "melee-singles").
- * @arg opts {Object} Optional object for overriding request properties.
- * @return Promise
- */
-/*const fetchEventStandings = (tournamentSlug, eventSlug, opts) => {
- const api = 'https://smash.gg/api/-/gg_api./tournament/' + tournamentSlug + '/event/' + eventSlug + '/standings;';
- const args = Object.assign({
- entityId: null,
- entityType: 'event',
- slug: tournamentSlug,
- eventSlug: eventSlug,
- expand: JSON.stringify(['entrants', 'standingGroup', 'attendee']),
- mutations: JSON.stringify(['playerData', 'standingLosses']),
- page: 1,
- per_page: 25
- }, opts || {});
-
- return fetch(
- api + Object.keys(args).map(key => `${key}=${args[key]}`).join(';') + '?returnMeta=true'
- ).then(response => response.json()).then(data => {
- var s = data.items.entities.standing,
- l = s.length;
-
- return Promise.resolve(data.items.entities.entrants.map(entrants => {
- if(typeof entrants.losses === 'undefined')
- entrants.losses = [];
-
- data.items.entities.standing.forEach(standing => {
- if(standing.entityId === entrants.id && standing.mutations)
- entrants.losses = entrants.losses.concat(standing.mutations.losses.map(loss => loss.name));
- });
-
- return entrants;
- }).sort((a, b) => a.finalPlacement > b.finalPlacement));
- });
-};
-
-/**
- * fetchBracketData
- *
- * Given a set of slugs/bracket ID, fetches bracket data for rendering. Performs a lot
- * of smaller operations to transpose it into an actually usable format for display.
- *
- * @arg tournamentSlug {String} Slug for the tournament (e.g, "the-mango").
- * @arg eventSlug {String} Slug for the event (e.g, "melee-singles").
- * @arg bracketID {String or Number} ID for the bracket - SmashGG calls this a phase. Go fig.
- * @arg opts {Object} Optional object for overriding request properties.
- * @return Promise
-
-const fetchBracketData = (tournamentSlug, eventSlug, bracketID, opts) => {
- const api = 'https://smash.gg/api/-/gg_api./tournament/' + tournamentSlug + '/event/' + eventSlug + '/phase_groups;';
- const args = {
- slug: tournamentSlug,
- eventSlug: eventSlug,
- expand: JSON.stringify(['results', 'character']),
- mutations: JSON.stringify(['ffaData', 'playerData']),
- filter: JSON.stringify({phaseId: bracketID}),
- getSingleBracket: true,
- page: 1,
- per_page: 20,
- reset: false,
- };
-
- return fetch(
- api + Object.keys(args).map(key => `${key}=${args[key]}`).join(';') + '?returnMeta=true'
- ).then(response => response.json()).then(data => {
- const grands = [];
- const brackets = {
- winners: [],
- losers: []
+ /**
+ * fetchStandings
+ *
+ * Given a set of slugs, fetches event standings.
+ */
+ fetchStandings = () => {
+ const api = 'https://smash.gg/api/-/gg_api./tournament/' + this.tournamentSlug + '/event/' + this.evtSlug + '/standings;';
+ const args = {
+ entityId: null,
+ entityType: 'event',
+ slug: this.tournamentSlug,
+ eventSlug: this.evtSlug,
+ expand: JSON.stringify(['entrants', 'standingGroup', 'attendee']),
+ mutations: JSON.stringify(['playerData', 'standingLosses']),
+ page: 1,
+ per_page: 25
};
- // Filter through the set list and make sure each object is filled out with necessary data,
- // and then place them into their bracket accordingly. Brackets will be sorted afterwards.
- data.items.entities.sets.forEach(function(result) {
- if(result.entrant1Id === null || result.entrant2Id === null)
- return;
+ this.standings = [];
+ this.fetchingStandingsData = true;
+ fetch(api + Object.keys(args).map(key => `${key}=${args[key]}`).join(';') + '?returnMeta=true').then(
+ response => response.json()
+ ).then(data => {
+ runInAction('Parse out Standings...', () => {
+ var s = data.items.entities.standing,
+ l = s.length;
- result.entrant1 = {};
- result.entrant2 = {};
+ this.standingsError = false;
+ this.standings = data.items.entities.entrants.map(entrants => {
+ if(typeof entrants.losses === 'undefined')
+ entrants.losses = [];
- data.items.entities.entrants.forEach(function(entrant) {
- if(entrant.id === result.entrant1Id)
- result.entrant1 = entrant;
-
- if(entrant.id === result.entrant2Id)
- result.entrant2 = entrant;
- });
+ data.items.entities.standing.forEach(standing => {
+ if(standing.entityId === entrants.id) {
+ if(standing.mutations)
+ entrants.losses = entrants.losses.concat(standing.mutations.losses.map(loss => loss.name));
- if(result.isGF) {
- grands.push(result);
- } else {
- var isLosers = result.displayRound < 0,
- key = isLosers ? 'losers' : 'winners',
- idx = isLosers ? (result.displayRound * -1) : result.displayRound;
-
- while(brackets[key].length < idx) {
- brackets[key].push({
- title: '', // Filled in later~
- sets: []
+ entrants.standing = standing.standing;
+ }
});
- }
-
- brackets[key][idx - 1].title = result.fullRoundText;
- brackets[key][idx - 1].sets.push(result);
- if(!brackets[key][idx - 1].key)
- brackets[key][idx - 1].key = v4();
- }
- });
- // GFs are technically in the winners bracket, but for presentation purposes they're shoved
- // in after to be like how smash.gg presents them.
- if(grands.length > 0) {
- grands.forEach(grandFinal => {
- brackets.winners.push({
- title: 'Grand Finals',
- sets: [grandFinal],
- key: v4()
- });
+ return entrants;
+ }).sort((a, b) => a.finalPlacement > b.finalPlacement);
+ this.fetchingStandingsData = false;
});
- }
+ }).catch(error => {
+ runInAction('Event standings data failed', () => {
+ this.standingsError = true;
+ this.fetchingStandingsData = false;
+ });
+ });
+ }
- return Promise.resolve(brackets);
- });
-};*/
-}
+ /**
+ * fetchBracketData
+ *
+ * Given a set of slugs/bracket ID, fetches bracket data for rendering. Performs a lot
+ * of smaller operations to transpose it into an actually usable format for display.
+ *
+ * @arg bracketID {String or Number} ID for the bracket - SmashGG calls this a phase. Go fig.
+ */
+ fetchBracketData = (bracketID) => {
+ this.bracketData = {winners: [], losers: []};
+ this.bracketID = bracketID;
+
+ const api = 'https://smash.gg/api/-/gg_api./tournament/' + this.tournamentSlug + '/event/' + this.evtSlug + '/phase_groups;';
+ const args = {
+ slug: this.tournamentSlug,
+ eventSlug: this.evtSlug,
+ expand: JSON.stringify(['results', 'character']),
+ mutations: JSON.stringify(['ffaData', 'playerData']),
+ filter: JSON.stringify({phaseId: bracketID}),
+ getSingleBracket: true,
+ page: 1,
+ per_page: 20,
+ reset: false,
+ };
+
+ const url = api + Object.keys(args).map(key => `${key}=${args[key]}`).join(';') + '?returnMeta=true'
+ console.log(url);
+ fetch(
+ url
+ ).then(response => response.json()).then(data => {
+ const grands = [];
+ const brackets = {
+ winners: [],
+ losers: []
+ };
+
+ // Filter through the set list and make sure each object is filled out with necessary data,
+ // and then place them into their bracket accordingly. Brackets will be sorted afterwards.
+ data.items.entities.sets.forEach(function(result) {
+ if(result.entrant1Id === null || result.entrant2Id === null)
+ return;
+
+ result.entrant1 = {};
+ result.entrant2 = {};
+
+ data.items.entities.entrants.forEach(function(entrant) {
+ if(entrant.id === result.entrant1Id)
+ result.entrant1 = entrant;
+
+ if(entrant.id === result.entrant2Id)
+ result.entrant2 = entrant;
+ });
+
+ if(result.isGF) {
+ grands.push(result);
+ } else {
+ var isLosers = result.displayRound < 0,
+ key = isLosers ? 'losers' : 'winners',
+ idx = isLosers ? (result.displayRound * -1) : result.displayRound;
+
+ while(brackets[key].length < idx) {
+ brackets[key].push({
+ title: '', // Filled in later~
+ sets: []
+ });
+ }
+
+ brackets[key][idx - 1].title = result.fullRoundText;
+ brackets[key][idx - 1].sets.push(result);
+ if(!brackets[key][idx - 1].key)
+ brackets[key][idx - 1].key = v4();
+ }
+ });
+
+ // GFs are technically in the winners bracket, but for presentation purposes they're shoved
+ // in after to be like how smash.gg presents them.
+ if(grands.length > 0) {
+ grands.forEach(grandFinal => {
+ brackets.winners.push({
+ title: 'Grand Finals',
+ sets: [grandFinal],
+ key: v4()
+ });
+ });
+ }
+
+ runInAction('Set Bracket Data', () => {
+ this.bracketData = brackets;
+ });
+ });
+ }
+};
export default new Store();
diff --git a/stores/index.js b/stores/index.js
index 9b6520f..70c3660 100644
--- a/stores/index.js
+++ b/stores/index.js
@@ -23,7 +23,7 @@ const hydrate = create({
const stores = {
Tournaments: TournamentsListingStore,
- TournamentEventStore: TournamentEventStore,
+ Events: TournamentEventStore,
Bookmarks: BookmarksStore,
Settings: SettingsStore
};
diff --git a/styles/index.js b/styles/index.js
index a3322a7..461503c 100644
--- a/styles/index.js
+++ b/styles/index.js
@@ -108,6 +108,19 @@ const stylesheet = StyleSheet.create({
flex: 1,
backgroundColor: iconColor,
padding: 10,
+ },
+
+ eventsErrorTextHeader: {
+ textAlign: 'center',
+ color: textColor,
+ fontWeight: 'bold',
+ fontSize: 16
+ },
+
+ eventsErrorText: {
+ textAlign: 'center',
+ marginTop: 5,
+ color: textColor
}
});
diff --git a/utils/Constants.js b/utils/Constants.js
new file mode 100644
index 0000000..c1e1098
--- /dev/null
+++ b/utils/Constants.js
@@ -0,0 +1,24 @@
+/**
+ * Constants
+ *
+ * They're constants. CONSTANTS. CONSTANCE TURN THE MUSIC DOWN.
+ *
+ * @copyright Ryan McGrath 2018
+ */
+
+export default {
+ Screens: {
+ TournamentsList: 'memelee.tournamentsList',
+ TournamentInfoScreen: 'memelee.tournamentInfoScreen',
+ TournamentEventInfoScreen: 'memelee.tournamentEventInfoScreen',
+
+ Attendees: 'memelee.attendeesListViewController',
+ Location: 'memelee.locationViewController',
+ Contact: 'memelee.contactViewController',
+ Info: 'memelee.infoViewController',
+
+ Bracket: 'memelee.tournamentBracket',
+ Bookmarks: 'memelee.bookmarks',
+ Settings: 'memelee.settings'
+ }
+};
diff --git a/utils/index.js b/utils/index.js
new file mode 100644
index 0000000..81f01ef
--- /dev/null
+++ b/utils/index.js
@@ -0,0 +1,35 @@
+/**
+ * Utils
+ *
+ * Various utilities used throughout the app. A kitchen junk drawer, if you will.
+ *
+ * @copyright Ryan McGrath 2018
+ */
+
+import {Linking} from 'react-native';
+
+export const parseSlugs = (tournament, evt) => {
+ const evtSlugs = evt && evt.slug.split('/');
+ const evtSlug = evt && evtSlugs.length > 0 ? evtSlugs[evtSlugs.length - 1] : null;
+ const tournamentSlug = (
+ tournament.slug ? tournament.slug :
+ tournament.slugs && tournament.slugs.length ? tournament.slugs[0] : ''
+ ).replace('tournament/', '');
+
+ const slugs = {tournamentSlug: null, evtSlug: null};
+
+ if(tournamentSlug && tournamentSlug !== '')
+ slugs.tournament = tournamentSlug;
+
+ if(evtSlug && evtSlug !== '')
+ slugs.evt = evtSlug;
+
+ return slugs;
+};
+
+export const openURL = (url) => {
+ return Linking.canOpenURL(url).then(supported => {
+ if(supported) Linking.openURL(url);
+ else console.warn('Cannot open URL: ' + url);
+ });
+};