mirror of
https://github.com/misenhower/splatoon2.ink.git
synced 2026-09-10 19:45:10 -05:00
Convert site to use Vuex to manage its state
This puts all the SplatNet data and other parameters in a centralized store (rather than passing down everything from a top-level component via props).
This commit is contained in:
@@ -53,39 +53,39 @@ async function captureScreenshot(options) {
|
||||
return result;
|
||||
}
|
||||
|
||||
function captureScheduleScreenshot(startTime) {
|
||||
let hash = `/schedules/${startTime}`;
|
||||
function captureScheduleScreenshot(now) {
|
||||
let hash = `/schedules/${now}`;
|
||||
|
||||
return captureScreenshot({ hash });
|
||||
}
|
||||
|
||||
function captureGearScreenshot(startTime, endTime) {
|
||||
let hash = `/splatNetGear/${startTime}/${endTime}`;
|
||||
function captureGearScreenshot(now) {
|
||||
let hash = `/splatNetGear/${now}`;
|
||||
|
||||
return captureScreenshot({ hash, viewport: { height: 700 } });
|
||||
}
|
||||
|
||||
function captureSalmonRunScreenshot(startTime) {
|
||||
let hash = `/salmonRun/${startTime}`;
|
||||
function captureSalmonRunScreenshot(now) {
|
||||
let hash = `/salmonRun/${now}`;
|
||||
|
||||
return captureScreenshot({ hash });
|
||||
}
|
||||
|
||||
function captureSalmonRunGearScreenshot(startTime) {
|
||||
let hash = `/salmonRunGear/${startTime}`;
|
||||
function captureSalmonRunGearScreenshot(now) {
|
||||
let hash = `/salmonRunGear/${now}`;
|
||||
|
||||
return captureScreenshot({ hash });
|
||||
}
|
||||
|
||||
function captureNewWeaponScreenshot(releaseTime) {
|
||||
let hash = `/newWeapon/${releaseTime}`;
|
||||
function captureNewWeaponScreenshot(now) {
|
||||
let hash = `/newWeapon/${now}`;
|
||||
|
||||
return captureScreenshot({ hash });
|
||||
}
|
||||
|
||||
function captureSplatfestScreenshot(region, startTime, regions) {
|
||||
function captureSplatfestScreenshot(region, now, regions) {
|
||||
regions = regions.join(',');
|
||||
let hash = `/splatfest/${region}/${startTime}?regions=${regions}`;
|
||||
let hash = `/splatfest/${region}/${now}?regions=${regions}`;
|
||||
|
||||
return captureScreenshot({ hash });
|
||||
}
|
||||
|
||||
@@ -27,9 +27,8 @@ class GearTweet extends TwitterPostBase {
|
||||
}
|
||||
|
||||
getImage(data) {
|
||||
let startTime = getTopOfCurrentHour();
|
||||
let endTime = this.getDataTime();
|
||||
return captureGearScreenshot(startTime, endTime);
|
||||
let now = getTopOfCurrentHour();
|
||||
return captureGearScreenshot(now);
|
||||
}
|
||||
|
||||
getText(data) {
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
<!-- Empty spacer to add some room when festivals are active -->
|
||||
</div>
|
||||
|
||||
<div class="level-item" v-if="merchandises && merchandises.length">
|
||||
<div class="level-item" v-if="hasMerchandises">
|
||||
<router-link to="/splatnet" class="button is-translucent-dark is-rounded">
|
||||
<span class="font-splatoon2">{{ $t('splatnet_gear.button') }}</span>
|
||||
</router-link>
|
||||
@@ -58,73 +58,73 @@
|
||||
|
||||
<div v-else>
|
||||
<!-- Active Splatfest: Show the Splatfest box and the Splatfest Battle box -->
|
||||
<div v-if="isSelectedFestivalActive" class="columns is-desktop limited-width">
|
||||
<div v-if="selectedRegionHasActiveSplatfest" class="columns is-desktop limited-width">
|
||||
<div class="column">
|
||||
<div class="splatfest tilt-left">
|
||||
<div class="hook-box">
|
||||
<SplatfestBox :festival="selectedFestival" :now="now"></SplatfestBox>
|
||||
<SplatfestBox :festival="selectedRegionCurrentSplatfest" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="column">
|
||||
<ScheduleBox class="main-schedule-box splatfest-schedule-box tilt-right" cssClass="regular" :schedules="regular" :festival="selectedFestival" :now="now"></ScheduleBox>
|
||||
<ScheduleBox class="main-schedule-box splatfest-schedule-box tilt-right" mode="regular" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- No active Splatfest: Show the Regular, Ranked, and League Battle boxes -->
|
||||
<div v-else class="columns is-desktop">
|
||||
<div class="column">
|
||||
<ScheduleBox class="main-schedule-box tilt-left" cssClass="regular" :schedules="regular" :now="now"></ScheduleBox>
|
||||
<ScheduleBox class="main-schedule-box tilt-left" mode="regular" />
|
||||
</div>
|
||||
<div class="column">
|
||||
<ScheduleBox class="main-schedule-box tilt-right" cssClass="ranked" :schedules="ranked" :now="now"></ScheduleBox>
|
||||
<ScheduleBox class="main-schedule-box tilt-right" mode="ranked" />
|
||||
</div>
|
||||
<div class="column">
|
||||
<ScheduleBox class="main-schedule-box tilt-left" cssClass="league" :schedules="league" :now="now"></ScheduleBox>
|
||||
<ScheduleBox class="main-schedule-box tilt-left" mode="league" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Upcoming Splatfest and Salmon Run boxes -->
|
||||
<div class="columns is-desktop" :class="{'limited-width': !bottomRowHasThreeColumns}">
|
||||
<div class="column" v-if="selectedFestival && !isSelectedFestivalActive">
|
||||
<div class="column" v-if="selectedRegionCurrentSplatfest && !selectedRegionHasActiveSplatfest">
|
||||
<div class="splatfest tilt-right" style="margin-top: 40px">
|
||||
<div class="hook-box">
|
||||
<SplatfestBox :festival="selectedFestival" :results="selectedFestivalResults" :now="now"></SplatfestBox>
|
||||
<SplatfestBox :festival="selectedRegionCurrentSplatfest" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="column" v-if="coopSchedules">
|
||||
<div class="column" v-if="hasSalmonRun">
|
||||
<div class="salmon-run tilt-left">
|
||||
<div class="hook-box">
|
||||
<SalmonRunBox :coop="coop" :coopSchedules="coopSchedules" :now="now"></SalmonRunBox>
|
||||
<SalmonRunBox />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="column is-narrow-desktop-only" :class="{'is-hidden-touch is-hidden-desktop-only is-hidden-widescreen-only': bottomRowHasThreeColumns }" style="margin-top: 40px" v-if="newWeapons">
|
||||
<div class="column is-narrow-desktop-only" :class="{'is-hidden-touch is-hidden-desktop-only is-hidden-widescreen-only': bottomRowHasThreeColumns }" style="margin-top: 40px" v-if="hasWeapons">
|
||||
<div class="new-weapon">
|
||||
<div class="columns">
|
||||
<div class="column" v-for="(weapon, index) in newWeapons">
|
||||
<div class="column" v-for="(weapon, index) in weapons" :key="weapon.id">
|
||||
<NewWeaponBox
|
||||
:weapon="weapon"
|
||||
:class="(index % 2 == 0) ? 'tilt-right' : 'tilt-left'"
|
||||
></NewWeaponBox>
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="is-hidden-fullhd" style="margin-top: 20px" v-if="newWeapons && bottomRowHasThreeColumns">
|
||||
<div class="is-hidden-fullhd" style="margin-top: 20px" v-if="hasWeapons && bottomRowHasThreeColumns">
|
||||
<div class="new-weapon">
|
||||
<div style="display: flex; align-items: center; justify-content: center;">
|
||||
<div v-for="(weapon, index) in newWeapons" style="margin: 0 20px">
|
||||
<div v-for="(weapon, index) in weapons" style="margin: 0 20px" :key="weapon.id">
|
||||
<NewWeaponBox
|
||||
:weapon="weapon"
|
||||
:class="(index % 2 == 0) ? 'tilt-right' : 'tilt-left'"
|
||||
style="min-width: 250px"
|
||||
></NewWeaponBox>
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -143,7 +143,7 @@
|
||||
<router-link to="/calendars">Calendar Feeds</router-link>
|
||||
–
|
||||
<a href="https://twitter.com/Splatoon2inkbot" target="_blank">Twitter</a>
|
||||
<template v-if="splatnet.festivals">
|
||||
<template v-if="hasSplatfests">
|
||||
–
|
||||
<router-link to="/splatfests">{{ $t('splatfest.previous') }}</router-link>
|
||||
</template>
|
||||
@@ -153,20 +153,6 @@
|
||||
</div>
|
||||
|
||||
<router-view @close="$router.push('/')" />
|
||||
|
||||
<SplatNetGearDialog
|
||||
v-if="splatNetGearOpen"
|
||||
:merchandises="merchandises"
|
||||
:now="now"
|
||||
@close="$router.push('/')"
|
||||
></SplatNetGearDialog>
|
||||
<SplatfestHistoryDialog
|
||||
v-if="splatfestHistoryOpen"
|
||||
:allFestivals="splatnet.festivals"
|
||||
:now="now"
|
||||
:initialRegion="selectedRegionKey"
|
||||
@close="$router.push('/')"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -179,62 +165,60 @@
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import axios from 'axios';
|
||||
import { mapActions } from 'vuex';
|
||||
import * as regions from '@/common/regions.esm';
|
||||
import { mapGetters, mapActions } from 'vuex';
|
||||
import Dropdown from './Dropdown.vue';
|
||||
import ScheduleBox from './splatoon/ScheduleBox.vue';
|
||||
import SalmonRunBox from './splatoon/SalmonRunBox.vue';
|
||||
import SplatfestBox from './splatoon/SplatfestBox.vue';
|
||||
import NewWeaponBox from './splatoon/NewWeaponBox.vue';
|
||||
import AboutDialog from './AboutDialog.vue';
|
||||
import SplatNetGearDialog from './splatoon/SplatNetGearDialog.vue';
|
||||
import SplatfestHistoryDialog from './splatoon/SplatfestHistoryDialog.vue';
|
||||
|
||||
const localStorage = window.localStorage;
|
||||
|
||||
export default {
|
||||
components: { Dropdown, ScheduleBox, SalmonRunBox, SplatfestBox, NewWeaponBox, AboutDialog, SplatNetGearDialog, SplatfestHistoryDialog },
|
||||
data() {
|
||||
return {
|
||||
actualSelectedRegionKey: null,
|
||||
actualSelectedLanguageKey: null,
|
||||
now: null,
|
||||
splatnet: {
|
||||
schedules: null,
|
||||
coopSchedules: null,
|
||||
timeline: null,
|
||||
merchandises: null,
|
||||
festivals: null,
|
||||
},
|
||||
language: null,
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
language(value) {
|
||||
if (value)
|
||||
this.loadLocale(value);
|
||||
this.$i18n.set(value || 'en');
|
||||
},
|
||||
},
|
||||
components: { Dropdown, ScheduleBox, SalmonRunBox, SplatfestBox, NewWeaponBox },
|
||||
computed: {
|
||||
loading() { return !this.splatnet.schedules; },
|
||||
...mapGetters('splatoon/regions', ['selectedRegion']),
|
||||
...mapGetters('splatoon/languages', ['selectedLanguage']),
|
||||
...mapGetters('splatoon/splatfests', [
|
||||
'anyRegionHasCurrentSplatfest',
|
||||
'selectedRegionCurrentSplatfest',
|
||||
'selectedRegionHasActiveSplatfest',
|
||||
]),
|
||||
...mapGetters('splatoon/newWeapons', ['weapons']),
|
||||
|
||||
loading() { return !this.$store.state.splatoon.data.schedules },
|
||||
|
||||
hasMerchandises() {
|
||||
let merchandises = this.$store.getters['splatoon/splatNetStore/merchandises'];
|
||||
return merchandises && merchandises.length;
|
||||
},
|
||||
|
||||
hasSplatfests() {
|
||||
return this.$store.state.splatoon.data.festivals;
|
||||
},
|
||||
|
||||
hasSalmonRun() {
|
||||
let schedules = this.$store.getters['splatoon/salmonRun/currentSchedules'];
|
||||
return schedules && schedules.length;
|
||||
},
|
||||
|
||||
hasWeapons() {
|
||||
return this.weapons && this.weapons.length;
|
||||
},
|
||||
|
||||
// Selected region
|
||||
regions() {
|
||||
return regions.splatoonRegions.map(({ key }) => {
|
||||
return this.$store.state.splatoon.regions.all.map(({ key }) => {
|
||||
let name = (key) ? this.$t(`regions.${key}.name`) : this.$t('regions.global.name');
|
||||
return { key, name };
|
||||
});
|
||||
},
|
||||
selectedRegionKey: {
|
||||
get() { return this.actualSelectedRegionKey; },
|
||||
set(value) { this.setRegion(value); },
|
||||
get() { return this.selectedRegion && this.selectedRegion.key; },
|
||||
set(value) { this.setRegion({ region: { key: value } }); },
|
||||
},
|
||||
|
||||
// Selected langauge
|
||||
languages() {
|
||||
return regions.languages
|
||||
return this.$store.state.splatoon.languages.all
|
||||
.reduce((languages, languageInfo) => {
|
||||
// Remove duplicates
|
||||
if (!languages.find(li => li.language == languageInfo.language))
|
||||
@@ -244,205 +228,38 @@ export default {
|
||||
.map(({ language, name }) => ({ key: language, name }));
|
||||
},
|
||||
selectedLanguageKey: {
|
||||
get() { return this.language; },
|
||||
set(value) { this.setLanguage(value); },
|
||||
get() { return this.selectedLanguage && this.selectedLanguage.language; },
|
||||
set(value) { this.setLanguage({ language: { language: value } }); },
|
||||
},
|
||||
|
||||
// Routes (temporary)
|
||||
splatNetGearOpen() {
|
||||
return this.$route.path === '/splatnet';
|
||||
},
|
||||
splatfestHistoryOpen() {
|
||||
return this.$route.path === '/splatfests';
|
||||
},
|
||||
|
||||
// Normal battles
|
||||
regular() { return !this.loading && this.splatnet.schedules.regular.filter(this.filterEndTime) },
|
||||
ranked() { return !this.loading && this.splatnet.schedules.gachi.filter(this.filterEndTime) },
|
||||
league() { return !this.loading && this.splatnet.schedules.league.filter(this.filterEndTime) },
|
||||
|
||||
// Festivals
|
||||
festivals() {
|
||||
if (this.splatnet.festivals) {
|
||||
return {
|
||||
na: (this.splatnet.festivals.na.festivals || []).filter(this.filterFestivals),
|
||||
eu: (this.splatnet.festivals.eu.festivals || []).filter(this.filterFestivals),
|
||||
jp: (this.splatnet.festivals.jp.festivals || []).filter(this.filterFestivals),
|
||||
};
|
||||
}
|
||||
},
|
||||
showFestivalRegionDropdown() {
|
||||
if (this.festivals)
|
||||
return Object.values(this.festivals).some(arr => arr.length > 0);
|
||||
},
|
||||
selectedFestival() {
|
||||
if (this.festivals && this.selectedRegionKey)
|
||||
return this.festivals[this.selectedRegionKey][0];
|
||||
},
|
||||
selectedFestivalResults() {
|
||||
if (this.selectedFestival)
|
||||
return this.splatnet.festivals[this.selectedRegionKey].results.find(r => r.festival_id == this.selectedFestival.festival_id);
|
||||
},
|
||||
isSelectedFestivalActive() {
|
||||
return this.selectedFestival && this.selectedFestival.times.start <= this.now && this.selectedFestival.times.end > this.now;
|
||||
},
|
||||
|
||||
// Salmon Run
|
||||
coop() {
|
||||
if (this.splatnet.timeline && this.splatnet.timeline.coop && this.splatnet.timeline.coop.schedule.end_time > this.now)
|
||||
return this.splatnet.timeline.coop;
|
||||
},
|
||||
coopSchedules() {
|
||||
if (this.splatnet.coopSchedules) {
|
||||
let details = this.splatnet.coopSchedules.details.filter(this.filterEndTime);
|
||||
let schedules = this.splatnet.coopSchedules.schedules.filter(this.filterEndTime);
|
||||
return { details, schedules };
|
||||
}
|
||||
},
|
||||
|
||||
// New weapons
|
||||
newWeapons() {
|
||||
if (this.splatnet.timeline && this.splatnet.timeline.weapon_availability && this.splatnet.timeline.weapon_availability.availabilities) {
|
||||
let weapons = this.splatnet.timeline.weapon_availability.availabilities
|
||||
.filter(a => a.release_time <= this.now)
|
||||
.map(a => a.weapon);
|
||||
|
||||
if (weapons.length > 0)
|
||||
return weapons;
|
||||
}
|
||||
},
|
||||
|
||||
// SplatNet Merchandise
|
||||
merchandises() {
|
||||
if (this.splatnet.merchandises && this.splatnet.merchandises.merchandises)
|
||||
return this.splatnet.merchandises.merchandises.filter(this.filterEndTime);
|
||||
return this.anyRegionHasCurrentSplatfest;
|
||||
},
|
||||
|
||||
// Other
|
||||
bottomRowHasThreeColumns() {
|
||||
return this.selectedFestival && !this.isSelectedFestivalActive && (this.coop || this.coopSchedules) && this.newWeapons;
|
||||
return this.selectedRegionCurrentSplatfest
|
||||
&& !this.selectedRegionHasActiveSplatfest
|
||||
&& this.hasSalmonRun
|
||||
&& this.hasWeapons;
|
||||
},
|
||||
},
|
||||
created() {
|
||||
this.loadLanguage();
|
||||
this.loadRegion(true);
|
||||
window.addEventListener('storage', this.loadLanguage);
|
||||
window.addEventListener('storage', this.loadRegion);
|
||||
|
||||
this.updateNow();
|
||||
this.updateNowTimer = setInterval(() => {
|
||||
this.updateNow();
|
||||
}, 200);
|
||||
|
||||
// Periodically retrieve updated data
|
||||
this.scheduleUpdateData();
|
||||
this.updateData();
|
||||
this.initialize();
|
||||
this.startUpdatingNow();
|
||||
this.startUpdatingData();
|
||||
},
|
||||
beforeDestroy() {
|
||||
clearInterval(this.updateNowTimer);
|
||||
clearInterval(this.updateDataTimer);
|
||||
window.removeEventListener('storage', this.loadLanguage);
|
||||
window.removeEventListener('storage', this.loadRegion);
|
||||
this.stopUpdatingNow();
|
||||
this.stopUpdatingData();
|
||||
this.shutdown();
|
||||
},
|
||||
methods: {
|
||||
...mapActions(['loadLocale']),
|
||||
loadLanguage() {
|
||||
let language = localStorage.getItem('selected-language');
|
||||
let languageInfo = regions.languages.find(li => li.language === language);
|
||||
if (!languageInfo)
|
||||
language = regions.detectSplatoonLanguage();
|
||||
this.language = language;
|
||||
},
|
||||
setLanguage(key) {
|
||||
this.language = key;
|
||||
localStorage.setItem('selected-language', key);
|
||||
this.updateLanguageData();
|
||||
},
|
||||
loadRegion(autoDetect = false) {
|
||||
// Get the previously-selected region from local storage
|
||||
let key = localStorage.getItem('selected-region');
|
||||
if (key !== null) {
|
||||
let region = regions.getRegionByKey(key);
|
||||
this.actualSelectedRegionKey = (region) ? region.key : null;
|
||||
return;
|
||||
}
|
||||
|
||||
// If no region was previously selected, attempt to detect the region by the browser's language
|
||||
if (autoDetect)
|
||||
this.actualSelectedRegionKey = regions.detectSplatoonRegion();
|
||||
},
|
||||
setRegion(key) {
|
||||
let region = regions.getRegionByKey(key);
|
||||
key = (region) ? region.key : null;
|
||||
this.actualSelectedRegionKey = key;
|
||||
localStorage.setItem('selected-region', key);
|
||||
},
|
||||
scheduleUpdateData() {
|
||||
let date = new Date;
|
||||
|
||||
// If we're more than 20 seconds past the current hour, schedule the update for the next hour
|
||||
if (date.getMinutes() !== 0 || date.getSeconds() >= 20)
|
||||
date.setHours(date.getHours() + 1);
|
||||
date.setMinutes(0);
|
||||
|
||||
// Random number of seconds past the hour (so all open browsers don't hit the server at the same time)
|
||||
let minSec = 25;
|
||||
let maxSec = 60;
|
||||
date.setSeconds(Math.floor(Math.random() * (maxSec - minSec + 1)) + minSec);
|
||||
|
||||
// Set the timeout
|
||||
this.updateDataTimer = setTimeout(() => {
|
||||
this.updateData();
|
||||
this.scheduleUpdateData();
|
||||
}, (date - new Date));
|
||||
},
|
||||
updateLanguageData() {
|
||||
if (this.language) {
|
||||
axios.get(`/data/locale/${this.language}.json`)
|
||||
.then(response => this.$i18n.add(this.language, { splatnet: response.data }))
|
||||
.catch(e => console.error(e));
|
||||
}
|
||||
},
|
||||
updateData() {
|
||||
this.updateLanguageData();
|
||||
|
||||
// Main map schedules
|
||||
axios.get('/data/schedules.json')
|
||||
.then(response => this.splatnet.schedules = response.data)
|
||||
.catch(e => console.error(e));
|
||||
|
||||
// Co-op schedules
|
||||
axios.get('/data/coop-schedules.json')
|
||||
.then(response => this.splatnet.coopSchedules = response.data)
|
||||
.catch(e => console.error(e));
|
||||
|
||||
// Timeline data (Salmon Run weapon of the month and new weapon availability)
|
||||
axios.get('/data/timeline.json')
|
||||
.then(response => this.splatnet.timeline = response.data)
|
||||
.catch(e => console.error(e));
|
||||
|
||||
// Splatfest data
|
||||
axios.get('/data/festivals.json')
|
||||
.then(response => this.splatnet.festivals = response.data)
|
||||
.catch(e => console.error(e));
|
||||
|
||||
// Splatnet merchandise data
|
||||
axios.get('/data/merchandises.json')
|
||||
.then(response => this.splatnet.merchandises = response.data)
|
||||
.catch(e => console.error(e));
|
||||
},
|
||||
updateNow() {
|
||||
this.now = Math.trunc((new Date).getTime() / 1000);
|
||||
},
|
||||
filterEndTime(item) {
|
||||
return item.end_time > this.now;
|
||||
},
|
||||
filterFestivals(item) {
|
||||
// We want to show future/current festivals and festivals that ended recently (so we can show their results)
|
||||
// Filter based on the result time and show festival results for 2 days
|
||||
let cutoff = item.times.result + 86400 * 3; // 3 days
|
||||
return cutoff > this.now;
|
||||
},
|
||||
...mapActions('splatoon', ['startUpdatingNow', 'stopUpdatingNow']),
|
||||
...mapActions('splatoon/settings', ['initialize', 'shutdown']),
|
||||
...mapActions('splatoon/data', ['startUpdatingData', 'stopUpdatingData']),
|
||||
...mapActions('splatoon/regions', ['setRegion']),
|
||||
...mapActions('splatoon/languages', ['setLanguage']),
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -3,3 +3,24 @@
|
||||
<router-view></router-view>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapActions } from 'vuex';
|
||||
|
||||
export default {
|
||||
watch: {
|
||||
'$route': {
|
||||
handler: 'loadData',
|
||||
immediate: true,
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
...mapActions('splatoon', ['setNow']),
|
||||
...mapActions('splatoon/data', ['updateAll']),
|
||||
loadData() {
|
||||
this.setNow({ now: this.$route.params.now || 0 });
|
||||
this.updateAll();
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
|
||||
<div v-if="latestGear">
|
||||
<strong>
|
||||
<router-link :to="`/splatNetGear/${topOfHour}/${latestGear.end_time}`">
|
||||
<router-link :to="`/splatNetGear/${latestGearTime}`">
|
||||
SplatNet Gear
|
||||
</router-link>
|
||||
</strong>
|
||||
@@ -48,7 +48,7 @@
|
||||
<router-link
|
||||
v-for="splatfest in splatfests"
|
||||
:key="splatfest.region"
|
||||
:to="`/splatfest/${splatfest.region}/${splatfest.festival.times.start}`">
|
||||
:to="`/splatfest/${splatfest.region}/${splatfest.time}`">
|
||||
{{ splatfest.region }}
|
||||
</router-link>
|
||||
</strong>
|
||||
@@ -59,72 +59,40 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import axios from 'axios';
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
schedules: null,
|
||||
coopSchedules: null,
|
||||
merchandises: null,
|
||||
timeline: null,
|
||||
festivals: null,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
topOfHour() {
|
||||
let date = new Date;
|
||||
date.setUTCMinutes(0);
|
||||
date.setUTCSeconds(0);
|
||||
return Math.floor(date.getTime() / 1000);
|
||||
},
|
||||
latestSchedule() {
|
||||
if (this.schedules)
|
||||
return this.schedules.regular[0];
|
||||
return this.$store.state.splatoon.data.schedules
|
||||
&& this.$store.state.splatoon.data.schedules.regular[0];
|
||||
},
|
||||
latestGear() {
|
||||
if (this.merchandises)
|
||||
return this.merchandises[this.merchandises.length - 1];
|
||||
return this.$store.state.splatoon.data.merchandises
|
||||
&& this.$store.state.splatoon.data.merchandises.merchandises[0];
|
||||
},
|
||||
latestGearTime() {
|
||||
return this.latestGear && this.latestGear.end_time - (2 * 60 * 60); // 2 hours
|
||||
},
|
||||
latestSalmonRun() {
|
||||
if (this.coopSchedules)
|
||||
return this.coopSchedules.details[0];
|
||||
return this.$store.state.splatoon.data.coop_schedules
|
||||
&& this.$store.state.splatoon.data.coop_schedules.schedules[0];
|
||||
},
|
||||
salmonRunGear() {
|
||||
if (this.timeline)
|
||||
return this.timeline.coop.reward_gear;
|
||||
return this.$store.state.splatoon.data.timeline
|
||||
&& this.$store.state.splatoon.data.timeline.coop.reward_gear;
|
||||
},
|
||||
newWeaponAvailability() {
|
||||
if (this.timeline && this.timeline.weapon_availability && this.timeline.weapon_availability.availabilities) {
|
||||
return this.timeline.weapon_availability.availabilities[0];
|
||||
}
|
||||
return this.$store.state.splatoon.data.timeline
|
||||
&& this.$store.state.splatoon.data.timeline.weapon_availability
|
||||
&& this.$store.state.splatoon.data.timeline.weapon_availability.availabilities[0];
|
||||
},
|
||||
splatfests() {
|
||||
if (this.festivals) {
|
||||
let splatfests = [];
|
||||
|
||||
for (let region in this.festivals) {
|
||||
let festival = this.festivals[region].festivals[0];
|
||||
if (festival)
|
||||
splatfests.push({ region, festival });
|
||||
}
|
||||
|
||||
if (splatfests.length)
|
||||
return splatfests;
|
||||
if (this.$store.state.splatoon.data.festivals) {
|
||||
return ['na', 'eu', 'jp'].map(region => ({
|
||||
region,
|
||||
time: this.$store.state.splatoon.data.festivals[region].festivals[0].times.start,
|
||||
}));
|
||||
}
|
||||
},
|
||||
},
|
||||
created() {
|
||||
axios.get('data/schedules.json')
|
||||
.then(response => this.schedules = response.data);
|
||||
axios.get('data/coop-schedules.json')
|
||||
.then(response => this.coopSchedules = response.data);
|
||||
axios.get('data/merchandises.json')
|
||||
.then(response => this.merchandises = response.data.merchandises);
|
||||
axios.get('data/timeline.json')
|
||||
.then(response => this.timeline = response.data);
|
||||
axios.get('data/festivals.json')
|
||||
.then(response => this.festivals = response.data);
|
||||
},
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -30,15 +30,15 @@
|
||||
<div class="font-splatoon2 is-size-7 has-text-grey-light time-table">
|
||||
<table>
|
||||
<tr>
|
||||
<td colspan="3" class="is-size-6 has-text-white">{{ time | dateTime('en', 'UTC', 'MMMM D, YYYY HH:mm z') }}</td>
|
||||
<td colspan="3" class="is-size-6 has-text-white">{{ now | dateTime('en', 'UTC', 'MMMM D, YYYY HH:mm z') }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ time | dateTime('en-us', 'America/Los_Angeles') }}</td>
|
||||
<td>{{ time | dateTime('en-gb', 'Europe/London') }}</td>
|
||||
<td>{{ now | dateTime('en-us', 'America/Los_Angeles') }}</td>
|
||||
<td>{{ now | dateTime('en-gb', 'Europe/London') }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ time | dateTime('en-us', 'America/New_York') }}</td>
|
||||
<td>{{ time | dateTime('ja-jp', 'Asia/Tokyo') }}</td>
|
||||
<td>{{ now | dateTime('en-us', 'America/New_York') }}</td>
|
||||
<td>{{ now | dateTime('ja-jp', 'Asia/Tokyo') }}</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
@@ -51,10 +51,11 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters } from 'vuex';
|
||||
import moment from 'moment-timezone';
|
||||
|
||||
export default {
|
||||
props: ['title', 'time'],
|
||||
props: ['title'],
|
||||
filters: {
|
||||
dateTime(value, locale, timeZone, format = 'LLL z') {
|
||||
let date = moment.unix(value).tz(timeZone);
|
||||
@@ -62,5 +63,8 @@ export default {
|
||||
return date.format(format);
|
||||
},
|
||||
},
|
||||
}
|
||||
computed: {
|
||||
...mapGetters('splatoon', ['now']),
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
<template>
|
||||
<Wrapper :title="title" :time="releaseTime">
|
||||
<div :class="{ 'new-weapons-large': newWeapons.length <= 3 }" v-if="newWeapons">
|
||||
<Wrapper :title="title">
|
||||
<div :class="{ 'new-weapons-large': weapons.length <= 3 }" v-if="weapons">
|
||||
<div style="display: flex; align-items: center; justify-content: center;">
|
||||
<div v-for="(weapon, index) in newWeapons" style="margin: 0 20px">
|
||||
<div v-for="(weapon, index) in weapons" style="margin: 0 20px" :key="index">
|
||||
<NewWeaponBox
|
||||
:weapon="weapon"
|
||||
:class="(index % 2 == 0) ? 'tilt-left' : 'tilt-right'"
|
||||
title="Now Available"
|
||||
></NewWeaponBox>
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -15,38 +15,19 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import axios from 'axios';
|
||||
import { mapGetters } from 'vuex';
|
||||
import Wrapper from '@/web/components/screenshots/Wrapper.vue';
|
||||
import NewWeaponBox from '@/web/components/splatoon/NewWeaponBox.vue';
|
||||
|
||||
export default {
|
||||
components: { Wrapper, NewWeaponBox },
|
||||
props: ['releaseTime'],
|
||||
data() {
|
||||
return {
|
||||
timeline: null,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
newWeapons() {
|
||||
if (this.timeline && this.timeline.weapon_availability && this.timeline.weapon_availability.availabilities) {
|
||||
let weapons = this.timeline.weapon_availability.availabilities
|
||||
.filter(a => a.release_time == this.releaseTime)
|
||||
.map(a => a.weapon);
|
||||
|
||||
if (weapons.length > 0)
|
||||
return weapons;
|
||||
}
|
||||
},
|
||||
...mapGetters('splatoon/newWeapons', ['weapons']),
|
||||
title() {
|
||||
if (this.newWeapons && this.newWeapons.length == 1)
|
||||
if (this.weapons && this.weapons.length == 1)
|
||||
return 'New Weapon';
|
||||
return 'New Weapons';
|
||||
},
|
||||
},
|
||||
created() {
|
||||
axios.get('data/timeline.json')
|
||||
.then(response => this.timeline = response.data)
|
||||
},
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -1,54 +1,18 @@
|
||||
<template>
|
||||
<Wrapper title="Salmon Run" :time="now">
|
||||
<Wrapper title="Salmon Run">
|
||||
<div class="salmon-run tilt-left">
|
||||
<div class="hook-box">
|
||||
<SalmonRunBox
|
||||
v-if="coopSchedules"
|
||||
:coop="coop"
|
||||
:coopSchedules="filteredCoopSchedules"
|
||||
:now="now"
|
||||
:screenshotMode="true"
|
||||
></SalmonRunBox>
|
||||
<SalmonRunBox screenshot-mode />
|
||||
</div>
|
||||
</div>
|
||||
</Wrapper>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import axios from 'axios';
|
||||
import Wrapper from '@/web/components/screenshots/Wrapper.vue';
|
||||
import SalmonRunBox from '@/web/components/splatoon/SalmonRunBox.vue';
|
||||
|
||||
export default {
|
||||
components: { Wrapper, SalmonRunBox },
|
||||
props: ['startTime'],
|
||||
data() {
|
||||
return {
|
||||
coopSchedules: null,
|
||||
timeline: null,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
now() {
|
||||
return this.startTime;
|
||||
},
|
||||
coop() {
|
||||
if (this.timeline && this.timeline.coop && this.timeline.coop.schedule.end_time > this.now)
|
||||
return this.timeline.coop;
|
||||
},
|
||||
filteredCoopSchedules() {
|
||||
if (this.coopSchedules) {
|
||||
let details = this.coopSchedules.details.filter(s => s.start_time == this.now);
|
||||
let schedules = this.coopSchedules.schedules.filter(s => s.start_time == this.now);
|
||||
return { details, schedules };
|
||||
}
|
||||
},
|
||||
},
|
||||
created() {
|
||||
axios.get('data/coop-schedules.json')
|
||||
.then(response => this.coopSchedules = response.data)
|
||||
axios.get('data/timeline.json')
|
||||
.then(response => this.timeline = response.data)
|
||||
},
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -1,33 +1,23 @@
|
||||
<template>
|
||||
<Wrapper title="Salmon Run Gear" :time="now">
|
||||
<div class="tilt-left salmon-run-gear" style="display: flex; align-items: center; justify-content: center;" v-if="merchandise">
|
||||
<Wrapper title="Salmon Run Gear">
|
||||
<div class="tilt-left salmon-run-gear" style="display: flex; align-items: center; justify-content: center;" v-if="rewardGear">
|
||||
<MerchandiseBox :merchandise="merchandise" class="salmon-run-gear" />
|
||||
</div>
|
||||
</Wrapper>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import axios from 'axios';
|
||||
import { mapGetters } from 'vuex';
|
||||
import Wrapper from '@/web/components/screenshots/Wrapper.vue';
|
||||
import MerchandiseBox from '@/web/components/splatoon/MerchandiseBox.vue';
|
||||
|
||||
export default {
|
||||
components: { Wrapper, MerchandiseBox },
|
||||
props: ['now'],
|
||||
data() {
|
||||
return {
|
||||
timeline: null,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapGetters('splatoon/salmonRun', ['rewardGear']),
|
||||
merchandise() {
|
||||
if (this.timeline)
|
||||
return { gear: this.timeline.coop.reward_gear.gear };
|
||||
return { gear: this.rewardGear };
|
||||
},
|
||||
},
|
||||
created() {
|
||||
axios.get('data/timeline.json')
|
||||
.then(response => this.timeline = response.data)
|
||||
},
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div v-if="schedule">
|
||||
<div v-if="schedule" :class="mode">
|
||||
<h3 class="title is-3 font-splatoon1 has-text-centered">
|
||||
{{ schedule.game_mode.name }}
|
||||
</h3>
|
||||
@@ -24,6 +24,11 @@ import Stage from '@/web/components/splatoon/Stage.vue';
|
||||
|
||||
export default {
|
||||
components: { Stage },
|
||||
props: ['schedule'],
|
||||
props: ['mode'],
|
||||
computed: {
|
||||
schedule() {
|
||||
return this.$store.getters[`splatoon/schedules/${this.mode}/activeSchedule`];
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -1,51 +1,26 @@
|
||||
<template>
|
||||
<Wrapper title="Map Schedules" :time="startTime">
|
||||
<Wrapper title="Map Schedules">
|
||||
<div class="columns font-splatoon2">
|
||||
<div class="column">
|
||||
<ScheduleBox class="main-schedule-box tilt-left regular" :schedule="regular"></ScheduleBox>
|
||||
<ScheduleBox class="main-schedule-box tilt-left" mode="regular" />
|
||||
</div>
|
||||
|
||||
<div class="column">
|
||||
<ScheduleBox class="main-schedule-box tilt-right ranked" :schedule="ranked"></ScheduleBox>
|
||||
<ScheduleBox class="main-schedule-box tilt-right" mode="ranked" />
|
||||
</div>
|
||||
|
||||
<div class="column">
|
||||
<ScheduleBox class="main-schedule-box tilt-left league" :schedule="league"></ScheduleBox>
|
||||
<ScheduleBox class="main-schedule-box tilt-left" mode="league" />
|
||||
</div>
|
||||
</div>
|
||||
</Wrapper>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import axios from 'axios';
|
||||
import Wrapper from '@/web/components/screenshots/Wrapper.vue';
|
||||
import ScheduleBox from './ScheduleBox.vue';
|
||||
|
||||
export default {
|
||||
components: { Wrapper, ScheduleBox },
|
||||
props: ['startTime'],
|
||||
data() {
|
||||
return {
|
||||
schedules: null,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
regular() {
|
||||
if (this.schedules)
|
||||
return this.schedules.regular.find(s => s.start_time == this.startTime);
|
||||
},
|
||||
ranked() {
|
||||
if (this.schedules)
|
||||
return this.schedules.gachi.find(s => s.start_time == this.startTime);
|
||||
},
|
||||
league() {
|
||||
if (this.schedules)
|
||||
return this.schedules.league.find(s => s.start_time == this.startTime);
|
||||
},
|
||||
},
|
||||
created() {
|
||||
axios.get('data/schedules.json')
|
||||
.then(response => this.schedules = response.data)
|
||||
},
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -1,23 +1,22 @@
|
||||
<template>
|
||||
<Wrapper :title="title" :time="now">
|
||||
<Wrapper :title="title" v-if="festival">
|
||||
<div class="level">
|
||||
<div class="level-item">
|
||||
<div class="splatfest tilt-left" v-if="festival">
|
||||
<div class="splatfest tilt-left">
|
||||
<div class="hook-box">
|
||||
<SplatfestBox :festival="festival" :now="now" :results="results" :screenshotMode="true" />
|
||||
<SplatfestBox :festival="festival" screenshot-mode />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="level-item" v-if="results">
|
||||
<SplatfestResultsBox :festival="festival" :results="results" />
|
||||
<div class="level-item" v-if="festival.results">
|
||||
<SplatfestResultsBox :festival="festival" />
|
||||
</div>
|
||||
</div>
|
||||
</Wrapper>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import axios from 'axios';
|
||||
import Wrapper from '@/web/components/screenshots/Wrapper.vue';
|
||||
import SplatfestBox from '@/web/components/splatoon/SplatfestBox.vue';
|
||||
import SplatfestResultsBox from '@/web/components/splatoon/SplatfestResultsBox.vue';
|
||||
@@ -25,15 +24,10 @@ import { splatoonRegions } from '@/common/regions.esm';
|
||||
|
||||
export default {
|
||||
components: { Wrapper, SplatfestBox, SplatfestResultsBox },
|
||||
props: ['region', 'startTime'],
|
||||
data() {
|
||||
return {
|
||||
festivals: null,
|
||||
};
|
||||
},
|
||||
props: ['region'],
|
||||
computed: {
|
||||
now() {
|
||||
return this.startTime;
|
||||
festival() {
|
||||
return this.$store.getters[`splatoon/splatfests/${this.region}/currentSplatfest`];
|
||||
},
|
||||
regions() {
|
||||
let regions = this.$route.query.regions;
|
||||
@@ -52,23 +46,12 @@ export default {
|
||||
title() {
|
||||
return `${this.displayRegions} Splatfest`;
|
||||
},
|
||||
festival() {
|
||||
if (this.festivals)
|
||||
return this.festivals[this.region].festivals.find(f => f.times.announce <= this.now && f.times.result >= this.now);
|
||||
},
|
||||
results() {
|
||||
if (this.festival)
|
||||
return this.festivals[this.region].results.find(r => r.festival_id == this.festival.festival_id);
|
||||
},
|
||||
},
|
||||
created() {
|
||||
if (this.region == 'jp') {
|
||||
if (this.region === 'jp') {
|
||||
this.$i18n.add('ja', require('@/web/locale/ja'));
|
||||
this.$i18n.set('ja');
|
||||
}
|
||||
|
||||
axios.get('data/festivals.json')
|
||||
.then(response => this.festivals = response.data)
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<Wrapper title="SplatNet Gear" :time="startTime" v-if="merchandises">
|
||||
<Wrapper title="SplatNet Gear" v-if="merchandises">
|
||||
<div class="columns">
|
||||
<div class="column is-5">
|
||||
<h1 class="title is-3 font-splatoon2 has-text-centered">New Gear</h1>
|
||||
@@ -13,19 +13,17 @@
|
||||
<div class="column is-5" style="min-height: 560px; display: flex; align-items: center; justify-content: center;">
|
||||
<MerchandiseBox
|
||||
:merchandise="featuredMerchandise"
|
||||
:now="startTime"
|
||||
class="featured-merchandise"
|
||||
></MerchandiseBox>
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="column" style="display: flex; align-items: center; justify-content: center;">
|
||||
<div class="columns is-multiline is-centered">
|
||||
<div class="column is-one-third" v-for="(merchandise, index) in otherMerchandises">
|
||||
<div class="column is-one-third" v-for="(merchandise, index) in otherMerchandises" :key="index">
|
||||
<MerchandiseBox
|
||||
:merchandise="merchandise"
|
||||
:now="startTime"
|
||||
:class="(index % 2 == 0) ? 'tilt-right' : 'tilt-left'"
|
||||
></MerchandiseBox>
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -34,34 +32,22 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import axios from 'axios';
|
||||
import Wrapper from '@/web/components/screenshots/Wrapper.vue';
|
||||
import MerchandiseBox from '@/web/components/splatoon/MerchandiseBox.vue';
|
||||
|
||||
export default {
|
||||
components: { Wrapper, MerchandiseBox },
|
||||
props: ['startTime', 'endTime'],
|
||||
data() {
|
||||
return {
|
||||
merchandises: null,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
merchandises() {
|
||||
return this.$store.getters['splatoon/splatNetStore/merchandises']
|
||||
&& this.$store.getters['splatoon/splatNetStore/merchandises'].slice().reverse();
|
||||
},
|
||||
featuredMerchandise() {
|
||||
if (this.merchandises)
|
||||
return this.merchandises.find(m => m.end_time == this.endTime);
|
||||
return this.merchandises && this.merchandises[0];
|
||||
},
|
||||
otherMerchandises() {
|
||||
if (this.merchandises) {
|
||||
return this.merchandises
|
||||
.filter(m => m != this.featuredMerchandise && m.end_time > this.startTime)
|
||||
.sort((a, b) => b.end_time - a.end_time);
|
||||
}
|
||||
return this.merchandises && this.merchandises.slice(1);
|
||||
},
|
||||
},
|
||||
created() {
|
||||
axios.get('data/merchandises.json')
|
||||
.then(response => this.merchandises = response.data.merchandises)
|
||||
},
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -3,9 +3,9 @@
|
||||
<div class="level-left">
|
||||
<div class="level-item">
|
||||
<div class="image is-48x48" style="margin-bottom: -6px">
|
||||
<img v-if="gameMode.key == 'regular'" src="~@/web/assets/img/battle-regular.png" />
|
||||
<img v-if="gameMode.key == 'gachi'" src="~@/web/assets/img/battle-ranked.png" />
|
||||
<img v-if="gameMode.key == 'league'" src="~@/web/assets/img/battle-league.png" />
|
||||
<img v-if="mode === 'regular'" src="~@/web/assets/img/battle-regular.png" />
|
||||
<img v-if="mode === 'ranked'" src="~@/web/assets/img/battle-ranked.png" />
|
||||
<img v-if="mode === 'league'" src="~@/web/assets/img/battle-league.png" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="level-item">
|
||||
@@ -18,16 +18,17 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters } from 'vuex';
|
||||
|
||||
export default {
|
||||
props: ['gameMode', 'festival', 'now'],
|
||||
props: ['mode'],
|
||||
computed: {
|
||||
isFestival() {
|
||||
return this.festival && this.festival.times.start <= this.now && this.festival.times.end > this.now;
|
||||
},
|
||||
...mapGetters('splatoon/splatfests', ['selectedRegionHasActiveSplatfest']),
|
||||
name() {
|
||||
if (this.isFestival)
|
||||
if (this.selectedRegionHasActiveSplatfest)
|
||||
return this.$t('splatfest.battle');
|
||||
return this.$t(`splatnet.game_modes.${this.gameMode.key}.name`, this.gameMode.name);
|
||||
|
||||
return this.$t(`game_mode.${this.mode}`);
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<div class="main skill-img-bg" v-if="merchandise.skill">
|
||||
<img :src="merchandise.skill.image | localSplatNetImageUrl" :title="skillName" />
|
||||
</div>
|
||||
<div class="sub" v-for="i in merchandise.gear.rarity + 1">
|
||||
<div class="sub" v-for="i in merchandise.gear.rarity + 1" :key="i">
|
||||
<img src="~@/web/assets/img/blank-skill-slot.png" />
|
||||
</div>
|
||||
</div>
|
||||
@@ -38,7 +38,7 @@
|
||||
<div class="skill-img-bg strikethrough">
|
||||
<img :src="merchandise.original_gear.skill.image | localSplatNetImageUrl" :title="originalSkillName" />
|
||||
</div>
|
||||
<div class="sub" v-for="i in merchandise.original_gear.rarity + 1">
|
||||
<div class="sub" v-for="i in merchandise.original_gear.rarity + 1" :key="i">
|
||||
<img src="~@/web/assets/img/blank-skill-slot.png" />
|
||||
</div>
|
||||
</div>
|
||||
@@ -74,9 +74,12 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters } from 'vuex';
|
||||
|
||||
export default {
|
||||
props: ['merchandise', 'now'],
|
||||
props: ['merchandise'],
|
||||
computed: {
|
||||
...mapGetters('splatoon', ['now']),
|
||||
brandName() {
|
||||
let brand = this.merchandise.gear.brand;
|
||||
return this.$t(`splatnet.brands.${brand.id}.name`, brand.name);
|
||||
|
||||
@@ -21,43 +21,43 @@
|
||||
<div class="column is-4 salmon-run-ad-img">
|
||||
<div class="image is-16by9 is-hidden-desktop"></div>
|
||||
|
||||
<template v-if="!screenshotMode && coop && coop.reward_gear">
|
||||
<template v-if="!screenshotMode && rewardGear">
|
||||
<div class="salmon-run-gear-text">
|
||||
{{ $t('coop.this_months_gear') }}
|
||||
</div>
|
||||
<div class="salmon-run-gear-image hand" @click="gearDialogOpen = true" :title="rewardGearName">
|
||||
<div class="image is-32x32">
|
||||
<img :src="coop.reward_gear.gear.image | localSplatNetImageUrl" />
|
||||
<img :src="rewardGear.image | localSplatNetImageUrl" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<div class="column">
|
||||
<div class="salmon-run-content">
|
||||
<div class="salmon-run-open" v-if="currentSchedule">
|
||||
<div class="salmon-run-open" v-if="activeSchedule">
|
||||
<div class="is-size-5 title-squid font-splatoon1">
|
||||
{{ $t('times.open') }}
|
||||
</div>
|
||||
<template v-if="screenshotMode">
|
||||
<div class="title-color is-size-5">
|
||||
{{ currentSchedule.end_time - now | durationHours | time.remaining }}
|
||||
{{ activeSchedule.end_time - now | durationHours | time.remaining }}
|
||||
</div>
|
||||
</template>
|
||||
<template v-else>
|
||||
<div class="title-color is-size-5">
|
||||
{{ currentSchedule.start_time | date }}
|
||||
{{ currentSchedule.start_time | time }}
|
||||
{{ activeSchedule.start_time | date }}
|
||||
{{ activeSchedule.start_time | time }}
|
||||
–
|
||||
{{ currentSchedule.end_time | date }}
|
||||
{{ currentSchedule.end_time | time }}
|
||||
{{ activeSchedule.end_time | date }}
|
||||
{{ activeSchedule.end_time | time }}
|
||||
</div>
|
||||
<div>
|
||||
{{ currentSchedule.end_time - now | duration | time.remaining }}
|
||||
{{ activeSchedule.end_time - now | duration | time.remaining }}
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div class="salmon-run-details">
|
||||
<SalmonRunDetailsBar :schedule="currentSchedule"></SalmonRunDetailsBar>
|
||||
<SalmonRunDetailsBar :schedule="activeSchedule" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -66,7 +66,7 @@
|
||||
<div class="is-size-6 title-squid font-splatoon1">
|
||||
{{ $t('times.soon') }}
|
||||
</div>
|
||||
<div v-for="event in upcomingSchedules" class="event">
|
||||
<div v-for="event in upcomingSchedules" :key="event.key" class="event">
|
||||
<div class="columns is-marginless is-gapless is-mobile">
|
||||
<div class="column is-narrow" style="margin-right: 5px">
|
||||
<div class="image is-4by3" style="width: 23px">
|
||||
@@ -91,7 +91,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<SalmonRunDetailsBar :schedule="event" :mini="true"></SalmonRunDetailsBar>
|
||||
<SalmonRunDetailsBar :schedule="event" mini />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -100,51 +100,33 @@
|
||||
</div>
|
||||
|
||||
<SalmonRunGearDialog
|
||||
v-if="gearDialogOpen && coop && coop.reward_gear"
|
||||
:gear="coop.reward_gear.gear"
|
||||
v-if="gearDialogOpen && rewardGear"
|
||||
@close="gearDialogOpen = false"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Vue from 'vue';
|
||||
import { mapGetters } from 'vuex';
|
||||
import SalmonRunDetailsBar from './SalmonRunDetailsBar.vue';
|
||||
import SalmonRunGearDialog from './SalmonRunGearDialog.vue';
|
||||
|
||||
export default {
|
||||
components: { SalmonRunDetailsBar, SalmonRunGearDialog },
|
||||
props: ['coop', 'coopSchedules', 'now', 'screenshotMode'],
|
||||
props: {
|
||||
screenshotMode: Boolean,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
gearDialogOpen: false,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
allSchedules() {
|
||||
// Combine the "schedules" and "details" sections into one list of schedules
|
||||
let results = {};
|
||||
|
||||
// Add the less-detailed schedules in first
|
||||
for (let schedule of this.coopSchedules.schedules)
|
||||
results[schedule.start_time] = schedule;
|
||||
|
||||
// Add/replace the schedules that have map/weapon details
|
||||
for (let schedule of this.coopSchedules.details)
|
||||
results[schedule.start_time] = schedule;
|
||||
|
||||
return Object.values(results).sort((a, b) => { return a.start_time - b.start_time });
|
||||
},
|
||||
upcomingSchedules() { return this.allSchedules.filter(schedule => schedule.start_time > this.now) },
|
||||
currentSchedule() {
|
||||
if (this.allSchedules.length > 0 && this.allSchedules[0].start_time <= this.now)
|
||||
return this.allSchedules[0];
|
||||
},
|
||||
...mapGetters('splatoon', ['now']),
|
||||
...mapGetters('splatoon/salmonRun', ['activeSchedule', 'upcomingSchedules', 'rewardGear']),
|
||||
rewardGearName() {
|
||||
if (this.coop.reward_gear) {
|
||||
let gear = this.coop.reward_gear.gear;
|
||||
return this.$t(`splatnet.gear.${gear.id}.name`, gear.name);
|
||||
}
|
||||
if (this.rewardGear)
|
||||
return this.$t(`splatnet.gear.${this.rewardGear.kind}.${this.rewardGear.id}.name`, this.rewardGear.name);
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div class="columns is-mobile is-slimmer" style="padding: 0.5rem 0" v-if="schedule.stage || schedule.weapons">
|
||||
<div class="column" :class="mini ? 'is-3' : 'is-5'" v-if="schedule.stage">
|
||||
<Stage :stage="schedule.stage" :isSalmonRun="true" :showTitle="!mini"></Stage>
|
||||
<Stage :stage="schedule.stage" is-salmon-run :showTitle="!mini"></Stage>
|
||||
</div>
|
||||
|
||||
<div class="column" style="display: flex; align-items: center;" v-if="mini && schedule.stage">
|
||||
@@ -45,7 +45,10 @@ import Stage from './Stage.vue';
|
||||
|
||||
export default {
|
||||
components: { Stage },
|
||||
props: ['schedule', 'mini'],
|
||||
props: {
|
||||
schedule: null,
|
||||
mini: Boolean,
|
||||
},
|
||||
computed: {
|
||||
stageName() {
|
||||
let stage = this.schedule.stage;
|
||||
|
||||
@@ -9,14 +9,17 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters } from 'vuex';
|
||||
import Modal from '@/web/components/Modal.vue';
|
||||
import MerchandiseBox from './MerchandiseBox.vue';
|
||||
|
||||
export default {
|
||||
components: { Modal, MerchandiseBox },
|
||||
props: ['gear'],
|
||||
computed: {
|
||||
merchandise() { return { gear: this.gear }; },
|
||||
...mapGetters('splatoon/salmonRun', ['rewardGear']),
|
||||
merchandise() {
|
||||
return { gear: this.rewardGear };
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -1,25 +1,25 @@
|
||||
<template>
|
||||
<div class="font-splatoon2" :class="cssClass" v-if="gameMode">
|
||||
<GameModeHeader :gameMode="gameMode" :festival="festival" :now="now"></GameModeHeader>
|
||||
<div class="font-splatoon2" :class="mode">
|
||||
<GameModeHeader :mode="mode" />
|
||||
|
||||
<div class="main-schedule" v-if="currentSchedule">
|
||||
<div class="main-schedule" v-if="activeSchedule">
|
||||
<div class="level is-mobile top-bar">
|
||||
<div class="level-left">
|
||||
<div class="level-item title-color is-size-5">{{ currentRuleName }}</div>
|
||||
<div class="level-item title-color is-size-5">{{ activeRuleName }}</div>
|
||||
</div>
|
||||
<div class="level-right">
|
||||
<div class="level-item">
|
||||
{{ currentSchedule.start_time | time }} –
|
||||
{{ currentSchedule.end_time | time }}
|
||||
{{ activeSchedule.start_time | time }} –
|
||||
{{ activeSchedule.end_time | time }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="is-clearfix"></div>
|
||||
<div class="columns is-mobile is-slim">
|
||||
<div class="column"><Stage :stage="currentSchedule.stage_a"></Stage></div>
|
||||
<div class="column"><Stage :stage="currentSchedule.stage_b"></Stage></div>
|
||||
<template v-if="festival">
|
||||
<div class="column"><Stage :stage="festival.special_stage"></Stage></div>
|
||||
<div class="column"><Stage :stage="activeSchedule.stage_a"></Stage></div>
|
||||
<div class="column"><Stage :stage="activeSchedule.stage_b"></Stage></div>
|
||||
<template v-if="selectedRegionHasActiveSplatfest">
|
||||
<div class="column"><Stage :stage="selectedRegionCurrentSplatfest.special_stage"></Stage></div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
@@ -29,11 +29,7 @@
|
||||
{{ $t('times.next') }}
|
||||
</div>
|
||||
|
||||
<ScheduleRow
|
||||
:schedule="upcomingSchedules[0]"
|
||||
:now="now"
|
||||
:smallSize="true"
|
||||
></ScheduleRow>
|
||||
<ScheduleRow :schedule="upcomingSchedules[0]" is-small />
|
||||
</template>
|
||||
|
||||
<p class="has-text-centered" style="margin-top: 10px">
|
||||
@@ -47,16 +43,14 @@
|
||||
|
||||
<ScheduleDialog
|
||||
v-if="dialogOpen"
|
||||
:schedules="schedules"
|
||||
:gameMode="gameMode"
|
||||
:cssClass="cssClass"
|
||||
:now="now"
|
||||
:mode="mode"
|
||||
@close="dialogOpen = false"
|
||||
></ScheduleDialog>
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters } from 'vuex';
|
||||
import GameModeHeader from './GameModeHeader.vue';
|
||||
import Stage from './Stage.vue';
|
||||
import ScheduleRow from './ScheduleRow.vue';
|
||||
@@ -64,32 +58,31 @@ import ScheduleDialog from './ScheduleDialog.vue';
|
||||
|
||||
export default {
|
||||
components: { GameModeHeader, Stage, ScheduleRow, ScheduleDialog },
|
||||
props: ['schedules', 'cssClass', 'festival', 'now'],
|
||||
props: ['mode'],
|
||||
data() {
|
||||
return {
|
||||
dialogOpen: false,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
gameMode() {
|
||||
if (this.schedules && this.schedules[0])
|
||||
return this.schedules[0].game_mode;
|
||||
},
|
||||
currentSchedule() {
|
||||
if (this.schedules && this.schedules[0] && this.schedules[0].start_time <= this.now)
|
||||
return this.schedules[0];
|
||||
},
|
||||
currentRuleName() {
|
||||
let rule = this.currentSchedule.rule;
|
||||
return this.$t(`splatnet.rules.${rule.key}.name`, rule.name);
|
||||
...mapGetters('splatoon', ['now']),
|
||||
...mapGetters('splatoon/splatfests', [
|
||||
'selectedRegionCurrentSplatfest',
|
||||
'selectedRegionHasActiveSplatfest',
|
||||
]),
|
||||
|
||||
activeSchedule() {
|
||||
return this.$store.getters[`splatoon/schedules/${this.mode}/activeSchedule`];
|
||||
},
|
||||
upcomingSchedules() {
|
||||
if (this.schedules) {
|
||||
if (this.currentSchedule)
|
||||
return this.schedules.slice(1);
|
||||
return this.schedules;
|
||||
return this.$store.getters[`splatoon/schedules/${this.mode}/upcomingSchedules`];
|
||||
},
|
||||
activeRuleName() {
|
||||
if (this.activeSchedule) {
|
||||
let rule = this.activeSchedule.rule;
|
||||
return this.$t(`splatnet.rules.${rule.key}.name`, rule.name);
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
<template>
|
||||
<Modal @close="$emit('close')" class="font-splatoon2">
|
||||
<div class="modal-card schedule-box tilt-left-slight" :class="cssClass">
|
||||
<div class="modal-card schedule-box tilt-left-slight" :class="mode">
|
||||
<header class="modal-card-head">
|
||||
<GameModeHeader :gameMode="gameMode"></GameModeHeader>
|
||||
<GameModeHeader :mode="mode" />
|
||||
</header>
|
||||
<section class="modal-card-body">
|
||||
<ScheduleList :schedules="schedules" :now="now"></ScheduleList>
|
||||
<ScheduleList :mode="mode" />
|
||||
</section>
|
||||
</div>
|
||||
</Modal>
|
||||
@@ -19,9 +19,9 @@ import ScheduleList from './ScheduleList.vue';
|
||||
|
||||
export default {
|
||||
components: { Modal, GameModeHeader, ScheduleList },
|
||||
props: ['schedules', 'gameMode', 'cssClass', 'now'],
|
||||
props: ['mode'],
|
||||
mounted() {
|
||||
analytics.event('Schedule', 'Open', this.gameMode.name);
|
||||
analytics.event('Schedule', 'Open', this.mode);
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -2,11 +2,11 @@
|
||||
<div>
|
||||
<div v-if="first">
|
||||
<div class="is-size-5 title-squid font-splatoon1">
|
||||
<span v-if="first.start_time <= this.now">{{ $t('times.now') }}</span>
|
||||
<span v-if="first.start_time <= now">{{ $t('times.now') }}</span>
|
||||
<span v-else>{{ $t('times.soon') }}</span>
|
||||
</div>
|
||||
|
||||
<ScheduleRow :schedule="first" :now="now"></ScheduleRow>
|
||||
<ScheduleRow :schedule="first" />
|
||||
</div>
|
||||
<template>
|
||||
<div v-if="second">
|
||||
@@ -14,7 +14,7 @@
|
||||
{{ $t('times.next') }}
|
||||
</div>
|
||||
|
||||
<ScheduleRow :schedule="second" :now="now"></ScheduleRow>
|
||||
<ScheduleRow :schedule="second" />
|
||||
</div>
|
||||
|
||||
<div v-if="others && others.length">
|
||||
@@ -24,23 +24,26 @@
|
||||
|
||||
<ScheduleRow
|
||||
v-for="schedule in others"
|
||||
:key="schedule.id"
|
||||
:key="schedule.start_time"
|
||||
:schedule="schedule"
|
||||
:now="now"
|
||||
></ScheduleRow>
|
||||
/>
|
||||
</div>
|
||||
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters } from 'vuex';
|
||||
import ScheduleRow from './ScheduleRow.vue';
|
||||
|
||||
export default {
|
||||
components: { ScheduleRow },
|
||||
props: ['schedules', 'now'],
|
||||
props: ['mode'],
|
||||
computed: {
|
||||
...mapGetters('splatoon', ['now']),
|
||||
schedules() {
|
||||
return this.$store.getters[`splatoon/schedules/${this.mode}/currentSchedules`];
|
||||
},
|
||||
first() { return this.schedules && this.schedules[0]; },
|
||||
second() { return this.schedules && this.schedules[1]; },
|
||||
others() { return this.schedules && this.schedules.slice(2); },
|
||||
|
||||
@@ -37,8 +37,8 @@
|
||||
</div>
|
||||
<div class="column is-8">
|
||||
<div class="columns is-mobile is-slim">
|
||||
<div class="column"><Stage :stage="schedule.stage_a"></Stage></div>
|
||||
<div class="column"><Stage :stage="schedule.stage_b"></Stage></div>
|
||||
<div class="column"><Stage :stage="schedule.stage_a" /></div>
|
||||
<div class="column"><Stage :stage="schedule.stage_b" /></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -47,12 +47,17 @@
|
||||
|
||||
<script>
|
||||
import Vue from 'vue';
|
||||
import { mapGetters } from 'vuex';
|
||||
import Stage from './Stage.vue';
|
||||
|
||||
export default {
|
||||
components: { Stage },
|
||||
props: ['schedule', 'now', 'smallSize'],
|
||||
props: {
|
||||
schedule: null,
|
||||
isSmall: Boolean,
|
||||
},
|
||||
computed: {
|
||||
...mapGetters('splatoon', ['now']),
|
||||
isToday() {
|
||||
let now = new Date(this.now * 1000);
|
||||
let start = new Date(this.schedule.start_time * 1000);
|
||||
@@ -62,8 +67,8 @@ export default {
|
||||
let rule = this.schedule.rule;
|
||||
return this.$t(`splatnet.rules.${rule.key}.name`, rule.name);
|
||||
},
|
||||
ruleNameClass() { return this.smallSize ? 'is-6' : 'is-5' },
|
||||
scheduleClass() { return this.smallSize ? 'is-7' : 'is-6' },
|
||||
ruleNameClass() { return this.isSmall ? 'is-6' : 'is-5' },
|
||||
scheduleClass() { return this.isSmall ? 'is-7' : 'is-6' },
|
||||
timeFromNow() {
|
||||
let time = Vue.filter('duration')(this.schedule.start_time - this.now);
|
||||
return this.$t('time.in', { time });
|
||||
|
||||
@@ -4,12 +4,11 @@
|
||||
<div class="splatnet-gear-dialog">
|
||||
<h3 class="title is-3 has-text-centered font-splatoon1 text-shadow">{{ $t('splatnet_gear.title') }}</h3>
|
||||
<div class="columns is-marginless is-multiline is-mobile is-centered">
|
||||
<div class="column is-half-mobile is-one-third-tablet" v-for="(merchandise, index) in merchandises">
|
||||
<div class="column is-half-mobile is-one-third-tablet" v-for="(merchandise, index) in merchandises" :key="merchandise.end_time">
|
||||
<MerchandiseBox
|
||||
:merchandise="merchandise"
|
||||
:now="now"
|
||||
:class="(index % 2 == 0) ? 'tilt-left' : 'tilt-right'"
|
||||
></MerchandiseBox>
|
||||
:class="(index % 2 === 0) ? 'tilt-left' : 'tilt-right'"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -18,13 +17,16 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters } from 'vuex';
|
||||
import analytics from '@/web/support/analytics';
|
||||
import Modal from '@/web/components/Modal.vue';
|
||||
import MerchandiseBox from './MerchandiseBox.vue';
|
||||
|
||||
export default {
|
||||
components: { Modal, MerchandiseBox },
|
||||
props: ['merchandises', 'now'],
|
||||
computed: {
|
||||
...mapGetters('splatoon/splatNetStore', ['merchandises']),
|
||||
},
|
||||
mounted() {
|
||||
analytics.event('SplatNet Gear', 'Open');
|
||||
},
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
<template>
|
||||
<div class="font-splatoon2" :class="{ 'has-results': results && !screenshotMode }">
|
||||
<div class="font-splatoon2" :class="{ 'has-results': showingResults }">
|
||||
<div class="splatfest-header">
|
||||
<h2 class="title is-3 is-size-2-fullhd font-splatoon1">
|
||||
{{ title }}
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<div class="panel-container" :class="{ 'is-hidden-mobile' : results }">
|
||||
<div class="panel-container" :class="{ 'is-hidden-mobile' : festival.results }">
|
||||
<div class="image panel-image">
|
||||
<img :src="image" />
|
||||
</div>
|
||||
@@ -21,15 +21,15 @@
|
||||
<div class="column has-text-right" v-text="teamNames.short.bravo"></div>
|
||||
</div>
|
||||
|
||||
<SplatfestResultsBox :festival="festival" :results="results" v-if="results && !screenshotMode" />
|
||||
<SplatfestResultsBox :festival="festival" v-if="showingResults" />
|
||||
</div>
|
||||
|
||||
<div class="mobile-results is-hidden-tablet" v-if="results">
|
||||
<SplatfestResultsBox :festival="festival" :results="results" v-if="results && !screenshotMode" />
|
||||
<div class="mobile-results is-hidden-tablet" v-if="festival.results">
|
||||
<SplatfestResultsBox :festival="festival" v-if="showingResults" />
|
||||
</div>
|
||||
|
||||
<div class="has-text-centered is-size-5 title-color festival-period-container">
|
||||
<div v-if="!results" class="festival-period" :style="{ 'background-color': festival.colors.middle.css_rgb }">
|
||||
<div v-if="!festival.results" class="festival-period" :style="{ 'background-color': festival.colors.middle.css_rgb }">
|
||||
<template v-if="!screenshotMode">
|
||||
<span class="nowrap">
|
||||
{{ festival.times.start | date(dateOptions) }}
|
||||
@@ -42,10 +42,10 @@
|
||||
</span>
|
||||
</template>
|
||||
<template v-else>
|
||||
<template v-if="state == 'upcoming'">
|
||||
<template v-if="festival.state == 'upcoming'">
|
||||
{{ festival.times.start - now | shortDuration | time.in }}
|
||||
</template>
|
||||
<template v-else-if="state == 'past' && !results && festival.times.result > now">
|
||||
<template v-else-if="festival.state == 'past' && !results && festival.times.result > now">
|
||||
{{ festival.times.result - now | durationHours | resultsIn }}
|
||||
</template>
|
||||
<template v-else>
|
||||
@@ -54,23 +54,19 @@
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<SplatfestWinnerBar
|
||||
v-else
|
||||
:festival="festival"
|
||||
:results="results"
|
||||
/>
|
||||
<SplatfestWinnerBar :festival="festival" v-else />
|
||||
</div>
|
||||
|
||||
<div class="splatfest-content has-text-centered" v-if="!screenshotMode">
|
||||
<template v-if="state == 'upcoming'">
|
||||
<template v-if="festival.state == 'upcoming'">
|
||||
{{ festival.times.start - now | duration | time.in }}
|
||||
</template>
|
||||
|
||||
<template v-else-if="state == 'active'">
|
||||
<template v-else-if="festival.state == 'active'">
|
||||
{{ festival.times.end - now | duration | time.remaining }}
|
||||
</template>
|
||||
|
||||
<template v-else-if="state == 'past' && !results && festival.times.result > now">
|
||||
<template v-else-if="festival.state == 'past' && !festival.results && festival.times.result > now">
|
||||
{{ festival.times.result - now | duration | resultsIn }}
|
||||
</template>
|
||||
</div>
|
||||
@@ -79,24 +75,26 @@
|
||||
|
||||
<script>
|
||||
import Vue from 'vue';
|
||||
import { mapGetters } from 'vuex';
|
||||
import SplatfestResultsBox from './SplatfestResultsBox.vue';
|
||||
import SplatfestWinnerBar from './SplatfestWinnerBar.vue';
|
||||
|
||||
export default {
|
||||
components: { SplatfestResultsBox, SplatfestWinnerBar },
|
||||
props: ['festival', 'results', 'now', 'screenshotMode', 'historyMode'],
|
||||
props: {
|
||||
festival: null,
|
||||
screenshotMode: Boolean,
|
||||
historyMode: Boolean,
|
||||
},
|
||||
filters: {
|
||||
resultsIn(time) {
|
||||
return Vue.i18n.translate('splatfest.results_in', { time });
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
state() {
|
||||
if (this.festival.times.start > this.now)
|
||||
return 'upcoming';
|
||||
if (this.festival.times.end > this.now)
|
||||
return 'active';
|
||||
return 'past';
|
||||
...mapGetters('splatoon', ['now']),
|
||||
showingResults() {
|
||||
return this.festival.results && !this.screenshotMode && !this.historyMode;
|
||||
},
|
||||
dateOptions() {
|
||||
if (this.historyMode)
|
||||
@@ -104,9 +102,9 @@ export default {
|
||||
return { weekday: 'short' };
|
||||
},
|
||||
title() {
|
||||
if (this.state == 'upcoming')
|
||||
if (this.festival.state == 'upcoming')
|
||||
return this.$t('splatfest.upcoming');
|
||||
if (this.state == 'past' && !this.screenshotMode)
|
||||
if (this.festival.state == 'past' && !this.screenshotMode)
|
||||
return this.$t('splatfest.recent');
|
||||
return this.$t('splatfest.title');
|
||||
},
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<Modal @close="$emit('close')" class="is-wide splatfest-history-dialog modal-rounded-box" v-if="allFestivals">
|
||||
<Modal @close="$emit('close')" class="is-wide splatfest-history-dialog modal-rounded-box" v-if="allSplatfests">
|
||||
<div class="modal-card">
|
||||
<div class="modal-card-head">
|
||||
<div class="level">
|
||||
@@ -18,16 +18,12 @@
|
||||
</div>
|
||||
|
||||
<div class="modal-card-body" ref="body">
|
||||
<div v-for="(info, index) in festivals" class="splatfest-history-row" :key="info.festival.festival_id">
|
||||
<div v-for="(festival, index) in festivals" class="splatfest-history-row" :key="festival.festival_id">
|
||||
<div class="level">
|
||||
<div class="level-item">
|
||||
<div class="splatfest" :class="(index % 2 == 0) ? 'tilt-left' : 'tilt-right'">
|
||||
<div class="hook-box">
|
||||
<SplatfestBox
|
||||
:festival="info.festival"
|
||||
:now="now"
|
||||
:historyMode="true"
|
||||
/>
|
||||
<SplatfestBox :festival="festival" history-mode />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -35,14 +31,14 @@
|
||||
<div class="level-item">
|
||||
<div class="splatfest-results-container">
|
||||
<SplatfestResultsBox
|
||||
v-if="info.results"
|
||||
:festival="info.festival"
|
||||
:results="info.results"
|
||||
:historyMode="true"
|
||||
v-if="festival.results"
|
||||
:festival="festival"
|
||||
:results="festival.results"
|
||||
history-mode
|
||||
/>
|
||||
|
||||
<div v-else-if="info.festival.times.result > now" class="has-text-centered font-splatoon2 is-size-5">
|
||||
{{ info.festival.times.result - now | duration | resultsIn }}
|
||||
<div v-else-if="festival.times.result > now" class="has-text-centered font-splatoon2 is-size-5">
|
||||
{{ festival.times.result - now | duration | resultsIn }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -55,8 +51,8 @@
|
||||
|
||||
<script>
|
||||
import Vue from 'vue';
|
||||
import { mapGetters } from 'vuex';
|
||||
import analytics from '@/web/support/analytics';
|
||||
import * as regions from '@/common/regions.esm';
|
||||
import Modal from '@/web/components/Modal.vue';
|
||||
import Dropdown from '@/web/components/Dropdown.vue';
|
||||
import SplatfestBox from './SplatfestBox.vue';
|
||||
@@ -64,7 +60,6 @@ import SplatfestResultsBox from './SplatfestResultsBox.vue';
|
||||
|
||||
export default {
|
||||
components: { Modal, Dropdown, SplatfestBox, SplatfestResultsBox },
|
||||
props: ['allFestivals', 'now', 'initialRegion'],
|
||||
filters: {
|
||||
resultsIn(time) {
|
||||
return Vue.i18n.translate('splatfest.results_in', { time });
|
||||
@@ -76,19 +71,23 @@ export default {
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapGetters('splatoon', ['now']),
|
||||
...mapGetters('splatoon/regions', { initialRegion: 'selectedKey' }),
|
||||
allSplatfests() {
|
||||
return {
|
||||
na: this.$store.getters['splatoon/splatfests/na/allSplatfests'],
|
||||
eu: this.$store.getters['splatoon/splatfests/eu/allSplatfests'],
|
||||
jp: this.$store.getters['splatoon/splatfests/jp/allSplatfests'],
|
||||
};
|
||||
},
|
||||
regions() {
|
||||
return regions.splatoonRegions.filter(({ key }) => key).map(({ key }) => {
|
||||
return this.$store.state.splatoon.regions.all.map(({ key }) => {
|
||||
let name = (key) ? this.$t(`regions.${key}.name`) : this.$t('regions.global.name');
|
||||
return { key, name };
|
||||
});
|
||||
},
|
||||
festivals() {
|
||||
let data = this.allFestivals[this.region];
|
||||
|
||||
return data.festivals.map(festival => {
|
||||
let results = data.results.find(r => r.festival_id == festival.festival_id);
|
||||
return { festival, results };
|
||||
});
|
||||
return this.allSplatfests[this.region];
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
|
||||
@@ -24,12 +24,12 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<SplatfestResultsRow :festival="festival" :results="results" type="vote" />
|
||||
<SplatfestResultsRow :festival="festival" :results="results" type="solo" />
|
||||
<SplatfestResultsRow :festival="festival" :results="results" type="team" />
|
||||
<SplatfestResultsRow :festival="festival" type="vote" />
|
||||
<SplatfestResultsRow :festival="festival" type="solo" />
|
||||
<SplatfestResultsRow :festival="festival" type="team" />
|
||||
|
||||
<div class="has-text-centered is-size-5 title-color font-splatoon2" style="margin-top: 10px;" v-if="historyMode">
|
||||
<SplatfestWinnerBar :festival="festival" :results="results" />
|
||||
<SplatfestWinnerBar :festival="festival" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -40,6 +40,6 @@ import SplatfestWinnerBar from './SplatfestWinnerBar.vue';
|
||||
|
||||
export default {
|
||||
components: { SplatfestResultsRow, SplatfestWinnerBar },
|
||||
props: ['festival', 'results', 'historyMode'],
|
||||
props: ['festival', 'historyMode'],
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<div class="winner-mark-shadow" v-if="winner == 'alpha'"></div>
|
||||
<div class="winner-mark" :style="{ background: festival.colors.alpha.css_rgb }" v-if="winner == 'alpha'"></div>
|
||||
<div class="font-splatoon2 title is-4" :title="alphaTitle | numberFormat">
|
||||
<div>{{ results.rates[type].alpha | wholePercent }}<span class="percent">.{{ results.rates[type].alpha | partialPercent }}{{ $t('splatfest.results.%') }}</span></div>
|
||||
<div>{{ rates.alpha | wholePercent }}<span class="percent">.{{ rates.alpha | partialPercent }}{{ $t('splatfest.results.%') }}</span></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
<div class="winner-mark-shadow" v-if="winner == 'bravo'"></div>
|
||||
<div class="winner-mark" :style="{ background: festival.colors.bravo.css_rgb }" v-if="winner == 'bravo'"></div>
|
||||
<div class="font-splatoon2 title is-4" :title="bravoTitle | numberFormat">
|
||||
<div>{{ results.rates[type].bravo | wholePercent }}<span class="percent">.{{ results.rates[type].bravo | partialPercent }}{{ $t('splatfest.results.%') }}</span></div>
|
||||
<div>{{ rates.bravo | wholePercent }}<span class="percent">.{{ rates.bravo | partialPercent }}{{ $t('splatfest.results.%') }}</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -24,10 +24,10 @@
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: ['festival', 'results', 'type'],
|
||||
props: ['festival', 'type'],
|
||||
computed: {
|
||||
winner() {
|
||||
return this.results.summary[this.type] ? 'bravo' : 'alpha';
|
||||
return this.festival.results.summary[this.type] ? 'bravo' : 'alpha';
|
||||
},
|
||||
alphaTitle() {
|
||||
return this.resultTitle('alpha');
|
||||
@@ -35,6 +35,9 @@ export default {
|
||||
bravoTitle() {
|
||||
return this.resultTitle('bravo');
|
||||
},
|
||||
rates() {
|
||||
return this.festival.results.rates[this.type];
|
||||
},
|
||||
},
|
||||
filters: {
|
||||
wholePercent(value) {
|
||||
@@ -47,8 +50,8 @@ export default {
|
||||
methods: {
|
||||
resultTitle(team) {
|
||||
if (this.type == 'vote')
|
||||
return this.results.team_participants && this.results.team_participants[team];
|
||||
return this.results.team_scores && this.results.team_scores[`${team}_${this.type}`];
|
||||
return this.festival.results.team_participants && this.festival.results.team_participants[team];
|
||||
return this.festival.results.team_scores && this.festival.results.team_scores[`${team}_${this.type}`];
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: ['festival', 'results'],
|
||||
props: ['festival'],
|
||||
computed: {
|
||||
teamNames() {
|
||||
return {
|
||||
@@ -13,10 +13,10 @@ export default {
|
||||
};
|
||||
},
|
||||
teamWins() {
|
||||
if (!this.festival || !this.results)
|
||||
if (!this.festival || !this.festival.results)
|
||||
return;
|
||||
|
||||
let winner = this.results.summary.total ? 'bravo' : 'alpha';
|
||||
let winner = this.festival.results.summary.total ? 'bravo' : 'alpha';
|
||||
|
||||
let team = `<span style="color: ${this.festival.colors[winner].css_rgb}">${this.teamNames[winner]}</span>`;
|
||||
return this.$t('splatfest.team_name_wins', { team });
|
||||
|
||||
@@ -29,7 +29,7 @@ export default {
|
||||
stage: {},
|
||||
showTitle: { default: true },
|
||||
clickable: { default: true },
|
||||
isSalmonRun: { default: false },
|
||||
isSalmonRun: Boolean,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
||||
@@ -74,5 +74,10 @@
|
||||
"get_updates_via_twitter": "Informiere dich über Twitter!",
|
||||
"all_upcoming_stages": "Alle bevorstehenden Arenen",
|
||||
"disclaimer": "Diese Website steht in keiner Verbindung zu Nintendo. Alle Produktnamen, Logos und Marken sind Eigentum der jeweiligen Inhaber."
|
||||
},
|
||||
"game_mode": {
|
||||
"regular": "Standardkampf",
|
||||
"ranked": "Rangkampf",
|
||||
"league": "Ligakampf"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,5 +74,10 @@
|
||||
"get_updates_via_twitter": "Get updates via Twitter!",
|
||||
"all_upcoming_stages": "All Upcoming Stages",
|
||||
"disclaimer": "This website is not affiliated with Nintendo. All product names, logos, and brands are property of their respective owners."
|
||||
},
|
||||
"game_mode": {
|
||||
"regular": "Regular Battle",
|
||||
"ranked": "Ranked Battle",
|
||||
"league": "League Battle"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,5 +67,10 @@
|
||||
"gear": {
|
||||
"frequent_ability": "Potenciador común"
|
||||
},
|
||||
"ui": {}
|
||||
"ui": {},
|
||||
"game_mode": {
|
||||
"regular": "Combate amistoso",
|
||||
"ranked": "Combate competitivo",
|
||||
"league": "Combate de liga"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,5 +67,10 @@
|
||||
"gear": {
|
||||
"frequent_ability": "Potenciadores comunes"
|
||||
},
|
||||
"ui": {}
|
||||
"ui": {},
|
||||
"game_mode": {
|
||||
"regular": "Combate amistoso",
|
||||
"ranked": "Combate competitivo",
|
||||
"league": "Torneo"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,5 +73,10 @@
|
||||
"get_updates_via_twitter": "Soyez informés par Twitter!",
|
||||
"all_upcoming_stages": "Les stages à venir",
|
||||
"disclaimer": "Ce site internet n'est pas affilié à Nintendo. Tous les noms de produits, logos et marques appartiennent à leurs propriétaires respectifs."
|
||||
},
|
||||
"game_mode": {
|
||||
"regular": "Match classique",
|
||||
"ranked": "Match pro",
|
||||
"league": "Match de ligue"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,5 +73,10 @@
|
||||
"get_updates_via_twitter": "Soyez informés par Twitter !",
|
||||
"all_upcoming_stages": "Les stages à venir",
|
||||
"disclaimer": "Ce site internet n'est pas affilié à Nintendo. Tous les noms de produits, logos et marques appartiennent à leurs propriétaires respectifs."
|
||||
},
|
||||
"game_mode": {
|
||||
"regular": "Match classique",
|
||||
"ranked": "Match pro",
|
||||
"league": "Match de ligue"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,5 +67,10 @@
|
||||
"gear": {
|
||||
"frequent_ability": "Abilità comune"
|
||||
},
|
||||
"ui": {}
|
||||
"ui": {},
|
||||
"game_mode": {
|
||||
"regular": "Partita amichevole",
|
||||
"ranked": "Partita pro",
|
||||
"league": "Partita di lega"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,5 +67,10 @@
|
||||
"gear": {
|
||||
"frequent_ability": "つきやすいギアパワー"
|
||||
},
|
||||
"ui": {}
|
||||
"ui": {},
|
||||
"game_mode": {
|
||||
"regular": "レギュラーマッチ",
|
||||
"ranked": "ガチマッチ",
|
||||
"league": "リーグマッチ"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,5 +67,10 @@
|
||||
"gear": {
|
||||
"frequent_ability": "Waarschijnlijk effect"
|
||||
},
|
||||
"ui": {}
|
||||
"ui": {},
|
||||
"game_mode": {
|
||||
"regular": "Standaardgevecht",
|
||||
"ranked": "Profgevecht",
|
||||
"league": "Toernooigevecht"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,5 +67,10 @@
|
||||
"gear": {
|
||||
"frequent_ability": "Обычное свойство снаряжения"
|
||||
},
|
||||
"ui": {}
|
||||
"ui": {},
|
||||
"game_mode": {
|
||||
"regular": "Бой салаг",
|
||||
"ranked": "Бой элиты",
|
||||
"league": "Бой лиги"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,14 @@ const routes = [
|
||||
path: '/',
|
||||
component: require('./components/Main.vue').default,
|
||||
children: [
|
||||
{
|
||||
path: 'splatnet',
|
||||
component: require('./components/splatoon/SplatNetGearDialog.vue').default,
|
||||
},
|
||||
{
|
||||
path: 'splatfests',
|
||||
component: require('./components/splatoon/SplatfestHistoryDialog.vue').default,
|
||||
},
|
||||
{
|
||||
path: 'about',
|
||||
component: require('./components/AboutDialog.vue').default,
|
||||
@@ -16,12 +24,6 @@ const routes = [
|
||||
path: 'calendars',
|
||||
component: require('./components/CalendarDialog.vue').default,
|
||||
},
|
||||
{
|
||||
path: 'splatnet',
|
||||
},
|
||||
{
|
||||
path: 'splatfests',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
|
||||
@@ -21,12 +21,12 @@ import NewWeapon from './components/screenshots/newweapon/NewWeapon.vue';
|
||||
import Splatfest from './components/screenshots/splatfest/Splatfest.vue';
|
||||
const routes = [
|
||||
{ path: '/', component: ScreenshotHelper },
|
||||
{ path: '/schedules/:startTime', component: Schedules, props: true },
|
||||
{ path: '/splatNetGear/:startTime/:endTime', component: SplatNetGear, props: true },
|
||||
{ path: '/salmonRun/:startTime', component: SalmonRun, props: true },
|
||||
{ path: '/salmonRunGear/:now', component: SalmonRunGear, props: true },
|
||||
{ path: '/newWeapon/:releaseTime', component: NewWeapon, props: true },
|
||||
{ path: '/splatfest/:region/:startTime', component: Splatfest, props: true },
|
||||
{ path: '/schedules/:now', component: Schedules },
|
||||
{ path: '/splatNetGear/:now', component: SplatNetGear },
|
||||
{ path: '/salmonRun/:now', component: SalmonRun },
|
||||
{ path: '/salmonRunGear/:now', component: SalmonRunGear },
|
||||
{ path: '/newWeapon/:now', component: NewWeapon },
|
||||
{ path: '/splatfest/:region/:now', component: Splatfest, props: true },
|
||||
];
|
||||
const router = new VueRouter({ routes });
|
||||
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
export default {
|
||||
loadLocale({ dispatch }, locale) {
|
||||
// Disabling this for now due to issues with disabling prefetching with the modern build (see vue.config.js)
|
||||
// import(/* webpackChunkName: "lang-[request]" */ `@/web/locale/${locale}`)
|
||||
// .then(translations => dispatch('addLocale', { locale, translations: translations.default }));
|
||||
|
||||
dispatch('addLocale', { locale, translations: require(`@/web/locale/${locale}`) });
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,15 @@
|
||||
import Vue from 'vue';
|
||||
import Vuex from 'vuex';
|
||||
|
||||
import splatoon from './splatoon';
|
||||
|
||||
Vue.use(Vuex);
|
||||
|
||||
const debug = process.env.NODE_ENV !== 'production';
|
||||
|
||||
export default new Vuex.Store({
|
||||
actions: require('./actions').default,
|
||||
modules: {
|
||||
splatoon,
|
||||
},
|
||||
strict: debug,
|
||||
});
|
||||
|
||||
109
src/web/store/splatoon/data.js
Normal file
109
src/web/store/splatoon/data.js
Normal file
@@ -0,0 +1,109 @@
|
||||
import axios from 'axios';
|
||||
|
||||
let updateDataTimer;
|
||||
|
||||
export const namespaced = true;
|
||||
|
||||
// State, actions, and mutators are identical for each data source, so this module
|
||||
// generates them automatically from a list of data sources.
|
||||
|
||||
let dataSources = [
|
||||
{
|
||||
name: 'schedules',
|
||||
url: '/data/schedules.json',
|
||||
},
|
||||
{
|
||||
name: 'coop_schedules',
|
||||
url: '/data/coop-schedules.json',
|
||||
},
|
||||
{
|
||||
name: 'timeline',
|
||||
url: '/data/timeline.json',
|
||||
},
|
||||
{
|
||||
name: 'festivals',
|
||||
url: '/data/festivals.json',
|
||||
},
|
||||
{
|
||||
name: 'merchandises',
|
||||
url: '/data/merchandises.json',
|
||||
},
|
||||
];
|
||||
|
||||
// Automatically determine the update action name and mutation name
|
||||
for (let source of dataSources) {
|
||||
// Action: updateSourceName
|
||||
source.actionName = 'update' + source.name[0].toUpperCase() + source.name.substr(1);
|
||||
|
||||
// Mutation: UPDATE_SOURCENAME
|
||||
source.mutationName = 'UPDATE_' + source.name.toUpperCase();
|
||||
}
|
||||
|
||||
export const state = { };
|
||||
|
||||
export const actions = {
|
||||
updateLanguage({ dispatch, rootGetters }) {
|
||||
let language = rootGetters['splatoon/languages/selectedLanguage'];
|
||||
if (language) {
|
||||
axios.get(`/data/locale/${language.language}.json`)
|
||||
.then(response => dispatch('addLocale', {
|
||||
locale: language.language,
|
||||
translations: { splatnet: response.data },
|
||||
}, { root: true }))
|
||||
.catch(e => console.error(e));
|
||||
}
|
||||
},
|
||||
updateAll({ dispatch }) {
|
||||
return Promise.all([
|
||||
dispatch('updateLanguage'),
|
||||
...dataSources.map(source => dispatch(source.actionName)),
|
||||
]);
|
||||
},
|
||||
startUpdatingData({ dispatch }) {
|
||||
if (updateDataTimer)
|
||||
return;
|
||||
|
||||
dispatch('updateAll');
|
||||
|
||||
let date = new Date;
|
||||
|
||||
// If we're more than 20 seconds past the current hour, schedule the update for the next hour
|
||||
if (date.getMinutes() !== 0 || date.getSeconds() >= 20)
|
||||
date.setHours(date.getHours() + 1);
|
||||
date.setMinutes(0);
|
||||
|
||||
// Random number of seconds past the hour (so all open browsers don't hit the server at the same time)
|
||||
let minSec = 25;
|
||||
let maxSec = 60;
|
||||
date.setSeconds(Math.floor(Math.random() * (maxSec - minSec + 1)) + minSec);
|
||||
|
||||
// Set the timeout
|
||||
updateDataTimer = setTimeout(() => {
|
||||
updateDataTimer = null;
|
||||
dispatch('startUpdatingData');
|
||||
}, (date - new Date));
|
||||
},
|
||||
stopUpdatingData() {
|
||||
clearInterval(updateDataTimer);
|
||||
updateDataTimer = null;
|
||||
},
|
||||
};
|
||||
|
||||
export const mutations = { };
|
||||
|
||||
for (let source of dataSources) {
|
||||
// State
|
||||
state[source.name] = null;
|
||||
|
||||
// Actions
|
||||
actions[source.actionName] = ({ commit }) => {
|
||||
axios.get(source.url)
|
||||
.then(({ data }) => commit(source.mutationName, { data }))
|
||||
.catch(e => console.error(e));
|
||||
};
|
||||
|
||||
// Mutations
|
||||
mutations[source.mutationName] = (state, { data }) => {
|
||||
state[source.name] = data;
|
||||
};
|
||||
}
|
||||
72
src/web/store/splatoon/index.js
Normal file
72
src/web/store/splatoon/index.js
Normal file
@@ -0,0 +1,72 @@
|
||||
import * as settings from './settings';
|
||||
import * as regions from './regions';
|
||||
import * as languages from './languages';
|
||||
import * as data from './data';
|
||||
import * as schedules from './schedules';
|
||||
import * as salmonRun from './salmonRun';
|
||||
import * as splatfests from './splatfests';
|
||||
import * as splatNetStore from './splatNetStore';
|
||||
import * as newWeapons from './newWeapons';
|
||||
|
||||
let updateNowTimer;
|
||||
|
||||
const state = {
|
||||
now: null,
|
||||
};
|
||||
|
||||
const getters = {
|
||||
now(state) {
|
||||
return state.now;
|
||||
},
|
||||
};
|
||||
|
||||
const actions = {
|
||||
updateNow({ state, commit }) {
|
||||
let now = Math.trunc((new Date).getTime() / 1000);
|
||||
|
||||
if (state.now != now)
|
||||
commit('UPDATE_NOW', { now });
|
||||
},
|
||||
setNow({ commit }, { now }) {
|
||||
commit('UPDATE_NOW', { now });
|
||||
},
|
||||
startUpdatingNow({ dispatch }) {
|
||||
if (updateNowTimer)
|
||||
return;
|
||||
|
||||
dispatch('updateNow');
|
||||
|
||||
updateNowTimer = setInterval(() => {
|
||||
dispatch('updateNow');
|
||||
}, 200);
|
||||
},
|
||||
stopUpdatingNow() {
|
||||
clearInterval(updateNowTimer);
|
||||
updateNowTimer = null;
|
||||
},
|
||||
};
|
||||
|
||||
const mutations = {
|
||||
UPDATE_NOW(state, { now }) {
|
||||
state.now = now;
|
||||
},
|
||||
};
|
||||
|
||||
export default {
|
||||
namespaced: true,
|
||||
state,
|
||||
getters,
|
||||
actions,
|
||||
mutations,
|
||||
modules: {
|
||||
settings,
|
||||
regions,
|
||||
languages,
|
||||
data,
|
||||
schedules,
|
||||
salmonRun,
|
||||
splatfests,
|
||||
splatNetStore,
|
||||
newWeapons,
|
||||
},
|
||||
};
|
||||
43
src/web/store/splatoon/languages.js
Normal file
43
src/web/store/splatoon/languages.js
Normal file
@@ -0,0 +1,43 @@
|
||||
import { languages, detectSplatoonLanguage } from '@/common/regions.esm';
|
||||
|
||||
export const namespaced = true;
|
||||
|
||||
export const state = {
|
||||
all: languages,
|
||||
detectedLanguageKey: detectSplatoonLanguage(),
|
||||
};
|
||||
|
||||
export const getters = {
|
||||
selectedKey(state, getters, { splatoon }) {
|
||||
let key = splatoon.settings.selectedLanguage;
|
||||
return (key === null) ? state.detectedLanguageKey : key;
|
||||
},
|
||||
selectedLanguage(state, getters) {
|
||||
return state.all.find(l => l.language === getters.selectedKey);
|
||||
},
|
||||
};
|
||||
|
||||
export const actions = {
|
||||
setLanguage({ dispatch }, { language }) {
|
||||
let key = (language && language.language) ? language.language : 'en';
|
||||
|
||||
dispatch('splatoon/settings/updateSetting',
|
||||
{ name: 'selectedLanguage', value: key },
|
||||
{ root: true });
|
||||
},
|
||||
languageChanged({ getters, dispatch }) {
|
||||
let language = getters.selectedLanguage;
|
||||
if (language) {
|
||||
dispatch('loadLocale', { locale: language.language });
|
||||
dispatch('splatoon/data/updateLanguage', null, { root: true });
|
||||
dispatch('setLocale', { locale: language.language }, { root: true });
|
||||
}
|
||||
},
|
||||
loadLocale({ dispatch }, { locale }) {
|
||||
// Disabling this for now due to issues with disabling prefetching with the modern build (see vue.config.js)
|
||||
// import(/* webpackChunkName: "lang-[request]" */ `@/web/locale/${locale}`)
|
||||
// .then(translations => dispatch('addLocale', { locale, translations: translations.default }, { root: true }));
|
||||
|
||||
dispatch('addLocale', { locale, translations: require(`@/web/locale/${locale}`) }, { root: true });
|
||||
},
|
||||
};
|
||||
17
src/web/store/splatoon/newWeapons.js
Normal file
17
src/web/store/splatoon/newWeapons.js
Normal file
@@ -0,0 +1,17 @@
|
||||
export const namespaced = true;
|
||||
|
||||
export const getters = {
|
||||
availabilities(state, getters, { splatoon }) {
|
||||
let now = splatoon.now;
|
||||
let availabilities = splatoon.data.timeline
|
||||
&& splatoon.data.timeline.weapon_availability
|
||||
&& splatoon.data.timeline.weapon_availability.availabilities;
|
||||
|
||||
if (now && availabilities)
|
||||
return availabilities.filter(a => a.release_time <= now);
|
||||
},
|
||||
weapons(state, getters) {
|
||||
return getters.availabilities
|
||||
&& getters.availabilities.map(a => a.weapon);
|
||||
},
|
||||
};
|
||||
31
src/web/store/splatoon/regions.js
Normal file
31
src/web/store/splatoon/regions.js
Normal file
@@ -0,0 +1,31 @@
|
||||
import { splatoonRegions, detectSplatoonRegion } from '@/common/regions.esm';
|
||||
|
||||
export const namespaced = true;
|
||||
|
||||
export const state = {
|
||||
all: splatoonRegions,
|
||||
detectedRegionKey: detectSplatoonRegion(),
|
||||
};
|
||||
|
||||
export const getters = {
|
||||
all(state) {
|
||||
return state.all;
|
||||
},
|
||||
selectedKey(state, getters, { splatoon }) {
|
||||
let key = splatoon.settings.selectedRegion;
|
||||
return (key === null) ? state.detectedRegionKey : key;
|
||||
},
|
||||
selectedRegion(state, getters) {
|
||||
return state.all.find(r => r.key === getters.selectedKey);
|
||||
},
|
||||
};
|
||||
|
||||
export const actions = {
|
||||
setRegion({ dispatch }, { region }) {
|
||||
let key = (region && region.key) ? region.key : 'global';
|
||||
|
||||
dispatch('splatoon/settings/updateSetting',
|
||||
{ name: 'selectedRegion', value: key },
|
||||
{ root: true });
|
||||
},
|
||||
};
|
||||
38
src/web/store/splatoon/salmonRun.js
Normal file
38
src/web/store/splatoon/salmonRun.js
Normal file
@@ -0,0 +1,38 @@
|
||||
export const namespaced = true;
|
||||
|
||||
export const getters = {
|
||||
// All current schedules (active & future)
|
||||
currentSchedules(state, getters, { splatoon }) {
|
||||
let now = splatoon.now;
|
||||
let coopSchedules = splatoon.data.coop_schedules;
|
||||
|
||||
if (now && coopSchedules) {
|
||||
let { schedules, details } = coopSchedules;
|
||||
|
||||
return schedules.map(schedule => {
|
||||
let detail = details.find(d => d.start_time == schedule.start_time);
|
||||
return detail || schedule;
|
||||
}).filter(s => s.end_time > now);
|
||||
}
|
||||
},
|
||||
// The currently-active schedule
|
||||
activeSchedule(state, getters, { splatoon }) {
|
||||
let now = splatoon.now;
|
||||
|
||||
if (now && getters.currentSchedules)
|
||||
return getters.currentSchedules.find(s => s.start_time <= now);
|
||||
},
|
||||
// Future upcoming schedules
|
||||
upcomingSchedules(state, getters, { splatoon }) {
|
||||
let now = splatoon.now;
|
||||
|
||||
if (now && getters.currentSchedules)
|
||||
return getters.currentSchedules.filter(s => s.start_time > now);
|
||||
},
|
||||
rewardGear(state, getters, { splatoon }) {
|
||||
return splatoon.data.timeline
|
||||
&& splatoon.data.timeline.coop
|
||||
&& splatoon.data.timeline.coop.reward_gear
|
||||
&& splatoon.data.timeline.coop.reward_gear.gear;
|
||||
},
|
||||
};
|
||||
37
src/web/store/splatoon/schedules.js
Normal file
37
src/web/store/splatoon/schedules.js
Normal file
@@ -0,0 +1,37 @@
|
||||
export const namespaced = true;
|
||||
|
||||
function generateModule(name, source) {
|
||||
return {
|
||||
namespaced: true,
|
||||
getters: {
|
||||
// All current schedules (active & future)
|
||||
currentSchedules(state, getters, { splatoon }) {
|
||||
let now = splatoon.now
|
||||
let schedules = splatoon.data.schedules;
|
||||
|
||||
if (now && schedules)
|
||||
return schedules[source].filter(s => s.end_time > now);
|
||||
},
|
||||
// The currently-active schedule
|
||||
activeSchedule(state, getters, { splatoon }) {
|
||||
let now = splatoon.now;
|
||||
|
||||
if (now && getters.currentSchedules)
|
||||
return getters.currentSchedules.find(s => s.start_time <= now);
|
||||
},
|
||||
// Future upcoming schedules
|
||||
upcomingSchedules(state, getters, { splatoon }) {
|
||||
let now = splatoon.now;
|
||||
|
||||
if (now && getters.currentSchedules)
|
||||
return getters.currentSchedules.filter(s => s.start_time > now);
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export const modules = {
|
||||
regular: generateModule('regular', 'regular'),
|
||||
ranked: generateModule('ranked', 'gachi'),
|
||||
league: generateModule('league', 'league'),
|
||||
};
|
||||
66
src/web/store/splatoon/settings.js
Normal file
66
src/web/store/splatoon/settings.js
Normal file
@@ -0,0 +1,66 @@
|
||||
import store from '@/web/store';
|
||||
|
||||
export const namespaced = true;
|
||||
|
||||
const settings = [
|
||||
{
|
||||
name: 'selectedRegion',
|
||||
key: 'selected-region',
|
||||
},
|
||||
{
|
||||
name: 'selectedLanguage',
|
||||
key: 'selected-language',
|
||||
action: 'splatoon/languages/languageChanged',
|
||||
},
|
||||
];
|
||||
|
||||
function reloadSettings() {
|
||||
for (let setting of settings)
|
||||
store.dispatch('splatoon/settings/loadSetting', { name: setting.name });
|
||||
}
|
||||
|
||||
export const state = {};
|
||||
|
||||
export const actions = {
|
||||
initialize({ dispatch }) {
|
||||
window.addEventListener('storage', reloadSettings);
|
||||
|
||||
for (let setting of settings)
|
||||
dispatch('loadSetting', { name: setting.name });
|
||||
},
|
||||
shutdown() {
|
||||
window.removeEventListener('storage', reloadSettings);
|
||||
},
|
||||
loadSetting({ commit, dispatch }, { name }) {
|
||||
let setting = settings.find(s => s.name === name);
|
||||
|
||||
if (setting) {
|
||||
let value = localStorage.getItem(setting.key);
|
||||
commit('UPDATE_SETTING', { name, value });
|
||||
|
||||
if (setting.action)
|
||||
dispatch(setting.action, null, { root: true });
|
||||
}
|
||||
},
|
||||
updateSetting({ commit, dispatch }, { name, value }) {
|
||||
let setting = settings.find(s => s.name === name);
|
||||
|
||||
if (setting) {
|
||||
localStorage.setItem(setting.key, value);
|
||||
commit('UPDATE_SETTING', { name, value });
|
||||
|
||||
if (setting.action)
|
||||
dispatch(setting.action, null, { root: true });
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
export const mutations = {
|
||||
UPDATE_SETTING(store, { name, value }) {
|
||||
store[name] = value;
|
||||
},
|
||||
};
|
||||
|
||||
for (let setting of settings) {
|
||||
state[setting.name] = null;
|
||||
}
|
||||
11
src/web/store/splatoon/splatNetStore.js
Normal file
11
src/web/store/splatoon/splatNetStore.js
Normal file
@@ -0,0 +1,11 @@
|
||||
export const namespaced = true;
|
||||
|
||||
export const getters = {
|
||||
merchandises(state, getters, { splatoon }) {
|
||||
let now = splatoon.now;
|
||||
let merchandises = splatoon.data.merchandises && splatoon.data.merchandises.merchandises;
|
||||
|
||||
if (now && merchandises)
|
||||
return merchandises.filter(m => m.end_time > now);
|
||||
},
|
||||
};
|
||||
66
src/web/store/splatoon/splatfests.js
Normal file
66
src/web/store/splatoon/splatfests.js
Normal file
@@ -0,0 +1,66 @@
|
||||
export const namespaced = true;
|
||||
|
||||
export const getters = {
|
||||
anyRegionHasCurrentSplatfest(state, getters, rootState, rootGetters) {
|
||||
return rootGetters['splatoon/splatfests/na/currentSplatfest']
|
||||
|| rootGetters['splatoon/splatfests/eu/currentSplatfest']
|
||||
|| rootGetters['splatoon/splatfests/jp/currentSplatfest'];
|
||||
},
|
||||
selectedRegionCurrentSplatfest(state, getters, rootState, rootGetters) {
|
||||
let region = rootGetters[`splatoon/regions/selectedRegion`];
|
||||
if (region)
|
||||
return rootGetters[`splatoon/splatfests/${region.key}/currentSplatfest`];
|
||||
},
|
||||
selectedRegionHasActiveSplatfest(state, getters) {
|
||||
return getters.selectedRegionCurrentSplatfest
|
||||
&& getters.selectedRegionCurrentSplatfest.state === 'active';
|
||||
},
|
||||
};
|
||||
|
||||
function generateModule(region) {
|
||||
return {
|
||||
namespaced: true,
|
||||
getters: {
|
||||
allSplatfests(state, getters, { splatoon }) {
|
||||
let now = splatoon.now;
|
||||
let data = splatoon.data.festivals && splatoon.data.festivals[region];
|
||||
|
||||
if (now && data) {
|
||||
let { festivals, results } = data;
|
||||
|
||||
return festivals.map(festival => {
|
||||
let state;
|
||||
if (festival.times.start > now)
|
||||
state = 'upcoming';
|
||||
else if (festival.times.end > now)
|
||||
state = 'active';
|
||||
else
|
||||
state = 'past';
|
||||
|
||||
return {
|
||||
...festival,
|
||||
state,
|
||||
results: results.find(r => r.festival_id === festival.festival_id),
|
||||
};
|
||||
});
|
||||
}
|
||||
},
|
||||
currentSplatfest(state, getters, { splatoon }) {
|
||||
let now = splatoon.now;
|
||||
|
||||
if (now && getters.allSplatfests) {
|
||||
// We want to show future/current festivals and festivals that ended recently (so we can show their results).
|
||||
// Filter based on the result time and show festival results for a few days.
|
||||
let cutoff = now - 86400 * 3; // 3 days
|
||||
return getters.allSplatfests.find(s => s.times.result > cutoff);
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export const modules = {
|
||||
na: generateModule('na'),
|
||||
eu: generateModule('eu'),
|
||||
jp: generateModule('jp'),
|
||||
}
|
||||
Reference in New Issue
Block a user