Compartmentalize game modal

This commit is contained in:
Gamebrary 2020-08-22 05:21:15 -07:00
parent 1e42364643
commit ae068f27cb
10 changed files with 679 additions and 442 deletions

View file

@ -0,0 +1,77 @@
<template lang="html">
<b-button
v-if="game && !list.games.includes(game.id)"
:title="$t('list.addGame')"
variant="success"
@click="addGame"
>
<b-icon-plus />
</b-button>
<b-button
v-else
variant="danger"
:title="$t('gameDetail.removeFromList')"
@click="removeGame"
>
<b-icon-trash />
</b-button>
</template>
<script>
import { mapState } from 'vuex';
export default {
props: {
game: Object,
list: Object,
},
computed: {
...mapState(['board']),
},
methods: {
async addGame() {
const { list, game, board } = this;
const listIndex = board.lists.findIndex(({ name }) => name === list.name);
this.$store.commit('ADD_GAME_TO_LIST', {
listIndex,
game,
});
await this.$store.dispatch('SAVE_BOARD')
.catch(() => {
this.$bvToast.toast(`There was an error adding "${this.game.name}"`, { title: list.name, variant: 'danger' });
});
// TODO: CUSTOMIZE TO SHOW GAME COVER
this.$bvToast.toast(`${this.game.name} added`, { title: list.name, variant: 'success' });
// this.$ga.event({
// eventCategory: 'game',
// eventAction: 'add',
// eventLabel: 'addGame',
// eventValue: data,
// });
},
async removeGame() {
const { list, game, board } = this;
const listIndex = board.lists.findIndex(({ name }) => name === list.name);
this.$store.commit('REMOVE_GAME_FROM_LIST', { listIndex, game });
await this.$store.dispatch('SAVE_BOARD')
.catch(() => {
this.$bvToast.toast(`There was an error removing "${this.game.name}"`, { title: list.name, variant: 'danger' });
});
this.$bvToast.toast(`${this.game.name} removed`, { title: list.name, variant: 'success' });
},
},
};
</script>

View file

@ -0,0 +1,123 @@
<template lang="html">
<b-tab title="Game details" active>
<dl class="row">
<!-- TODO: plural vs singular translations? -->
<dt class="col-sm-3">{{ $t('gameDetail.platforms') }}</dt>
<dd class="col-sm-9">{{ platforms }}</dd>
<dt class="col-sm-3">{{ $t('gameDetail.genres') }}</dt>
<dd class="col-sm-9">{{ genres }}</dd>
<dt class="col-sm-3">{{ $t('gameDetail.gameModes') }}</dt>
<dd class="col-sm-9">{{ gameModes }}</dd>
<dt class="col-sm-3">{{ $t('gameDetail.developers') }}</dt>
<dd class="col-sm-9">{{ gameDevelopers }}</dd>
<dt class="col-sm-3">{{ $t('gameDetail.publishers') }}</dt>
<dd class="col-sm-9">{{ gamePublishers }}</dd>
<dt class="col-sm-3">{{ $t('gameDetail.perspective') }}</dt>
<dd class="col-sm-9">{{ playerPerspectives }}</dd>
<dt class="col-sm-3">{{ $t('gameDetail.timeToBeat') }}</dt>
<dd class="col-sm-9">{{ timeToBeat }}</dd>
<dt class="col-sm-3">{{ $t('gameDetail.ageRatings') }}</dt>
<dd class="col-sm-9">{{ ageRatings }}</dd>
<!-- TODO: add release dates -->
<!-- {{ $t('gameDetail.releaseDate') }} -->
<!-- <pre>{{ game.release_dates }}</pre> -->
</dl>
</b-tab>
</template>
<script>
import moment from 'moment';
export default {
props: {
game: Object,
},
data() {
return {
ageRating: {
categories: {
1: 'ESRB',
2: 'PEGI',
},
values: {
1: '3',
2: '7',
3: '12',
4: '16',
5: '18',
6: 'RP',
7: 'EC',
8: 'E',
9: 'E10',
10: 'T',
11: 'M',
12: 'AO',
},
},
};
},
computed: {
genres() {
return this.game && this.game.genres
? this.game.genres.map(({ name }) => name).join(', ')
: 'N/A';
},
timeToBeat() {
return this.game && this.game.time_to_beat
? moment.unix(this.game.time_to_beat).format('h[h] m[m]')
: 'N/A';
},
platforms() {
return this.game && this.game.platforms
? this.game.platforms.map(({ name }) => name).join(', ')
: 'N/A';
},
gameDevelopers() {
return this.game && this.game.involved_companies
? this.game.involved_companies
.filter(({ developer }) => developer)
.map(({ company }) => company.name).join(', ')
: 'N/A';
},
gamePublishers() {
return this.game && this.game.involved_companies
? this.game.involved_companies
.filter(({ publisher }) => publisher)
.map(({ company }) => company.name).join(', ')
: 'N/A';
},
gameModes() {
return this.game && this.game.game_modes
? this.game.game_modes.map(({ name }) => name).join(', ')
: 'N/A';
},
playerPerspectives() {
return this.game && this.game.player_perspectives
? this.game.player_perspectives.map(({ name }) => name).join(', ')
: 'N/A';
},
ageRatings() {
return this.game && this.game.age_ratings
? this.game.age_ratings.map(({ category, rating }) => `${this.ageRating.categories[category]}: ${this.ageRating.values[rating]}`).join(', ')
: 'N/A';
},
},
};
</script>

View file

@ -6,17 +6,17 @@
header-bg-variant="light"
footer-bg-variant="light"
footer-class="p-2 justify-content-center"
:title="modalTitle"
:title="game.name"
@show="setData"
@shown="loadGame"
@hidden="reset"
>
<b-container v-if="game">
<b-container v-if="game.name">
<b-row>
<b-col lg="4">
<b-img
:src="coverUrl"
:alt="title"
:alt="game.name"
rounded
fluid
/>
@ -31,9 +31,7 @@
</b-col>
<b-col lg="8" md="auto">
<h3 class="mb-0">
{{ title }}
</h3>
<h3 class="mb-0">{{ game.name }}</h3>
<!-- <h6>
<b-badge
@ -47,100 +45,52 @@
<b-form-rating
v-if="rating"
class="p-0"
inline
:value="rating"
class="p-0 game-rating"
inline
readonly
variant="warning"
size="lg"
no-border
/>
<div v-if="gameTags">
<b-badge
v-for="({ games, hex, tagTextColor }, name) in tags"
v-if="games.includes(game.id)"
:key="name"
pill
tag="small"
class="mr-1 mb-2"
:style="`background-color: ${hex}; color: ${tagTextColor}`"
>
{{ name }}
</b-badge>
</div>
<b-badge
v-for="({ games, hex, tagTextColor }, name) in tags"
v-if="games.includes(game.id)"
:key="name"
pill
tag="small"
class="mr-1 mb-2"
:style="`background-color: ${hex}; color: ${tagTextColor}`"
>
{{ name }}
</b-badge>
<div class="mb-3">
<b-button v-b-modal.progress variant="info">
<div v-if="loading" class="my-2">
<b-button variant="light">
<b-icon-check />
</b-button>
<b-modal id="progress" title="Set game progress" @shown="getProgress">
<b-input-group :prepend="`${localProgress}%`" class="mb-4" size="lg">
<b-form-input
size="lg"
v-model="localProgress"
type="range"
max="100"
step="5"
/>
</b-input-group>
<template v-slot:modal-footer>
<b-button variant="danger" @click="deleteProgress">
{{ $t('progresses.deleteProgress') }}
</b-button>
<b-button variant="primary" @click="saveProgress">
{{ $t('progresses.save') }}
</b-button>
</template>
</b-modal>
<b-button v-b-modal.notes variant="warning">
<b-button variant="light">
<b-icon-file-earmark-text />
</b-button>
<b-modal id="notes" title="Game notes" @shown="getNotes">
<b-form-textarea
v-model.trim="localNote.text"
placeholder="Type note here"
rows="3"
max-rows="20"
/>
<template v-slot:modal-footer>
<b-button variant="danger" @click="deleteNote">
{{ $t('progresses.deleteProgress') }}
</b-button>
<b-button variant="primary" @click="saveNote">
{{ $t('progresses.save') }}
</b-button>
</template>
</b-modal>
<game-tags :game-id="game.id" />
<b-button
v-if="game && !list.games.includes(game.id)"
:title="$t('list.addGame')"
variant="success"
@click="addGame"
>
<b-icon-plus />
<b-button variant="light">
<b-icon-tag />
</b-button>
<b-button
v-else
variant="danger"
:title="$t('gameDetail.removeFromList')"
@click="removeGame"
>
<b-button variant="light">
<b-icon-trash />
</b-button>
</div>
<div v-if="!loading" class="my-2">
<game-progress :game="game" />
<game-notes :game="game" />
<game-tags :game="game" />
<add-remove-game :game="game" :list="list" />
</div>
<placeholder :lines="3" v-if="loading"/>
<p v-else>{{ game.summary }}</p>
</b-col>
@ -150,110 +100,11 @@
<b-card v-else class="mt-4" no-body>
<b-tabs card>
<b-tab title="Game details" active>
<dl class="row">
<!-- TODO: plural vs singular translations? -->
<dt class="col-sm-3">{{ $t('gameDetail.platforms') }}</dt>
<dd class="col-sm-9">{{ platforms }}</dd>
<dt class="col-sm-3">{{ $t('gameDetail.genres') }}</dt>
<dd class="col-sm-9">{{ genres }}</dd>
<dt class="col-sm-3">{{ $t('gameDetail.gameModes') }}</dt>
<dd class="col-sm-9">{{ gameModes }}</dd>
<dt class="col-sm-3">{{ $t('gameDetail.developers') }}</dt>
<dd class="col-sm-9">{{ gameDevelopers }}</dd>
<dt class="col-sm-3">{{ $t('gameDetail.publishers') }}</dt>
<dd class="col-sm-9">{{ gamePublishers }}</dd>
<dt class="col-sm-3">{{ $t('gameDetail.perspective') }}</dt>
<dd class="col-sm-9">{{ playerPerspectives }}</dd>
<dt class="col-sm-3">{{ $t('gameDetail.timeToBeat') }}</dt>
<dd class="col-sm-9">{{ timeToBeat }}</dd>
<dt class="col-sm-3">{{ $t('gameDetail.ageRatings') }}</dt>
<dd class="col-sm-9">{{ ageRatings }}</dd>
<!-- TODO: add release dates -->
<!-- {{ $t('gameDetail.releaseDate') }} -->
<!-- <pre>{{ game.release_dates }}</pre> -->
</dl>
</b-tab>
<b-tab>
<template v-slot:title>
Notes <b-badge v-if="notes[gameId]">1</b-badge>
</template>
<template v-if="notes[gameId]">
<vue-markdown :source="notes[gameId].text" />
<!-- TODO add markdown preview? -->
<!-- <vue-markdown :source="localNote.text" /> -->
<b-button v-b-modal.notes variant="warning">
Edit note
</b-button>
</template>
<b-button v-else v-b-modal.notes>
Add note
</b-button>
</b-tab>
<b-tab v-if="game.websites">
<template v-slot:title>
Links
<b-badge>{{ game.websites.length }}</b-badge>
</template>
<dl class="row mb-0" v-for="link in game.websites" :key="link.id">
<!-- TODO: research which links can be leveraged to get API data,
e.g. wikipedia article, wikia, etc -->
<dt class="col-sm-3">{{ linkTypes[link.category]}}</dt>
<dd class="col-sm-9"><a :href="link.url" target="_blank">{{ link.url }}</a></dd>
</dl>
</b-tab>
<b-tab v-if="game.videos">
<template v-slot:title>
Videos
<b-badge>{{ game.videos.length }}</b-badge>
</template>
<b-embed
v-for="{ video_id } in game.videos"
:key="video_id"
type="iframe"
aspect="16by9"
:src="`https://www.youtube.com/embed/${video_id}`"
allowfullscreen
/>
</b-tab>
<b-tab v-if="screenshots">
<template v-slot:title>
Screenshots
<b-badge>{{ screenshots.length }}</b-badge>
</template>
<!-- TODO: add placeholder image, use blank-src="placeholder.jpg" prop -->
<!-- TODO: use blank-width prop? -->
<!-- TODO: use blank-height prop? -->
<b-img-lazy
v-for="(screenshot, index) in screenshots"
:key="index"
:src="screenshot"
fluid
blank-color="#ccc"
fluid-grow
rounded
class="mb-3"
/>
</b-tab>
<game-details-tab :game="game" />
<game-notes-tab :game="game" />
<game-websites-tab :game="game" />
<game-videos-tab :game="game" />
<game-screenshots-tab :game="game" />
</b-tabs>
</b-card>
</b-container>
@ -266,9 +117,16 @@
<script>
import moment from 'moment';
import { mapState, mapGetters } from 'vuex';
import VueMarkdown from 'vue-markdown';
import { mapState } from 'vuex';
import GameDetailPlaceholder from '@/components/Game/GameDetailPlaceholder';
import GameDetailsTab from '@/components/Game/GameDetailsTab';
import GameNotesTab from '@/components/Game/GameNotesTab';
import GameScreenshotsTab from '@/components/Game/GameScreenshotsTab';
import GameVideosTab from '@/components/Game/GameVideosTab';
import GameWebsitesTab from '@/components/Game/GameWebsitesTab';
import GameNotes from '@/components/Game/GameNotes';
import GameProgress from '@/components/Game/GameProgress';
import AddRemoveGame from '@/components/Game/AddRemoveGame';
import GameTags from '@/components/Game/GameTags';
import IgdbLogo from '@/components/IgdbLogo';
import Placeholder from '@/components/Placeholder';
@ -278,70 +136,28 @@ export default {
Placeholder,
GameTags,
IgdbLogo,
VueMarkdown,
GameDetailPlaceholder,
GameDetailsTab,
GameNotesTab,
GameScreenshotsTab,
GameVideosTab,
GameWebsitesTab,
GameNotes,
GameProgress,
AddRemoveGame,
},
data() {
return {
gameId: null,
listId: null,
localProgress: '0',
game: null,
game: {},
loading: true,
showPreview: false,
localNote: '',
// TODO: GET THIS FROM https://api-v3.igdb.com/igdbapi.proto?
linkTypes: {
1: 'Official site',
2: 'Wikia',
3: 'Wikipedia',
4: 'Facebook',
5: 'Twitter',
6: 'Twitch',
8: 'Instagram',
9: 'YouTube',
10: 'iPhone',
11: 'iPad',
12: 'Android',
13: 'Steam',
14: 'Reddit',
15: 'Itch',
16: 'Epic Games',
17: 'GOG',
18: 'Discord',
},
ageRating: {
categories: {
1: 'ESRB',
2: 'PEGI',
},
values: {
1: '3',
2: '7',
3: '12',
4: '16',
5: '18',
6: 'RP',
7: 'EC',
8: 'E',
9: 'E10',
10: 'T',
11: 'M',
12: 'AO',
},
},
};
},
computed: {
// TODO: rename gameModalData
...mapState(['gameModalData', 'games', 'platform', 'progresses', 'tags', 'notes', 'gameLists']),
...mapGetters(['gameTags']),
modalTitle() {
return this.game && this.game.name;
},
...mapState(['gameModalData', 'games', 'platform', 'progresses', 'tags']),
releaseDate() {
const releaseDate = this.game
@ -363,65 +179,6 @@ export default {
return progresses[gameId] || null;
},
genres() {
return this.game && this.game.genres
? this.game.genres.map(({ name }) => name).join(', ')
: 'N/A';
},
screenshots() {
return this.game && this.game.screenshots
// eslint-disable-next-line
? this.game.screenshots.map(({ image_id }) => `https://images.igdb.com/igdb/image/upload/t_screenshot_huge/${image_id}.jpg`)
: 'N/A';
},
platforms() {
return this.game && this.game.platforms
? this.game.platforms.map(({ name }) => name).join(', ')
: 'N/A';
},
timeToBeat() {
return this.game && this.game.time_to_beat
? moment.unix(this.game.time_to_beat).format('h[h] m[m]')
: 'N/A';
},
gameDevelopers() {
return this.game && this.game.involved_companies
? this.game.involved_companies
.filter(({ developer }) => developer)
.map(({ company }) => company.name).join(', ')
: 'N/A';
},
gamePublishers() {
return this.game && this.game.involved_companies
? this.game.involved_companies
.filter(({ publisher }) => publisher)
.map(({ company }) => company.name).join(', ')
: 'N/A';
},
gameModes() {
return this.game && this.game.game_modes
? this.game.game_modes.map(({ name }) => name).join(', ')
: 'N/A';
},
playerPerspectives() {
return this.game && this.game.player_perspectives
? this.game.player_perspectives.map(({ name }) => name).join(', ')
: 'N/A';
},
ageRatings() {
return this.game && this.game.age_ratings
? this.game.age_ratings.map(({ category, rating }) => `${this.ageRating.categories[category]}: ${this.ageRating.values[rating]}`).join(', ')
: 'N/A';
},
coverUrl() {
return this.game
&& this.game.cover
@ -477,138 +234,19 @@ export default {
});
},
async saveProgress() {
const payload = {
progress: this.localProgress,
gameId: this.gameId,
};
this.$store.commit('SET_GAME_PROGRESS', payload);
await this.$store.dispatch('SAVE_PROGRESSES_LEGACY')
.catch(() => {
this.$bvToast.toast('There was an error saving your progress', { title: 'Error', variant: 'error' });
this.$router.push({ name: 'sessionExpired' });
});
this.$bvToast.toast('Progress updated', { title: 'Success', variant: 'success' });
this.$bvModal.hide('progress');
},
getProgress() {
this.localProgress = this.progresses[this.gameId]
? this.progresses[this.gameId]
: 0;
},
getNotes() {
const { gameId, notes } = this;
this.localNote = notes[gameId] || '';
},
async saveNote() {
const payload = {
note: this.localNote,
gameId: this.gameId,
};
this.$store.commit('SET_GAME_NOTE', payload);
await this.$store.dispatch('SAVE_NOTES_LEGACY')
.catch(() => {
this.$bvToast.toast('There was an error saving your note', { title: 'Error', variant: 'danger' });
this.$router.push({ name: 'sessionExpired' });
});
this.$bvToast.toast('Note saved', { title: 'Success', variant: 'success' });
this.$bvModal.hide('notes');
},
async deleteNote() {
this.$store.commit('REMOVE_GAME_NOTE', this.gameId);
await this.$store.dispatch('SAVE_NOTES_NO_MERGE_LEGACY')
.catch(() => {
this.$bvToast.toast('There was an error deleting your note', { title: 'Error', variant: 'danger' });
this.$router.push({ name: 'sessionExpired' });
});
this.$bvToast.toast('Note deleted', { title: 'Success', variant: 'success' });
this.$bvModal.hide('notes');
},
addGame() {
// TODO: destructure
const data = {
listId: this.listId,
gameId: this.game.id,
};
// this.$emit('added');
this.$store.commit('ADD_GAME_LEGACY', data);
this.$ga.event({
eventCategory: 'game',
eventAction: 'add',
eventLabel: 'addGame',
eventValue: data,
});
this.$store.dispatch('SAVE_LIST_LEGACY', this.gameLists)
.then(() => {
// TODO: customize using cover image
this.$bvToast.toast(`Added ${this.game.name} to list ${this.list.name}`, { title: 'Success', variant: 'success' });
})
.catch(() => {
this.$bvToast.toast('Authentication error', { title: 'Error', variant: 'danger' });
this.$router.push({ name: 'sessionExpired' });
});
},
removeGame() {
const data = {
listId: this.listId,
gameId: this.game.id,
};
this.$store.commit('REMOVE_GAME_LEGACY', data);
this.$store
.dispatch('SAVE_LIST_LEGACY', this.gameLists)
.then(() => {
// TODO customize using cover
this.$bvToast.toast(`Removed ${this.game.name} from list ${this.list.name}`, { title: 'Success', variant: 'success' });
})
.catch(() => {
this.$bvToast.toast('Authentication error', { title: 'Error', variant: 'danger' });
this.$router.push({ name: 'sessionExpired' });
});
},
reset() {
this.gameId = null;
this.listId = null;
this.game = null;
this.game = null;
},
async deleteProgress() {
const { gameId } = this.gameModalData;
this.$store.commit('REMOVE_GAME_PROGRESS', gameId);
await this.$store.dispatch('SAVE_PROGRESSES_NO_MERGE_LEGACY')
.catch(() => {
this.$bvToast.toast('There was an error deleting your progress', { title: 'Error', variant: 'error' });
this.$router.push({ name: 'sessionExpired' });
});
this.$bvToast.toast('Progress deleted', { title: 'Success', variant: 'success' });
this.$bvModal.hide('progress');
this.loading = true;
this.game = {};
},
},
};
</script>
<style lang="scss" rel="stylesheet/scss">
.b-rating {
line-height: normal !important;
height: auto !important;
}
</style>

View file

@ -0,0 +1,113 @@
<template lang="html">
<b-button v-b-modal.notes variant="warning">
<b-icon-file-earmark-text />
<b-modal
id="notes"
title="Game notes"
@show="show"
>
<b-form-textarea
v-model.trim="localNote"
placeholder="Type note here"
rows="3"
max-rows="20"
/>
<b-form-text id="input-live-help">
<a href="https://www.markdownguide.org/cheat-sheet/" target="_blank">
Markdown supported
</a>
</b-form-text>
<template v-slot:modal-footer>
<b-button
variant="danger"
:disabled="deleting"
@click="deleteNote"
>
<b-spinner small v-if="deleting" />
<span v-else>{{ $t('global.delete') }}</span>
</b-button>
<b-button
variant="primary"
:disabled="saving"
@click="saveNote"
>
<b-spinner small v-if="saving" />
<span v-else>{{ $t('global.save') }}</span>
</b-button>
</template>
</b-modal>
</b-button>
</template>
<script>
import { mapState } from 'vuex';
export default {
props: {
game: Object,
},
data() {
return {
localNote: '',
deleting: false,
saving: false,
};
},
computed: {
...mapState(['notes']),
},
methods: {
show() {
this.deleting = false;
this.saving = false;
const { id } = this.game;
this.localNote = this.notes[id]
? JSON.parse(JSON.stringify(this.notes[id]))
: '';
},
async saveNote() {
this.saving = true;
this.$store.commit('SET_GAME_NOTE', {
note: this.localNote,
gameId: this.game.id,
});
await this.$store.dispatch('SAVE_NOTES')
.catch(() => {
this.saving = false;
this.$bvToast.toast('There was an error saving your note', { title: 'Error', variant: 'danger' });
this.$router.push({ name: 'sessionExpired' });
});
this.$bvToast.toast('Note saved', { title: 'Success', variant: 'success' });
this.$bvModal.hide('notes');
},
async deleteNote() {
this.deleting = true;
this.$store.commit('REMOVE_GAME_NOTE', this.game.id);
await this.$store.dispatch('SAVE_NOTES_NO_MERGE')
.catch(() => {
this.deleting = false;
this.$bvToast.toast('There was an error deleting your note', { title: 'Error', variant: 'danger' });
});
this.$bvToast.toast('Note deleted', { title: 'Success', variant: 'success' });
this.$bvModal.hide('notes');
},
},
};
</script>

View file

@ -0,0 +1,38 @@
<template lang="html">
<b-tab>
<template v-slot:title>
Notes <b-badge v-if="notes[game.id]">1</b-badge>
</template>
<template v-if="notes[game.id]">
<vue-markdown :source="notes[game.id]" />
<b-button v-b-modal.notes variant="warning">
Edit note
</b-button>
</template>
<b-button v-else v-b-modal.notes>
Add note
</b-button>
</b-tab>
</template>
<script>
import { mapState } from 'vuex';
import VueMarkdown from 'vue-markdown';
export default {
components: {
VueMarkdown,
},
props: {
game: Object,
},
computed: {
...mapState(['notes']),
},
};
</script>

View file

@ -0,0 +1,116 @@
<template lang="html">
<b-button
v-b-modal.progress
variant="info"
>
<b-icon-check />
<b-modal
id="progress"
title="Set game progress"
@show="show"
>
<b-input-group :prepend="`${localProgress}%`" class="mb-4" size="lg">
<b-form-input
size="lg"
v-model="localProgress"
type="range"
max="100"
step="5"
/>
</b-input-group>
<template v-slot:modal-footer>
<b-button
variant="danger"
:disabled="deleting"
@click="deleteProgress"
>
<b-spinner small v-if="deleting" />
<span v-else>{{ $t('progresses.deleteProgress') }}</span>
</b-button>
<b-button
variant="primary"
:disabled="saving"
@click="saveProgress"
>
<b-spinner small v-if="saving" />
<span v-else>{{ $t('progresses.save') }}</span>
</b-button>
</template>
</b-modal>
</b-button>
</template>
<script>
import { mapState } from 'vuex';
export default {
props: {
game: Object,
},
data() {
return {
localProgress: '0',
saving: false,
deleting: false,
};
},
computed: {
...mapState(['progresses']),
},
methods: {
show() {
const { id } = this.game;
this.localProgress = this.progresses[id]
? JSON.parse(JSON.stringify(this.progresses[id]))
: '0';
this.saving = false;
this.deleting = false;
},
async deleteProgress() {
const { id, name } = this.game;
this.deleting = true;
this.$store.commit('REMOVE_GAME_PROGRESS', id);
await this.$store.dispatch('SAVE_PROGRESSES_NO_MERGE')
.catch(() => {
this.$bvToast.toast('There was an error deleting your progress', { title: `${name} progress`, variant: 'error' });
this.deleting = false;
});
this.$bvToast.toast('Progress deleted', { title: `${name} progress`, variant: 'success' });
this.$bvModal.hide('progress');
},
async saveProgress() {
const { id, name } = this.game;
this.saving = true;
this.$store.commit('SET_GAME_PROGRESS', {
progress: this.localProgress,
gameId: id,
});
await this.$store.dispatch('SAVE_PROGRESSES')
.catch(() => {
this.saving = false;
this.$bvToast.toast('There was an error saving your progress', { title: 'Error', variant: 'error' });
});
this.$bvToast.toast('Progress updated', { title: `${name} progress`, variant: 'success' });
this.$bvModal.hide('progress');
},
},
};
</script>

View file

@ -0,0 +1,41 @@
<template lang="html">
<b-tab v-if="screenshots">
<template v-slot:title>
Screenshots
<b-badge>{{ screenshots.length }}</b-badge>
</template>
<b-img-lazy
v-for="(screenshot, index) in screenshots"
:key="index"
:src="screenshot"
fluid
blank-color="#ccc"
fluid-grow
rounded
class="mb-3"
/>
</b-tab>
</template>
<script>
export default {
props: {
game: Object,
},
data() {
return {
};
},
computed: {
screenshots() {
return this.game && this.game.screenshots
// eslint-disable-next-line
? this.game.screenshots.map(({ image_id }) => `https://images.igdb.com/igdb/image/upload/t_screenshot_huge/${image_id}.jpg`)
: 'N/A';
},
},
};
</script>

View file

@ -24,6 +24,20 @@
</template>
<b-list-group>
<div v-if="empty">
<b-alert show class="mb-2">
No tags
</b-alert>
<b-button
size="sm"
variant="primary"
@click="openTagsSettings"
>
Add a tag
</b-button>
</div>
<b-list-group-item
v-for="({ games, hex, tagTextColor }, name) in tags"
:key="name"
@ -39,7 +53,7 @@
</b-badge>
<b-button
v-if="games.includes(gameId)"
v-if="games.includes(game.id)"
variant="outline-danger"
size="sm"
@click="removeTag(name)"
@ -63,16 +77,19 @@
</template>
<script>
import { mapState, mapGetters } from 'vuex';
import { mapState } from 'vuex';
export default {
props: {
gameId: Number,
game: Object,
},
computed: {
...mapGetters(['gameTags']),
...mapState(['tags', 'games']),
...mapState(['tags']),
empty() {
return Object.keys(this.tags).length === 0;
},
},
methods: {
@ -80,28 +97,29 @@ export default {
this.$bvModal.show('tags-settings');
},
addTag(tagName) {
const { gameId } = this;
async addTag(tagName) {
const gameId = this.game.id;
this.$store.commit('ADD_GAME_TAG', { tagName, gameId });
this.saveTags();
await this.saveTags();
this.$bvToast.toast(`Tag "${tagName}" added`, { title: this.game.name, variant: 'success' });
},
removeTag(tagName) {
const { gameId } = this;
async removeTag(tagName) {
const gameId = this.game.id;
this.$store.commit('REMOVE_GAME_TAG', { tagName, gameId });
this.saveTags();
await this.saveTags();
this.$bvToast.toast(`Tag "${tagName}" removed`, { title: this.game.name, variant: 'success' });
},
async saveTags() {
await this.$store.dispatch('SAVE_TAGS_LEGACY', this.tags)
saveTags() {
this.$store.dispatch('SAVE_TAGS_LEGACY', this.tags)
.catch(() => {
this.$bvToast.toast('Authentication error', { title: 'Error', variant: 'danger' });
this.$router.push({ name: 'sessionExpired' });
});
this.$bvToast.toast('Tags updated', { title: 'Success', variant: 'success' });
},
},
};

View file

@ -0,0 +1,25 @@
<template lang="html">
<b-tab v-if="game.videos">
<template v-slot:title>
Videos
<b-badge>{{ game.videos.length }}</b-badge>
</template>
<b-embed
v-for="{ video_id } in game.videos"
:key="video_id"
type="iframe"
aspect="16by9"
:src="`https://www.youtube.com/embed/${video_id}`"
allowfullscreen
/>
</b-tab>
</template>
<script>
export default {
props: {
game: Object,
},
};
</script>

View file

@ -0,0 +1,48 @@
<template lang="html">
<b-tab v-if="game.websites">
<template v-slot:title>
Links
<b-badge>{{ game.websites.length }}</b-badge>
</template>
<dl class="row mb-0" v-for="link in game.websites" :key="link.id">
<!-- TODO: research which links can be leveraged to get API data,
e.g. wikipedia article, wikia, etc -->
<dt class="col-sm-3">{{ linkTypes[link.category]}}</dt>
<dd class="col-sm-9"><a :href="link.url" target="_blank">{{ link.url }}</a></dd>
</dl>
</b-tab>
</template>
<script>
export default {
props: {
game: Object,
},
data() {
return {
// TODO: GET THIS FROM https://api-v3.igdb.com/igdbapi.proto?
linkTypes: {
1: 'Official site',
2: 'Wikia',
3: 'Wikipedia',
4: 'Facebook',
5: 'Twitter',
6: 'Twitch',
8: 'Instagram',
9: 'YouTube',
10: 'iPhone',
11: 'iPad',
12: 'Android',
13: 'Steam',
14: 'Reddit',
15: 'Itch',
16: 'Epic Games',
17: 'GOG',
18: 'Discord',
},
};
},
};
</script>