mirror of
https://github.com/romancm/gamebrary
synced 2024-11-24 20:23:06 +00:00
Compartmentalize game modal
This commit is contained in:
parent
1e42364643
commit
ae068f27cb
10 changed files with 679 additions and 442 deletions
77
src/components/Game/AddRemoveGame.vue
Normal file
77
src/components/Game/AddRemoveGame.vue
Normal 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>
|
123
src/components/Game/GameDetailsTab.vue
Normal file
123
src/components/Game/GameDetailsTab.vue
Normal 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>
|
|
@ -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>
|
||||
|
|
113
src/components/Game/GameNotes.vue
Normal file
113
src/components/Game/GameNotes.vue
Normal 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>
|
38
src/components/Game/GameNotesTab.vue
Normal file
38
src/components/Game/GameNotesTab.vue
Normal 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>
|
116
src/components/Game/GameProgress.vue
Normal file
116
src/components/Game/GameProgress.vue
Normal 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>
|
41
src/components/Game/GameScreenshotsTab.vue
Normal file
41
src/components/Game/GameScreenshotsTab.vue
Normal 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>
|
|
@ -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' });
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
25
src/components/Game/GameVideosTab.vue
Normal file
25
src/components/Game/GameVideosTab.vue
Normal 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>
|
48
src/components/Game/GameWebsitesTab.vue
Normal file
48
src/components/Game/GameWebsitesTab.vue
Normal 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>
|
Loading…
Reference in a new issue