Initial commit.

This commit is contained in:
Ryan McGrath 2018-04-05 15:17:53 -04:00
commit 1f90ca2575
No known key found for this signature in database
GPG key ID: 811674B62B666830
63 changed files with 4059 additions and 0 deletions

20
stores/BookmarksStore.js Normal file
View file

@ -0,0 +1,20 @@
/**
* BookmarksStore
*
* Exactly what it sounds like.
*
* @copyright Ryan McGrath 2018
*/
import moment from 'moment';
import {observable, action, runInAction} from 'mobx';
class Store {
@observable bookmarks;
constructor() {
this.bookmarks = [];
}
};
export default new Store();

20
stores/SettingsStore.js Normal file
View file

@ -0,0 +1,20 @@
/**
* SettingsStore
*
* Exactly what it sounds like.
*
* @copyright Ryan McGrath 2018
*/
import moment from 'moment';
import {observable, action, runInAction} from 'mobx';
class Store {
@observable settings;
constructor() {
this.settings = {};
}
};
export default new Store();

View file

@ -0,0 +1,171 @@
/**
* TournamentEventStore
*
* Store for tournament event data. Currently kind of dumb, but that's fine.
*
* @copyright Ryan McGrath 2018
*/
import {observable, action, runInAction} from 'mobx';
class Store {
@observable data;
@observable fetchingData;
constructor() {
this.data = {};
}
fetchEventExpanded = async (tournamentSlug, eventSlug, opts) => {
const api = 'https://smash.gg/api/-/gg_api./tournament/' + tournamentSlug + '/event/' + eventSlug + ';';
const args = Object.assign({
expand: JSON.stringify(['phase', 'groups']),
reset: false,
slug: tournamentSlug,
eventSlug: eventSlug
}, opts || {});
return 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) : []);
});
}
/**
* 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: []
};
// 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()
});
});
}
return Promise.resolve(brackets);
});
};*/
}
export default new Store();

View file

@ -0,0 +1,140 @@
/**
* TournamentListingsStore
*
* Store for tournament listing and search data.
*
* @copyright Ryan McGrath 2018
*/
import axios from 'axios';
import moment from 'moment';
import {observable, action, runInAction} from 'mobx';
const placeholders = [
require('../images/placeholder1.png'),
require('../images/placeholder2.png'),
require('../images/placeholder3.png'),
];
class Store {
@observable tournamentsList;
@observable searchResults;
@observable fetchingData;
@observable mode;
constructor() {
this.tournamentsList = [];
this.searchResults = [];
this.fetchingData = false;
this.mode = 'featured';
}
@action('Fetch Featured Tournaments')
fetchFeatured = async (opts) => {
this.fetchingData = true;
this.mode = 'featured';
const args = Object.assign({
list: 'featured',
reset: false
}, opts || {});
const api = 'https://smash.gg/api/-/gg_api./tournaments/list/featured;';
const url = api + Object.keys(args).map(key => `${key}=${args[key]}`).join(';') + '?returnMeta=true'
const response = await fetch(url);
const data = await response.json();
runInAction('Parse tournament listing data', () => {
this.tournamentsList = this.parseData(data.entities.tournament, data.entities.event);
this.mode = 'featured';
this.fetchingData = false;
});
}
searchTimer = null;
cancelSource = null;
@action('Perform Search')
search = async (query) => {
if(this.searchTimer) {
clearTimeout(this.searchTimer);
if(this.cancelSource) {
this.cancelSource.cancel();
this.cancelSource = null;
}
}
if(query === '') {
this.mode = 'featured';
this.fetchingData = false;
return;
}
this.searchTimer = setTimeout(async () => {
this.fetchingData = true;
this.cancelSource = axios.CancelToken.source();
const api = 'https://smash.gg/api/-/gg_api./public/tournaments/schedule;';
const args = {
filter: encodeURIComponent(query),
page: 1,
per_page: 60,
reset: false,
schedule: true
};
const url = api + Object.keys(args).map(key => `${key}=${args[key]}`).join(';');
axios.get(url, {
cancelToken: this.cancelSource.token,
params: {returnMeta: 'true'}
}).then(response => {
runInAction('Parse search results data', () => {
this.searchResults = this.parseData(response.data.items.entities.tournament, response.data.items.entities.event);
this.mode = 'search';
this.fetchingData = false;
});
}).catch(thrown => {
// There's no need to do anything here, at least for now.
if(axios.isCancel(thrown))
return;
console.error(thrown);
});
}, 500);
}
parseData = (tourneys, events) => {
var tournaments = [];
for(var i = 0, l = tourneys.length; i < l; i++) {
var tourney = tourneys[i];
if(tourney['private'] || tourney.sandboxMode || tourney.testMode)
continue;
var img = tourney.images.filter(img => img.width === 1200);
if(img.length > 0)
tourney.memeleePromoImage = {width: img[0].width, msrc: {uri: img[0].url}};
else
tourney.memeleePromoImage = {width: 1200, msrc: placeholders[Math.floor(Math.random() * 3)]};
var starts = moment.unix(tourney.startAt).utc(),
ends = moment.unix(tourney.endAt);
tourney.memeleeTournamentRange = starts.format('MM/DD/YYYY') + ' - ' + ends.format('MM/DD/YYYY');
tourney.memeleeEventsCount = tourney.eventIds.length;
/* Stitch together the event listings... */
tourney.memeleeEvents = tourney.eventIds.map(eventID => {
const evts = events.filter(obj => obj.id === eventID);
return evts.length > 0 ? evts[0] : null;
}).filter(evt => evt !== null);
tournaments.push(tourney)
}
return tournaments;
}
}
export default new Store();

33
stores/index.js Normal file
View file

@ -0,0 +1,33 @@
/**
* smashGG
*
* Handles communication with Smash.gg endpoints. Mostly promise based.
*
* @copyright Ryan McGrath 2018, unless the content belongs to Smash.gg, in which case,
* it's them.
*/
import {v4} from 'uuid';
import moment from 'moment';
import {create} from 'mobx-persist';
import {AsyncStorage} from 'react-native';
import TournamentsListingStore from './TournamentsListingStore';
import TournamentEventStore from './TournamentEventStore';
import BookmarksStore from './BookmarksStore';
import SettingsStore from './SettingsStore';
const hydrate = create({
storage: AsyncStorage
});
const stores = {
Tournaments: TournamentsListingStore,
TournamentEventStore: TournamentEventStore,
Bookmarks: BookmarksStore,
Settings: SettingsStore
};
// Hydrate
export default stores;