mirror of
https://github.com/romancm/gamebrary
synced 2024-11-23 11:43:07 +00:00
add vue swatches
This commit is contained in:
parent
c1f3626a72
commit
642718d207
34 changed files with 1025 additions and 1067 deletions
|
@ -42,6 +42,7 @@
|
|||
"vue-raven": "^1.0.0",
|
||||
"vue-router": "^3.5.1",
|
||||
"vue-shortkey": "^3.1.7",
|
||||
"vue-swatches": "^2.1.1",
|
||||
"vue-tweet-embed": "^2.4.0",
|
||||
"vuedraggable": "^2.24.3",
|
||||
"vuefire": "^1.4.5",
|
||||
|
|
|
@ -72,7 +72,7 @@ export default {
|
|||
...mapState(['user', 'settings', 'sessionExpired']),
|
||||
|
||||
style() {
|
||||
const backgroundImage = this.backgroundImageUrl
|
||||
const backgroundImage = this.$route.name === 'game' && this.backgroundImageUrl
|
||||
? `background-image: url('${this.backgroundImageUrl}');`
|
||||
: null;
|
||||
|
||||
|
@ -142,18 +142,18 @@ export default {
|
|||
}
|
||||
|
||||
if (this.user) {
|
||||
this.load();
|
||||
this.boot();
|
||||
} else if (this.$route.name !== 'auth' && !this.$route.params.providerId) {
|
||||
this.$router.replace({ name: 'auth' });
|
||||
}
|
||||
},
|
||||
|
||||
load() {
|
||||
boot() {
|
||||
this.$store.dispatch('LOAD_BOARDS');
|
||||
this.$store.dispatch('LOAD_RELEASES');
|
||||
this.$store.dispatch('LOAD_WALLPAPERS');
|
||||
this.$store.dispatch('SYNC_LOAD_SETTINGS');
|
||||
this.$store.dispatch('SYNC_LOAD_TAGS');
|
||||
this.$store.dispatch('LOAD_TAGS');
|
||||
this.$store.dispatch('SYNC_LOAD_NOTES');
|
||||
this.$store.dispatch('SYNC_LOAD_PROGRESSES');
|
||||
},
|
||||
|
|
|
@ -26,17 +26,12 @@
|
|||
:disabled="saving || isDuplicate || !listName"
|
||||
@click.stop="submit"
|
||||
>
|
||||
<b-spinner small v-if="saving" />
|
||||
<b-spinner v-if="saving" small />
|
||||
<span v-else>Add</span>
|
||||
|
||||
</b-button>
|
||||
<!-- <b-button variant="outline-success">Button</b-button> -->
|
||||
<!-- <b-button variant="info">Button</b-button> -->
|
||||
</b-input-group-append>
|
||||
</b-input-group>
|
||||
|
||||
|
||||
|
||||
<b-alert
|
||||
class="mb-2"
|
||||
:show="isDuplicate && !saving"
|
||||
|
|
|
@ -1,17 +1,7 @@
|
|||
<template lang="html">
|
||||
<div class="text-center pt-5">
|
||||
<div class="text-center pt-5 ml-auto mr-auto">
|
||||
<h2 v-if="title">{{ title }}</h2>
|
||||
<p v-if="message">{{ message }}</p>
|
||||
|
||||
<b-button
|
||||
v-if="actionText"
|
||||
variant="primary"
|
||||
@click="$emit('action')"
|
||||
>
|
||||
<b-spinner small v-if="busy" />
|
||||
<span v-else>{{ actionText }}</span>
|
||||
</b-button>
|
||||
|
||||
<slot />
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
@ -13,11 +13,6 @@
|
|||
<span class="d-none d-lg-inline">Tags</span>
|
||||
</b-button>
|
||||
|
||||
<b-button variant="light" :to="{ name: 'game.progress', params: { id: game.id, slug: game.slug } }">
|
||||
<i class="fa-solid fa-bars-progress fa-fw" />
|
||||
<span class="d-none d-lg-inline">Track progress</span>
|
||||
</b-button>
|
||||
|
||||
<b-button variant="light" :to="{ name: 'game.notes', params: { id: game.id, slug: game.slug } }">
|
||||
<i class="fa-solid fa-note-sticky fa-fw" />
|
||||
<span class="d-none d-lg-inline">Notes</span>
|
||||
|
|
|
@ -1,11 +1,7 @@
|
|||
<!-- TODO: restore wikipedia -->
|
||||
<template lang="html">
|
||||
<div class="game-description">
|
||||
<template v-if="loading">
|
||||
<b-skeleton
|
||||
v-for="n in 3"
|
||||
:key="n"
|
||||
/>
|
||||
</template>
|
||||
<b-spinner v-if="loading" class="spinner-centered" />
|
||||
|
||||
<template v-else>
|
||||
<div v-html="description" />
|
||||
|
@ -51,9 +47,10 @@ export default {
|
|||
if (this.wikipediaExtract) return 'Wikipedia';
|
||||
if (this.steamDescription) return 'Steam';
|
||||
|
||||
return 'IGDB';
|
||||
|
||||
// if (this.game?.steam?.short_description) return 'Steam';
|
||||
if (this.game?.steam?.short_description) return 'Steam';
|
||||
|
||||
return 'IGDB';
|
||||
//
|
||||
// return this.wikipediaArticle && this.wikipediaArticle.lead && this.wikipediaArticle.lead[0]
|
||||
// ? 'Wikipedia'
|
||||
|
|
|
@ -1,34 +1,34 @@
|
|||
<template lang="html">
|
||||
<b-list-group flush>
|
||||
<div class="mt-3">
|
||||
<game-genres />
|
||||
|
||||
<b-list-group-item v-if="gameModes" class="p-2 small">
|
||||
<div v-if="gameModes" class="pr-2 pb-3 small">
|
||||
<strong>{{ $t('board.gameModal.gameModes') }}: </strong>
|
||||
<span class="text-wrap">{{ gameModes }}</span>
|
||||
</b-list-group-item>
|
||||
</div>
|
||||
|
||||
<b-list-group-item v-if="gameDevelopers" class="p-2 small">
|
||||
<div v-if="gameDevelopers" class="pr-2 pb-3 small">
|
||||
<strong>{{ $t('board.gameModal.developers') }}: </strong>
|
||||
<span class="text-wrap">{{ gameDevelopers }}</span>
|
||||
</b-list-group-item>
|
||||
</div>
|
||||
|
||||
<b-list-group-item v-if="gamePublishers" class="p-2 small">
|
||||
<div v-if="gamePublishers" class="pr-2 pb-3 small">
|
||||
<strong>{{ $t('board.gameModal.publishers') }}: </strong>
|
||||
<span class="text-wrap">{{ gamePublishers }}</span>
|
||||
</b-list-group-item>
|
||||
</div>
|
||||
|
||||
<b-list-group-item v-if="playerPerspectives" class="p-2 small">
|
||||
<div v-if="playerPerspectives" class="pr-2 pb-3 small">
|
||||
<strong>{{ $t('board.gameModal.perspective') }}: </strong>
|
||||
<span class="text-wrap">{{ playerPerspectives }}</span>
|
||||
</b-list-group-item>
|
||||
</div>
|
||||
|
||||
<b-list-group-item class="p-2 small">
|
||||
<div class="pr-2 pb-3 small">
|
||||
<strong>Available for: </strong>
|
||||
|
||||
<span class="text-wrap">{{ gamePlatforms || 'N/A' }}</span>
|
||||
</b-list-group-item>
|
||||
</div>
|
||||
|
||||
<b-list-group-item class="p-2 small">
|
||||
<div class="pr-2 pb-3 small">
|
||||
<strong>{{ $t('board.gameModal.releaseDate') }}</strong>
|
||||
<ol v-if="releaseDates" class="list-unstyled mb-0">
|
||||
<li
|
||||
|
@ -42,8 +42,8 @@
|
|||
<div v-else>
|
||||
Not released yet
|
||||
</div>
|
||||
</b-list-group-item>
|
||||
</b-list-group>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
|
|
@ -1,19 +1,17 @@
|
|||
<template lang="html">
|
||||
<b-list-group-item v-if="gameGenres" class="p-2 small">
|
||||
<div v-if="gameGenres" class="pr-2 pb-3 small">
|
||||
<strong>Genres:</strong>
|
||||
<b-avatar
|
||||
|
||||
{{ gameGenres }}
|
||||
|
||||
<!-- <span
|
||||
v-for="genre in gameGenres"
|
||||
:key="genre.id"
|
||||
rounded
|
||||
button
|
||||
variant="transparent"
|
||||
class="mr-1"
|
||||
v-b-tooltip.hover
|
||||
:title="genre.name"
|
||||
>
|
||||
<i v-if="genre.icon" :class="`${genre.icon} p-0`" />
|
||||
</b-avatar>
|
||||
</b-list-group-item>
|
||||
{{ genre.name }}
|
||||
</span> -->
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
@ -27,10 +25,7 @@ export default {
|
|||
gameGenres() {
|
||||
const gameGenres = this.game?.genres || [];
|
||||
|
||||
return gameGenres.map(genre => ({
|
||||
...genre,
|
||||
icon: GENRE_ICONS[genre.id] || null,
|
||||
}));
|
||||
return gameGenres.map((genre) => genre.name).join(', ');
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
|
@ -2,40 +2,42 @@
|
|||
<b-row v-if="user" class="p-1 boards">
|
||||
<!-- TODO: allow reorganizing and save -->
|
||||
<!-- TODO: add sorting -->
|
||||
<empty-state
|
||||
v-if="!user || !loading && sortedBoards.length === 0"
|
||||
title="Boards"
|
||||
message="Use boards to easily organize your video game collections"
|
||||
>
|
||||
<b-button :to="{ name: 'create.board' }">
|
||||
{{ $t('boards.create') }}
|
||||
</b-button>
|
||||
|
||||
<!-- <b-button :to="{ name: 'public.boards' }">
|
||||
View public boards
|
||||
</b-button> -->
|
||||
</empty-state>
|
||||
|
||||
<template v-if="showPlaceholder">
|
||||
Loading
|
||||
</template>
|
||||
|
||||
<template v-else>
|
||||
<b-col
|
||||
v-for="board in sortedBoards"
|
||||
:key="board.id"
|
||||
cols="6"
|
||||
sm="6"
|
||||
md="4"
|
||||
lg="3"
|
||||
class="p-2"
|
||||
<empty-state
|
||||
v-if="!user || !loading && sortedBoards.length === 0"
|
||||
title="Boards"
|
||||
message="Use boards to easily organize your video game collections"
|
||||
>
|
||||
<mini-board
|
||||
:board="board"
|
||||
:background-image="getWallpaperUrl(board.backgroundUrl)"
|
||||
@view-board="viewBoard(board.id)"
|
||||
/>
|
||||
</b-col>
|
||||
<b-button :to="{ name: 'create.board' }">
|
||||
{{ $t('boards.create') }}
|
||||
</b-button>
|
||||
|
||||
<!-- <b-button :to="{ name: 'public.boards' }">
|
||||
View public boards
|
||||
</b-button> -->
|
||||
</empty-state>
|
||||
|
||||
<template v-else>
|
||||
<b-col
|
||||
v-for="board in sortedBoards"
|
||||
:key="board.id"
|
||||
cols="6"
|
||||
sm="6"
|
||||
md="4"
|
||||
lg="3"
|
||||
class="p-2"
|
||||
>
|
||||
<mini-board
|
||||
:board="board"
|
||||
:background-image="getWallpaperUrl(board.backgroundUrl)"
|
||||
@view-board="viewBoard(board.id)"
|
||||
/>
|
||||
</b-col>
|
||||
</template>
|
||||
</template>
|
||||
</b-row>
|
||||
</template>
|
||||
|
|
|
@ -1,35 +1,23 @@
|
|||
<template lang="html">
|
||||
<b-card no-body class="mb-2">
|
||||
<b-row no-gutters>
|
||||
<b-col cols="2">
|
||||
<b-link :to="{ name: 'game', params: { id: game.id, slug: game.slug }}">
|
||||
<b-card-img
|
||||
:src="coverUrl"
|
||||
alt="Image"
|
||||
/>
|
||||
</b-link>
|
||||
</b-col>
|
||||
<b-card
|
||||
no-body
|
||||
:title="game.name"
|
||||
:img-src="coverUrl"
|
||||
:img-alt="game.name"
|
||||
img-top
|
||||
class="mb-2"
|
||||
footer-class="p-0 text-center font-weight-bold bold strong"
|
||||
@click="$router.push({ name: 'game', params: { id: game.id, slug: game.slug }})"
|
||||
>
|
||||
<!-- :to="{ name: 'game', params: { id: game.id, slug: game.slug }}" -->
|
||||
|
||||
<b-col cols="10">
|
||||
<b-card-body :title="game.name" title-tag="h4">
|
||||
<b-button
|
||||
v-if="activeList"
|
||||
@click="$emit('addToActiveList', game.id)"
|
||||
>
|
||||
Add
|
||||
</b-button>
|
||||
|
||||
<b-button
|
||||
v-else
|
||||
@click="handleGameClick"
|
||||
variant="primary"
|
||||
>
|
||||
<i class="fa fa-plus fa-fw" aria-hidden="true" />
|
||||
Add to list
|
||||
</b-button>
|
||||
</b-card-body>
|
||||
</b-col>
|
||||
</b-row>
|
||||
<template #footer>
|
||||
<small class="text-muted">
|
||||
<!-- <pre>{{ selectedBoard }}</pre> -->
|
||||
<!-- <pre>{{ selectedList }}</pre> -->
|
||||
<strong>{{ game.name }}</strong>
|
||||
</small>
|
||||
</template>
|
||||
</b-card>
|
||||
</template>
|
||||
|
||||
|
@ -43,22 +31,71 @@ export default {
|
|||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
activeList: Boolean,
|
||||
},
|
||||
|
||||
computed: {
|
||||
...mapState(['user']),
|
||||
...mapState(['user', 'boards']),
|
||||
|
||||
coverUrl() {
|
||||
return getGameCoverUrl(this.game);
|
||||
},
|
||||
|
||||
selectedBoard() {
|
||||
const { boardId } = this.$route.query;
|
||||
|
||||
return this.boards.find(({ id }) => id === boardId);
|
||||
},
|
||||
|
||||
selectedList() {
|
||||
const { listIndex } = this.$route.query;
|
||||
|
||||
return this.selectedBoard.lists[listIndex];
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
handleGameClick() {
|
||||
console.log(this.user);
|
||||
// handleClick() {
|
||||
// const { listIndex, boardId } = this.$route.query;
|
||||
//
|
||||
// if (listIndex && boardId) return this.addGameToList();
|
||||
//
|
||||
// return this.user
|
||||
// ? this.$bus.$emit('ADD_GAME', this.game.id)
|
||||
// : this.$router.push({ name: 'game', params: { id: this.game.id, slug: this.game.slug }});
|
||||
// },
|
||||
|
||||
// this.$bus.$emit('ADD_GAME', this.game.id);
|
||||
addGameToList() {
|
||||
return this.selectedList.games.includes(this.game.id)
|
||||
? this.removeGame()
|
||||
: this.addGame();
|
||||
},
|
||||
|
||||
async addGame() {
|
||||
const boardIndex = this.boards.findIndex(({ id }) => id === this.selectedBoard.id);
|
||||
const board = this.boards[boardIndex];
|
||||
console.log(board);
|
||||
|
||||
// board.lists[listIndex].games.push(this.game.id);
|
||||
|
||||
// try {
|
||||
// await this.$store.dispatch('SAVE_GAME_BOARD', board);
|
||||
// } catch (e) {
|
||||
// // this.$bvToast.toast(`There was an error adding "${this.game.name}"`, { title: list.name, variant: 'danger' });
|
||||
// }
|
||||
},
|
||||
|
||||
async removeGame({ listIndex, boardId }) {
|
||||
// const boardIndex = this.boards.findIndex(({ id }) => id === boardId);
|
||||
// const board = this.boards[boardIndex];
|
||||
// const gameIndex = board.lists[listIndex].games.indexOf(this.gameId);
|
||||
//
|
||||
// board.lists[listIndex].games.splice(gameIndex, 1);
|
||||
//
|
||||
// try {
|
||||
// await this.$store.dispatch('SAVE_GAME_BOARD', board);
|
||||
// } catch (e) {
|
||||
// // this.$bvToast.toast(`There was an error removing "${this.game.name}"`, { title: list.name, variant: 'danger' });
|
||||
// }
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
|
@ -1,102 +0,0 @@
|
|||
<template lang="html">
|
||||
<b-card
|
||||
no-body
|
||||
:title="game.name"
|
||||
:img-src="coverUrl"
|
||||
:img-alt="game.name"
|
||||
img-top
|
||||
class="mb-2"
|
||||
footer-class="p-0 text-center font-weight-bold bold strong"
|
||||
@click="handleClick"
|
||||
>
|
||||
<!-- :to="{ name: 'game', params: { id: game.id, slug: game.slug }}" -->
|
||||
|
||||
<template #footer>
|
||||
<small class="text-muted">
|
||||
<!-- <pre>{{ selectedBoard }}</pre> -->
|
||||
<!-- <pre>{{ selectedList }}</pre> -->
|
||||
<strong>{{ game.name }}</strong>
|
||||
</small>
|
||||
</template>
|
||||
</b-card>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getGameCoverUrl } from '@/utils';
|
||||
import { mapState } from 'vuex';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
game: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
|
||||
computed: {
|
||||
...mapState(['user', 'boards']),
|
||||
|
||||
coverUrl() {
|
||||
return getGameCoverUrl(this.game);
|
||||
},
|
||||
|
||||
selectedBoard() {
|
||||
const { boardId } = this.$route.query;
|
||||
|
||||
return this.boards.find(({ id }) => id === boardId);
|
||||
},
|
||||
|
||||
selectedList() {
|
||||
const { listIndex } = this.$route.query;
|
||||
|
||||
return this.selectedBoard.lists[listIndex];
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
handleClick() {
|
||||
const { listIndex, boardId } = this.$route.query;
|
||||
|
||||
if (listIndex && boardId) return this.addGameToList();
|
||||
|
||||
return this.user
|
||||
? this.$bus.$emit('ADD_GAME', this.game.id)
|
||||
: this.$router.push({ name: 'game', params: { id: this.game.id, slug: this.game.slug }});
|
||||
},
|
||||
|
||||
addGameToList() {
|
||||
return this.selectedList.games.includes(this.game.id)
|
||||
? this.removeGame()
|
||||
: this.addGame();
|
||||
},
|
||||
|
||||
async addGame() {
|
||||
const boardIndex = this.boards.findIndex(({ id }) => id === this.selectedBoard.id);
|
||||
const board = this.boards[boardIndex];
|
||||
console.log(board);
|
||||
|
||||
// board.lists[listIndex].games.push(this.game.id);
|
||||
|
||||
// try {
|
||||
// await this.$store.dispatch('SAVE_GAME_BOARD', board);
|
||||
// } catch (e) {
|
||||
// // this.$bvToast.toast(`There was an error adding "${this.game.name}"`, { title: list.name, variant: 'danger' });
|
||||
// }
|
||||
},
|
||||
|
||||
async removeGame({ listIndex, boardId }) {
|
||||
// const boardIndex = this.boards.findIndex(({ id }) => id === boardId);
|
||||
// const board = this.boards[boardIndex];
|
||||
// const gameIndex = board.lists[listIndex].games.indexOf(this.gameId);
|
||||
//
|
||||
// board.lists[listIndex].games.splice(gameIndex, 1);
|
||||
//
|
||||
// try {
|
||||
// await this.$store.dispatch('SAVE_GAME_BOARD', board);
|
||||
// } catch (e) {
|
||||
// // this.$bvToast.toast(`There was an error removing "${this.game.name}"`, { title: list.name, variant: 'danger' });
|
||||
// }
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
|
@ -1,11 +1,13 @@
|
|||
<template lang="html">
|
||||
<header class="p-2 d-flex">
|
||||
<home-button />
|
||||
<!-- TODO: rename target -->
|
||||
<portal-target name="headerTitle" slim />
|
||||
<!-- <boards-dropdown v-if="board.id && isBoardPage" /> -->
|
||||
<!-- <game-dropdown v-if="isGamePage" /> -->
|
||||
|
||||
<div class="global-actions">
|
||||
<!-- TODO: rename target -->
|
||||
<portal-target name="headerActions" />
|
||||
|
||||
<!-- <b-button v-if="user" class="mr-2" variant="success" :to="{ name: 'upgrade' }">
|
||||
|
|
|
@ -142,8 +142,4 @@ export default {
|
|||
</script>
|
||||
|
||||
<style lang="scss" rel="stylesheet/scss" scoped>
|
||||
.field {
|
||||
width: 340px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,72 +1,50 @@
|
|||
<template lang="html">
|
||||
<div>
|
||||
<b-card
|
||||
v-for="({ games, hex, tagTextColor }, name) in tags"
|
||||
class="tags-list"
|
||||
<b-row>
|
||||
<b-col
|
||||
v-for="({ hex, tagTextColor, name }, index) in tags"
|
||||
@click="$router.push({ name: 'tag.edit', params: { id: index } })"
|
||||
cols="6"
|
||||
xl="4"
|
||||
class="mb-3"
|
||||
:key="name"
|
||||
>
|
||||
<div>
|
||||
<b-dropdown class="float-right" right>
|
||||
<template v-slot:button-content>
|
||||
<i class="fas fa-ellipsis-h fa-fw" aria-hidden />
|
||||
</template>
|
||||
|
||||
<b-dropdown-item @click="$emit('edit', name)">
|
||||
Edit
|
||||
</b-dropdown-item>
|
||||
|
||||
<b-dropdown-item
|
||||
variant="danger"
|
||||
@click="$emit('delete', name)"
|
||||
>
|
||||
Delete
|
||||
</b-dropdown-item>
|
||||
</b-dropdown>
|
||||
|
||||
<b-badge
|
||||
pill
|
||||
tag="small"
|
||||
:style="`background-color: ${hex}; color: ${tagTextColor}`"
|
||||
>
|
||||
{{ name }}
|
||||
</b-badge>
|
||||
|
||||
<p class="small text-muted">
|
||||
{{ games.length }} Games
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="d-flex align-items-center overflow-auto">
|
||||
<b-img
|
||||
v-for="gameId in games"
|
||||
:key="gameId"
|
||||
:src="getCoverUrl(gameId)"
|
||||
width="80"
|
||||
class="rounded cursor-pointer mr-2"
|
||||
@click.stop="openGame(gameId)"
|
||||
/>
|
||||
</div>
|
||||
</b-card>
|
||||
</div>
|
||||
<b-button
|
||||
rounded
|
||||
block
|
||||
variant="outline-light"
|
||||
:style="`background-color: ${hex}; color: ${tagTextColor}`"
|
||||
>
|
||||
{{ name }}
|
||||
</b-button>
|
||||
</b-col>
|
||||
</b-row>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapState } from 'vuex';
|
||||
import { mapState, mapGetters } from 'vuex';
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
loading: true,
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
...mapState(['tags', 'games']),
|
||||
...mapState(['tags']),
|
||||
},
|
||||
|
||||
async mounted() {
|
||||
this.loading = true;
|
||||
|
||||
await this.$store.dispatch('LOAD_TAGS').catch(() => {
|
||||
this.loading = false;
|
||||
});
|
||||
|
||||
this.loading = false;
|
||||
},
|
||||
|
||||
methods: {
|
||||
getCoverUrl(gameId) {
|
||||
const game = this.games[gameId];
|
||||
|
||||
return game && game.cover && game.cover.image_id
|
||||
? `https://images.igdb.com/igdb/image/upload/t_cover_small_2x/${game.cover.image_id}.jpg`
|
||||
: '/no-image.jpg';
|
||||
},
|
||||
|
||||
openGame(gameId) {
|
||||
const { id, slug } = this.games[gameId];
|
||||
|
||||
|
@ -75,9 +53,3 @@ export default {
|
|||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" rel="stylesheet/scss" scoped>
|
||||
.tags-list {
|
||||
background: #fc0;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -19,6 +19,7 @@ import messages from '@/i18n/';
|
|||
import store from '@/store/';
|
||||
import router from '@/router';
|
||||
import bootstrapSettings from '@/bootstrapSettings';
|
||||
import 'vue-swatches/dist/vue-swatches.css'
|
||||
|
||||
const EventBus = new Vue();
|
||||
|
||||
|
|
|
@ -66,20 +66,4 @@ export default {
|
|||
: '/no-image.jpg';
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
removeTag(tagName) {
|
||||
this.$store.commit('REMOVE_GAME_TAG', { tagName, gameId: this.gameId });
|
||||
this.saveTags();
|
||||
},
|
||||
|
||||
async saveTags() {
|
||||
await this.$store.dispatch('SAVE_TAGS', this.tags)
|
||||
.catch(() => {
|
||||
this.$store.commit('SET_SESSION_EXPIRED', true);
|
||||
});
|
||||
|
||||
this.$bvToast.toast('Tags updated');
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
|
@ -1,64 +1,60 @@
|
|||
<template lang="html">
|
||||
<b-container fluid class="create-board-page pt-3">
|
||||
<b-container fluid>
|
||||
<portal to="headerTitle">Create board</portal>
|
||||
<b-form @submit.prevent="createBoard" class="field">
|
||||
<b-form-group label="Board name:" label-for="boardName">
|
||||
<b-form-input
|
||||
id="boardName"
|
||||
v-model.trim="board.name"
|
||||
placeholder="PS4 collection, Nintendo Switch, etc..."
|
||||
autofocus
|
||||
required
|
||||
/>
|
||||
</b-form-group>
|
||||
|
||||
<b-card class="create-board-form">
|
||||
<b-form @submit.prevent="createBoard">
|
||||
<h3>{{ $t('boards.create') }}</h3>
|
||||
<b-form-group
|
||||
label="Board description"
|
||||
label-for="boardDescription"
|
||||
>
|
||||
<b-form-textarea
|
||||
id="boardDescription"
|
||||
v-model="board.description"
|
||||
maxlength="280"
|
||||
rows="2"
|
||||
/>
|
||||
</b-form-group>
|
||||
|
||||
<b-form-group label="Board name:" label-for="boardName">
|
||||
<b-form-input
|
||||
id="boardName"
|
||||
v-model.trim="board.name"
|
||||
placeholder="e.g. PS4 collection, Nintendo Switch, Xbox..."
|
||||
autofocus
|
||||
required
|
||||
/>
|
||||
</b-form-group>
|
||||
<!-- <b-form-group
|
||||
label="Board template"
|
||||
>
|
||||
<b-form-radio-group
|
||||
v-model="selectedTemplate"
|
||||
:options="boardTemplatesOptions"
|
||||
name="radios-btn-default"
|
||||
description="Optional"
|
||||
/>
|
||||
|
||||
<b-form-group
|
||||
label="Board description"
|
||||
label-for="boardDescription"
|
||||
>
|
||||
<b-form-textarea
|
||||
id="boardDescription"
|
||||
v-model="board.description"
|
||||
maxlength="280"
|
||||
rows="2"
|
||||
/>
|
||||
</b-form-group>
|
||||
<b-row v-if="selectedTemplate" class="mt-3">
|
||||
<b-col v-for="column in boardTemplates[selectedTemplate]" :key="column">
|
||||
<b-card
|
||||
:header="column"
|
||||
header-tag="header"
|
||||
header-class="p-1 pl-2"
|
||||
hide-footer
|
||||
/>
|
||||
</b-col>
|
||||
</b-row>
|
||||
</b-form-group> -->
|
||||
|
||||
<!-- <b-form-group
|
||||
label="Board template"
|
||||
>
|
||||
<b-form-radio-group
|
||||
v-model="selectedTemplate"
|
||||
:options="boardTemplatesOptions"
|
||||
name="radios-btn-default"
|
||||
description="Optional"
|
||||
/>
|
||||
|
||||
<b-row v-if="selectedTemplate" class="mt-3">
|
||||
<b-col v-for="column in boardTemplates[selectedTemplate]" :key="column">
|
||||
<b-card
|
||||
:header="column"
|
||||
header-tag="header"
|
||||
header-class="p-1 pl-2"
|
||||
hide-footer
|
||||
/>
|
||||
</b-col>
|
||||
</b-row>
|
||||
</b-form-group> -->
|
||||
|
||||
<b-button
|
||||
variant="primary"
|
||||
loading
|
||||
type="submit"
|
||||
>
|
||||
<b-spinner small v-if="saving" />
|
||||
<template v-else>Create board</template>
|
||||
</b-button>
|
||||
</b-form>
|
||||
</b-card>
|
||||
<b-button
|
||||
variant="primary"
|
||||
loading
|
||||
type="submit"
|
||||
>
|
||||
<b-spinner small v-if="saving" />
|
||||
<template v-else>Create board</template>
|
||||
</b-button>
|
||||
</b-form>
|
||||
</b-container>
|
||||
</template>
|
||||
|
||||
|
@ -111,14 +107,3 @@ export default {
|
|||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" rel="stylesheet/scss" scoped>
|
||||
.create-board-page {
|
||||
height: calc(100vh - 56px);
|
||||
}
|
||||
|
||||
.create-board-form {
|
||||
max-width: 420px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
</style>
|
||||
|
|
118
src/pages/CreateTagPage.vue
Normal file
118
src/pages/CreateTagPage.vue
Normal file
|
@ -0,0 +1,118 @@
|
|||
<template lang="html">
|
||||
<b-container fluid>
|
||||
<portal to="headerTitle">
|
||||
Create tag
|
||||
</portal>
|
||||
|
||||
<form
|
||||
ref="newTagForm"
|
||||
@submit.stop.prevent="submit"
|
||||
>
|
||||
<b-form-row class="mb-3">
|
||||
<b-col cols="8" md="9">
|
||||
<b-form-input
|
||||
maxlength="20"
|
||||
:placeholder="$t('tags.form.inputPlaceholder')"
|
||||
required
|
||||
v-model.trim="tagName"
|
||||
/>
|
||||
|
||||
<b-form-text v-if="tagName" tag="span">
|
||||
{{ $t('tags.form.preview') }}
|
||||
|
||||
<b-badge :style="`background-color: ${hex}; color: ${tagTextColor}`">
|
||||
{{ tagName }}
|
||||
</b-badge>
|
||||
</b-form-text>
|
||||
</b-col>
|
||||
|
||||
<b-col cols="4" md="3">
|
||||
<b-input-group>
|
||||
<b-form-input
|
||||
v-model="hex"
|
||||
type="color"
|
||||
required
|
||||
/>
|
||||
|
||||
<b-form-input
|
||||
v-model="tagTextColor"
|
||||
type="color"
|
||||
required
|
||||
/>
|
||||
</b-input-group>
|
||||
</b-col>
|
||||
</b-form-row>
|
||||
|
||||
<b-button
|
||||
variant="primary"
|
||||
class="d-flex ml-auto"
|
||||
:disabled="isDuplicate || saving || !Boolean(tagName)"
|
||||
@click="submit"
|
||||
>
|
||||
<b-spinner small v-if="saving" />
|
||||
<span v-else>{{ $t('tags.form.addTag')}}</span>
|
||||
</b-button>
|
||||
|
||||
<b-alert
|
||||
class="mt-3 mb-0"
|
||||
:show="isDuplicate"
|
||||
variant="warning"
|
||||
>
|
||||
{{ $t('tags.form.duplicateMessage', { tagName }) }}
|
||||
<strong>{{ tagName }}</strong>
|
||||
</b-alert>
|
||||
</form>
|
||||
</b-container>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapState } from 'vuex';
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
colorCombinations: [
|
||||
['#0d1137', '#e52165'],
|
||||
['#ffffff', '#000000'],
|
||||
['#101820', '#FEE715'],
|
||||
['#F2AA4C', '#101820'],
|
||||
['#F93822', '#FDD20E'],
|
||||
],
|
||||
tagTextColor: '#F4B41A',
|
||||
tagName: '',
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.setRandomColors();
|
||||
},
|
||||
|
||||
computed: {
|
||||
...mapState(['tags', 'platform', 'games']),
|
||||
// isDuplicate() {
|
||||
// const { tagName, localTags } = this;
|
||||
//
|
||||
// const tagNames = Object.keys(localTags)
|
||||
// .filter(name => name !== tagName)
|
||||
// .map(name => name.toLowerCase());
|
||||
//
|
||||
// return tagNames.includes(tagName.toLowerCase());
|
||||
// },
|
||||
},
|
||||
|
||||
methods: {
|
||||
setRandomColors() {
|
||||
const { colorCombinations } = this;
|
||||
|
||||
const randomNumber = Math.floor(Math.random() * colorCombinations.length);
|
||||
|
||||
this.tagTextColor = colorCombinations[randomNumber][0];
|
||||
this.hex = colorCombinations[randomNumber][1];
|
||||
},
|
||||
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" rel="stylesheet/scss" scoped>
|
||||
</style>
|
|
@ -4,6 +4,10 @@
|
|||
<!-- TODO: show list preview in full page view -->
|
||||
<!-- TODO: show search inline, allow to go full screen (search page) -->
|
||||
<b-container fluid class="p-0">
|
||||
<portal to="headerTitle">
|
||||
Edit list
|
||||
</portal>
|
||||
|
||||
<b-row v-if="list" no-gutters>
|
||||
<b-col sm="12" md="6">
|
||||
<div :style="boardStyles" class="p-3 list-preview d-flex justify-content-center">
|
||||
|
|
236
src/pages/EditTagPage.vue
Normal file
236
src/pages/EditTagPage.vue
Normal file
|
@ -0,0 +1,236 @@
|
|||
<template lang="html">
|
||||
<b-container fluid>
|
||||
<portal to="headerTitle">
|
||||
<div>
|
||||
<b-button
|
||||
variant="light"
|
||||
class="mr-2"
|
||||
:to="{ name: 'tags' }"
|
||||
>
|
||||
<i class="fa-solid fa-chevron-left" />
|
||||
</b-button>
|
||||
|
||||
Edit tag
|
||||
</div>
|
||||
</portal>
|
||||
|
||||
|
||||
<portal to="headerActions">
|
||||
<b-button
|
||||
variant="light"
|
||||
class="mr-2"
|
||||
@click="promptDeleteTag(tag.name)"
|
||||
>
|
||||
<i class="fas fa-trash-alt fa-fw" aria-hidden />
|
||||
</b-button>
|
||||
</portal>
|
||||
|
||||
<div v-if="loading" class="text-center mt-5 ml-auto">
|
||||
<b-spinner/>
|
||||
</div>
|
||||
|
||||
<form
|
||||
v-else
|
||||
ref="form"
|
||||
@submit="saveTag"
|
||||
>
|
||||
<b-alert
|
||||
class="mt-3 mb-0"
|
||||
:show="isEditedNameDuplicate && !saving"
|
||||
variant="warning"
|
||||
>
|
||||
You already have a tag named <strong>{{ tag.name }}</strong>
|
||||
</b-alert>
|
||||
|
||||
<label for="tagName">Tag name:</label>
|
||||
|
||||
<b-form-input
|
||||
id="tagName"
|
||||
v-model.trim="tag.name"
|
||||
class="mb-3 field"
|
||||
maxlength="20"
|
||||
:placeholder="$t('tags.form.inputPlaceholder')"
|
||||
required
|
||||
trim
|
||||
/>
|
||||
|
||||
<p>Background color</p>
|
||||
|
||||
<v-swatches
|
||||
v-model="tag.hex"
|
||||
show-fallback
|
||||
popover-x="left"
|
||||
/>
|
||||
|
||||
<p>Text color</p>
|
||||
|
||||
<v-swatches
|
||||
v-model="tag.tagTextColor"
|
||||
show-fallback
|
||||
popover-x="left"
|
||||
/>
|
||||
|
||||
<p>Preview</p>
|
||||
|
||||
<b-button
|
||||
v-if="tag.name"
|
||||
rounded
|
||||
block
|
||||
size="sm"
|
||||
class="mr-2 mb-2 field"
|
||||
variant="outline-light"
|
||||
:style="`background-color: ${tag.hex}; color: ${tag.tagTextColor}`"
|
||||
>
|
||||
{{ tag.name }}
|
||||
</b-button>
|
||||
|
||||
<hr />
|
||||
|
||||
<p>Games tagged</p>
|
||||
|
||||
<div class="tagged-games">
|
||||
<b-img
|
||||
v-for="game in tag.games"
|
||||
:key="game"
|
||||
:src="getCoverUrl(game)"
|
||||
class="cursor-pointer"
|
||||
thumbnail
|
||||
@click="$router.push({ name: 'game', params: { id: games[game].id, slug: games[game].slug }})"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<hr />
|
||||
|
||||
<b-button
|
||||
variant="primary"
|
||||
:disabled="isEditedNameDuplicate || saving"
|
||||
type="submit"
|
||||
>
|
||||
<b-spinner small v-if="saving" />
|
||||
<span v-else>Save</span>
|
||||
</b-button>
|
||||
</form>
|
||||
</b-container>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import VSwatches from 'vue-swatches'
|
||||
import { mapState } from 'vuex';
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
tag: {},
|
||||
loading: true,
|
||||
originalTagName: '',
|
||||
localTags: {},
|
||||
saving: false,
|
||||
}
|
||||
},
|
||||
|
||||
components: {
|
||||
VSwatches,
|
||||
},
|
||||
|
||||
computed: {
|
||||
...mapState(['tags', 'games']),
|
||||
|
||||
tagNames() {
|
||||
const sanitizedNames = this.tags?.map(({ name }) => name.toLowerCase());
|
||||
|
||||
return sanitizedNames.length > 0
|
||||
? sanitizedNames.filter(name => name?.toLowerCase() !== this.originalTagName?.toLowerCase())
|
||||
: [];
|
||||
},
|
||||
|
||||
isEditedNameDuplicate() {
|
||||
return this.tagNames?.includes(this.tag?.name?.toLowerCase());
|
||||
},
|
||||
|
||||
tagIndex() {
|
||||
return this.$route?.params?.id;
|
||||
},
|
||||
},
|
||||
|
||||
async mounted() {
|
||||
this.load();
|
||||
},
|
||||
|
||||
methods: {
|
||||
getCoverUrl(gameId) {
|
||||
const game = this.games[gameId];
|
||||
|
||||
return game?.cover?.image_id
|
||||
? `https://images.igdb.com/igdb/image/upload/t_cover_small_2x/${game.cover.image_id}.jpg`
|
||||
: '/no-image.jpg';
|
||||
},
|
||||
|
||||
async load() {
|
||||
this.loading = true;
|
||||
await this.$store.dispatch('LOAD_TAGS');
|
||||
|
||||
const { tags, tagIndex } = this;
|
||||
|
||||
this.tag = JSON.parse(JSON.stringify(tags[tagIndex]));
|
||||
this.originalTagName = JSON.parse(JSON.stringify(this.tag.name));
|
||||
|
||||
this.loading = false;
|
||||
},
|
||||
|
||||
promptDeleteTag(tagName) {
|
||||
this.$bvModal.msgBoxConfirm(this.$t('tags.delete.message'), {
|
||||
title: this.$t('tags.delete.title'),
|
||||
okVariant: 'danger',
|
||||
okTitle: this.$t('tags.delete.buttonLabel'),
|
||||
cancelTitle: this.$t('global.cancel'),
|
||||
headerClass: 'pb-0 border-0',
|
||||
footerClass: 'pt-0 border-0',
|
||||
})
|
||||
.then((value) => {
|
||||
if (value) {
|
||||
this.deleteTag(tagName);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
deleteTag(tagName) {
|
||||
this.$delete(this.localTags, tagName);
|
||||
this.saveTags(true);
|
||||
},
|
||||
|
||||
removeTag(tagName) {
|
||||
this.$store.commit('REMOVE_GAME_TAG', { tagName, gameId: this.gameId });
|
||||
this.saveTags();
|
||||
},
|
||||
|
||||
async saveTag(e) {
|
||||
e.preventDefault();
|
||||
|
||||
if (this.$refs.form.checkValidity()) {
|
||||
const { tag, tags, originalTagName } = this;
|
||||
|
||||
tags[this.tagIndex] = tag;
|
||||
|
||||
console.log(tags);
|
||||
|
||||
// await this.$store.dispatch('SAVE_TAGS', tags)
|
||||
// .catch(() => {
|
||||
// this.$store.commit('SET_SESSION_EXPIRED', true);
|
||||
// });
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" rel="stylesheet/scss" scoped>
|
||||
.tagged-games {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill,minmax(80px, 1fr));
|
||||
grid-auto-flow: column;
|
||||
grid-auto-columns: minmax(80px, 1fr);
|
||||
grid-gap: .5rem;
|
||||
overflow-x: auto;
|
||||
max-width: calc(100vw - 32px);
|
||||
}
|
||||
</style>
|
|
@ -8,7 +8,9 @@
|
|||
|
||||
<template lang="html">
|
||||
<b-container fluid>
|
||||
<b-skeleton v-if="loading" />
|
||||
<div v-if="loading" class="text-center mt-5 ml-auto">
|
||||
<b-spinner/>
|
||||
</div>
|
||||
|
||||
<template v-else-if="game">
|
||||
<game-actions />
|
||||
|
@ -73,33 +75,56 @@
|
|||
sm="8"
|
||||
xl="9"
|
||||
>
|
||||
<div class="bg-white p-4 rounded">
|
||||
<game-titles />
|
||||
<article class="bg-white p-4 rounded">
|
||||
<header class="d-flex align-items-start justify-content-between pb-2">
|
||||
<game-titles />
|
||||
|
||||
<b-progress
|
||||
v-if="progress"
|
||||
:value="progress"
|
||||
variant="success"
|
||||
height="8px"
|
||||
v-b-modal.progress
|
||||
class="my-1 w-25"
|
||||
@click.native="$router.push({ name: 'game.notes', params: { id: game.id, slug: game.slug } })"
|
||||
/>
|
||||
<b-badge variant="success" v-if="game && game.steam && game.steam.metacritic">{{ game.steam.metacritic.score }}</b-badge>
|
||||
<aside>
|
||||
<b-button
|
||||
variant="light"
|
||||
pill
|
||||
@click="$router.push({ name: 'game.progress', params: { id: game.id, slug: game.slug } })"
|
||||
>
|
||||
{{ progress || 0 }}%
|
||||
</b-button>
|
||||
|
||||
<b-badge
|
||||
v-for="({ hex, tagTextColor }, name) in gameTags"
|
||||
<!-- <b-button :href="metacriticScore.url" variant="success" v-if="metacriticScore.url">
|
||||
{{ metacriticScore.score }}
|
||||
|
||||
</b-button> -->
|
||||
</aside>
|
||||
</header>
|
||||
|
||||
<b-button
|
||||
v-for="({ hex, tagTextColor, name }) in tags"
|
||||
:key="name"
|
||||
pill
|
||||
tag="small"
|
||||
class="mr-1 mb-2"
|
||||
rounded
|
||||
size="sm"
|
||||
variant="outline-light"
|
||||
class="mr-1 my-2"
|
||||
:disabled="saving"
|
||||
:style="`background-color: ${hex}; color: ${tagTextColor}`"
|
||||
@click="$router.push({ name: 'game.tags', params: { id: game.id, slug: game.slug } })"
|
||||
v-b-modal.tags
|
||||
>
|
||||
{{ name }}
|
||||
</b-badge>
|
||||
</b-button>
|
||||
|
||||
<aside class="bg-white float-right pl-2 pb-2">
|
||||
<b-link
|
||||
:to="{ name: 'game.media', params: { id: game.id, slug: game.slug } }"
|
||||
>
|
||||
<b-img
|
||||
:src="gameScrenshot"
|
||||
thumbnail
|
||||
width="300"
|
||||
/>
|
||||
</b-link>
|
||||
|
||||
<game-websites :game="game" />
|
||||
</aside>
|
||||
|
||||
<game-description />
|
||||
<game-details />
|
||||
|
||||
<game-note
|
||||
v-if="note"
|
||||
|
@ -108,35 +133,14 @@
|
|||
@click.native="$router.push({ name: 'game.notes', params: { id: game.id } })"
|
||||
/>
|
||||
|
||||
<b-card
|
||||
no-body
|
||||
>
|
||||
<b-link :to="{ name: 'game.media', params: { id: game.id, slug: game.slug } }">
|
||||
<b-card-img :src="gameScrenshot" top />
|
||||
</b-link>
|
||||
<b-button
|
||||
class="m-1"
|
||||
variant="light"
|
||||
:to="{ name: 'game.media', params: { id: game.id, slug: game.slug } }"
|
||||
>
|
||||
<i class="fa-solid fa-photo-film" />
|
||||
Videos & Screenshots
|
||||
</b-button>
|
||||
<b-card-footer v-if="legalNotice">
|
||||
<small class="text-muted" v-html="legalNotice" />
|
||||
</b-card-footer>
|
||||
|
||||
<game-details />
|
||||
<game-websites
|
||||
:game="game"
|
||||
/>
|
||||
|
||||
<b-card-footer v-if="legalNotice">
|
||||
<small class="text-muted" v-html="legalNotice" />
|
||||
</b-card-footer>
|
||||
|
||||
<!-- TODO: use speedrun logo -->
|
||||
<!-- <pre>{{ game}}</pre> -->
|
||||
<!-- <b-card-img src="https://placekitten.com/480/210" alt="Image" bottom></b-card-img> -->
|
||||
</b-card>
|
||||
</div>
|
||||
<!-- TODO: use speedrun logo -->
|
||||
<!-- <pre>{{ game}}</pre> -->
|
||||
<!-- <b-card-img src="https://placekitten.com/480/210" alt="Image" bottom></b-card-img> -->
|
||||
</article>
|
||||
</b-col>
|
||||
|
||||
<b-col
|
||||
|
@ -301,7 +305,7 @@ export default {
|
|||
},
|
||||
|
||||
beforeDestroy() {
|
||||
this.$bus.$emit('UPDATE_WALLPAPER', null);
|
||||
// this.$bus.$emit('UPDATE_WALLPAPER', null);
|
||||
// TODO: only clear board if game being viewed is not in current board
|
||||
// if (!['game', 'board'].includes(this.$route.name)) {
|
||||
// this.$store.commit('CLEAR_BOARD');
|
||||
|
@ -310,7 +314,10 @@ export default {
|
|||
|
||||
computed: {
|
||||
...mapState(['game', 'progresses', 'tags', 'boards', 'user', 'notes']),
|
||||
...mapGetters(['gameTags']),
|
||||
|
||||
metacriticScore() {
|
||||
return this.game?.steam?.metacritic || {};
|
||||
},
|
||||
|
||||
note() {
|
||||
return this.notes[this.game?.id] || null;
|
||||
|
@ -444,6 +451,7 @@ export default {
|
|||
this.loading = true;
|
||||
this.$store.commit('CLEAR_GAME');
|
||||
this.$bus.$emit('UPDATE_WALLPAPER', null);
|
||||
this.$store.dispatch('LOAD_TAGS');
|
||||
|
||||
await this.$store.dispatch('LOAD_GAME', this.gameId)
|
||||
.catch(() => {
|
||||
|
@ -513,4 +521,9 @@ export default {
|
|||
background-size: cover;
|
||||
background-position: center;
|
||||
}
|
||||
|
||||
article {
|
||||
background: red;
|
||||
min-height: 50vh;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -2,8 +2,14 @@
|
|||
<b-container>
|
||||
<portal to="headerTitle">
|
||||
<span>
|
||||
{{ game.name }} |
|
||||
<span class="text-muted">Track progress</span>
|
||||
<b-button
|
||||
:to="{ name: 'game', params: { id: game.id, slug: game.slug }}"
|
||||
variant="light"
|
||||
>
|
||||
{{ game.name }}
|
||||
</b-button>
|
||||
|
||||
Track progress
|
||||
</span>
|
||||
</portal>
|
||||
|
||||
|
|
|
@ -1,65 +1,97 @@
|
|||
<!-- TODO: finish layout -->
|
||||
<template lang="html">
|
||||
<b-container class="p-2">
|
||||
<b-container fluid>
|
||||
<portal to="headerTitle">
|
||||
<span>
|
||||
{{ game.name }} |
|
||||
<span class="text-muted">Tags</span>
|
||||
</span>
|
||||
<div>
|
||||
<b-button
|
||||
:to="gamePage"
|
||||
variant="light"
|
||||
class="mr-2"
|
||||
>
|
||||
<i v-if="showBackIcon" class="fa-solid fa-chevron-left" />
|
||||
<template v-else-if="game">
|
||||
{{ game.name }}
|
||||
</template>
|
||||
</b-button>
|
||||
|
||||
Tags
|
||||
</div>
|
||||
</portal>
|
||||
|
||||
<template v-if="loading">
|
||||
loading
|
||||
</template>
|
||||
|
||||
<template v-else>
|
||||
<router-link :to="{ name: 'game', params: { id: game.id, slug: game.slug }}">
|
||||
<b-img :src="gameCoverUrl" width="200" rounded class="mb-2 mr-2" />
|
||||
</router-link>
|
||||
|
||||
<h3>Tags</h3>
|
||||
<p>Click on tag to add or remove tag from game</p>
|
||||
|
||||
<empty-state
|
||||
v-if="empty"
|
||||
class="mb-4"
|
||||
message="Looks like you don't have any tags yet."
|
||||
<portal to="headerActions">
|
||||
<b-button
|
||||
:to="{ name: 'tags' }"
|
||||
variant="light"
|
||||
class="mr-2"
|
||||
>
|
||||
<b-button @click="manageTags">Manage tags</b-button>
|
||||
</empty-state>
|
||||
Manage tags
|
||||
</b-button>
|
||||
</portal>
|
||||
|
||||
<b-row v-else>
|
||||
<!-- TODO: Show current games in tag -->
|
||||
<!-- TODO: Filter tag option if tags > too many -->
|
||||
<b-col cols="12" md="auto">
|
||||
<b-list-group>
|
||||
<b-list-group-item
|
||||
v-for="({ games, hex, tagTextColor }, name) in sortedTags"
|
||||
:key="name"
|
||||
class="d-flex justify-content-between"
|
||||
button
|
||||
:variant="games.includes(game.id) ? 'success' : ''"
|
||||
@click="games.includes(game.id) ? removeTag(name) : addTag(name)"
|
||||
<div v-if="loading" class="text-center mt-5 ml-auto">
|
||||
<b-spinner/>
|
||||
</div>
|
||||
|
||||
<b-row v-else>
|
||||
<b-col cols="6">
|
||||
<router-link :to="{ name: 'game', params: { id: game.id, slug: game.slug }}" class="float-right">
|
||||
<b-img :src="gameCoverUrl" fluid rounded class="mb-2 mr-2 field" />
|
||||
</router-link>
|
||||
</b-col>
|
||||
|
||||
<b-col>
|
||||
<empty-state
|
||||
v-if="empty"
|
||||
class="mb-4"
|
||||
message="Looks like you don't have any tags yet."
|
||||
>
|
||||
<b-button @click="manageTags">Manage tags</b-button>
|
||||
</empty-state>
|
||||
|
||||
<section class="field">
|
||||
<section>
|
||||
<h3 class="mb-3">Tags applied to {{ game.name }}</h3>
|
||||
|
||||
<b-alert
|
||||
v-if="tagsSelected.length === 0"
|
||||
show
|
||||
variant="light"
|
||||
>
|
||||
<b-badge
|
||||
pill
|
||||
tag="small"
|
||||
class="mr-3"
|
||||
:style="`background-color: ${hex}; color: ${tagTextColor}`"
|
||||
>
|
||||
{{ name }}
|
||||
</b-badge>
|
||||
No tags applied
|
||||
</b-alert>
|
||||
|
||||
<b-badge variant="light">{{ games.length }} games</b-badge>
|
||||
</b-list-group-item>
|
||||
</b-list-group>
|
||||
</b-col>
|
||||
</b-row>
|
||||
</template>
|
||||
<b-button
|
||||
v-for="{ name, hex, tagTextColor } in tags"
|
||||
:key="name"
|
||||
rounded
|
||||
block
|
||||
variant="outline-light"
|
||||
:style="`background-color: ${hex}; color: ${tagTextColor}`"
|
||||
@click="removeTag"
|
||||
>
|
||||
{{ name }}
|
||||
</b-button>
|
||||
</section>
|
||||
|
||||
<b-button :to="{ name: 'team.settings' }">Manage tags</b-button>
|
||||
<br />
|
||||
<b-button :to="{ name: 'team.settings' }">Create new tag</b-button>
|
||||
<hr />
|
||||
|
||||
<h3 class="my-3">Tags available</h3>
|
||||
|
||||
<pre>{{ tags }}</pre>
|
||||
<!-- <b-button
|
||||
v-for="({ name, hex, tagTextColor }, index) in tags"
|
||||
:key="name"
|
||||
rounded
|
||||
block
|
||||
variant="outline-light"
|
||||
:disabled="saving"
|
||||
:style="`background-color: ${hex}; color: ${tagTextColor}`"
|
||||
@click="addTag(index)"
|
||||
>
|
||||
{{ name }}
|
||||
</b-button> -->
|
||||
</section>
|
||||
</b-col>
|
||||
</b-row>
|
||||
</b-container>
|
||||
</template>
|
||||
|
||||
|
@ -76,6 +108,7 @@ export default {
|
|||
data() {
|
||||
return {
|
||||
loading: true,
|
||||
saving: false,
|
||||
};
|
||||
},
|
||||
|
||||
|
@ -86,19 +119,38 @@ export default {
|
|||
return getGameCoverUrl(this.game);
|
||||
},
|
||||
|
||||
showBackIcon() {
|
||||
return this.game?.name.length > 25;
|
||||
},
|
||||
|
||||
gamePage() {
|
||||
return { name: 'game', params: { id: this.game?.id, slug: this.game?.slug }};
|
||||
},
|
||||
|
||||
empty() {
|
||||
return Object.keys(this.tags).length === 0;
|
||||
},
|
||||
|
||||
sortedTags() {
|
||||
return Object.keys(this.tags)
|
||||
.sort()
|
||||
.reduce((res, key) => (res[key] = this.tags[key], res), {});
|
||||
tagsSelected() {
|
||||
return this.tags?.filter(({ games }) => {
|
||||
return games?.includes(this.game?.id);
|
||||
})
|
||||
},
|
||||
|
||||
tagsAvailable() {
|
||||
return Object.entries(this.tags).map((t) => {
|
||||
const [name, tag] = t;
|
||||
|
||||
return { name: name, ...tag };
|
||||
})
|
||||
.filter(({ games }) => {
|
||||
return !games.includes(this.game.id);
|
||||
})
|
||||
},
|
||||
},
|
||||
|
||||
mounted() {
|
||||
if (this.game.id !== this.$route.params.id) {
|
||||
if (this.game?.id !== this.$route.params.id) {
|
||||
this.loadGame();
|
||||
} else {
|
||||
this.loading = false;
|
||||
|
@ -110,7 +162,8 @@ export default {
|
|||
this.loading = true;
|
||||
this.$store.commit('CLEAR_GAME');
|
||||
|
||||
await this.$store.dispatch('LOAD_GAME', this.$route.params.id)
|
||||
await this.$store.dispatch('LOAD_GAME', this.$route.params.id);
|
||||
await this.$store.dispatch('LOAD_TAGS')
|
||||
.catch(() => {
|
||||
this.loading = false;
|
||||
});
|
||||
|
@ -118,39 +171,43 @@ export default {
|
|||
this.loading = false;
|
||||
},
|
||||
|
||||
async addTag(tagName) {
|
||||
const gameId = this.game.id;
|
||||
async addTag(index) {
|
||||
console.log('add this tag');
|
||||
// TODO: use commit instead?
|
||||
// const gameId = this.game.id;
|
||||
//
|
||||
// if (!gameId) return;
|
||||
//
|
||||
// const tags = JSON.parse(JSON.stringify(this.tags)) ;
|
||||
//
|
||||
// tags[index].games.push(gameId)
|
||||
//
|
||||
// console.log(`game id ${gameId} should be included`, tags[index].games);
|
||||
|
||||
this.$store.commit('ADD_GAME_TAG', { tagName, gameId });
|
||||
await this.saveTags();
|
||||
// this.saving = true;
|
||||
|
||||
this.$bvToast.toast(`Tag "${tagName}" added`, { title: this.game.name, variant: 'success' });
|
||||
// await this.$store.dispatch('SAVE_TAGS', tags)
|
||||
// .catch((e) => {
|
||||
// console.log(e);
|
||||
// });
|
||||
|
||||
// this.saving = false;
|
||||
|
||||
// this.$store.commit('ADD_GAME_TAG', { tagName, gameId });
|
||||
// await this.saveTags();
|
||||
},
|
||||
|
||||
async removeTag(tagName) {
|
||||
const gameId = this.game.id;
|
||||
|
||||
this.$store.commit('REMOVE_GAME_TAG', { tagName, gameId });
|
||||
await this.saveTags();
|
||||
|
||||
this.$bvToast.toast(`Tag "${tagName}" removed`, { title: this.game.name, variant: 'success' });
|
||||
},
|
||||
|
||||
saveTags() {
|
||||
this.$store.dispatch('SAVE_TAGS', this.tags)
|
||||
.catch(() => {
|
||||
this.$store.commit('SET_SESSION_EXPIRED', true);
|
||||
});
|
||||
// this.$store.commit('REMOVE_GAME_TAG', { tagName, gameId });
|
||||
// await this.saveTags();
|
||||
},
|
||||
|
||||
manageTags() {
|
||||
this.$bvModal.hide('tags');
|
||||
this.$bvModal.hide('game-modal');
|
||||
this.$router.push({ name: 'tags' });
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" rel="stylesheet/scss" scoped>
|
||||
</style>
|
||||
|
|
|
@ -2,8 +2,19 @@
|
|||
<!-- TODO: pagination? -->
|
||||
<template lang="html">
|
||||
<b-container fluid>
|
||||
<portal to="headerTitle">Notes</portal>
|
||||
<portal to="headerTitle">
|
||||
<div>
|
||||
<b-button
|
||||
:to="{ name: 'settings' }"
|
||||
variant="light"
|
||||
class="mr-2"
|
||||
>
|
||||
<i class="fa-solid fa-chevron-left" />
|
||||
</b-button>
|
||||
|
||||
Notes
|
||||
</div>
|
||||
</portal>
|
||||
<portal to="headerActions">
|
||||
<b-form-input
|
||||
v-if="!showEmptyState"
|
||||
|
|
|
@ -46,37 +46,16 @@
|
|||
|
||||
<div v-else-if="searchResults.length > 0">
|
||||
<header class="my-2 d-flex align-items-center justify-content-between">
|
||||
<pre>{{ activeBoardList }}</pre>
|
||||
<!-- <pre>{{ activeBoardList }}</pre> -->
|
||||
<h3 v-if="activeBoardList.length">
|
||||
Add games to <strong>{{ activeBoardList.name }}</strong>
|
||||
</h3>
|
||||
<!-- <h3>Search results</h3> -->
|
||||
|
||||
<b-button-toolbar key-nav aria-label="Toolbar with button groups" class="mr-1">
|
||||
<b-button-group class="mx-1">
|
||||
<b-button :variant="listView ? 'primary' : 'light'" @click="listView = true">
|
||||
<i class="fa-solid fa-list fa-fw" aria-hidden />
|
||||
</b-button>
|
||||
|
||||
<b-button :variant="listView ? 'light' : 'primary'" @click="listView = false">
|
||||
<i class="fa-solid fa-grip fa-fw" aria-hidden />
|
||||
</b-button>
|
||||
</b-button-group>
|
||||
</b-button-toolbar>
|
||||
</header>
|
||||
|
||||
<template v-if="listView">
|
||||
<div class="masonry-container">
|
||||
<game-card-search
|
||||
v-for="game in searchResults"
|
||||
:key="game.id"
|
||||
:game="game"
|
||||
:active-list="Boolean(activeBoardList)"
|
||||
@addToActiveList="addToActiveList"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<div v-else class="masonry-container">
|
||||
<game-card-search-vertical
|
||||
v-for="game in searchResults"
|
||||
class="masonry-item"
|
||||
:key="game.id"
|
||||
|
@ -100,23 +79,20 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import GameCardSearch from '@/components/GameCards/GameCardSearch';
|
||||
import SearchBox from '@/components/SearchBox';
|
||||
import GameCardSearchVertical from '@/components/GameCards/GameCardSearchVertical';
|
||||
import GameCardSearch from '@/components/GameCards/GameCardSearch';
|
||||
import { mapState } from 'vuex';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
GameCardSearch,
|
||||
SearchBox,
|
||||
GameCardSearchVertical,
|
||||
GameCardSearch,
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
searchResults: [],
|
||||
loading: false,
|
||||
listView: true,
|
||||
};
|
||||
},
|
||||
|
||||
|
|
|
@ -3,108 +3,109 @@
|
|||
<portal to="headerTitle">Settings</portal>
|
||||
|
||||
<b-row>
|
||||
<b-col cols="12" sm="10" md="8" lg="6">
|
||||
<b-col>
|
||||
<div class="field">
|
||||
<settings-card
|
||||
title="Wallpapers"
|
||||
description="Manage your wallpapers"
|
||||
icon="fa-images"
|
||||
@click.native="$router.push({ name: 'wallpapers' })"
|
||||
/>
|
||||
|
||||
<settings-card
|
||||
title="Notes"
|
||||
description="View all your notes"
|
||||
icon="fa-note-sticky"
|
||||
@click.native="$router.push({ name: 'notes' })"
|
||||
/>
|
||||
|
||||
<settings-card
|
||||
title="Tags"
|
||||
description="View all your tags"
|
||||
icon="fa-tags"
|
||||
@click.native="$router.push({ name: 'tags' })"
|
||||
/>
|
||||
|
||||
<settings-card
|
||||
title="Account"
|
||||
description="Manage your Gamebrary account"
|
||||
icon="fa-user"
|
||||
@click.native="$router.push({ name: 'account.settings' })"
|
||||
/>
|
||||
|
||||
<!-- TODO: fix and reenable -->
|
||||
<!-- <b-button
|
||||
block
|
||||
variant="secondary"
|
||||
v-b-modal.keyboard-shortcuts
|
||||
>
|
||||
Keyboard shortcuts
|
||||
</b-button> -->
|
||||
|
||||
<!-- <b-button
|
||||
block
|
||||
variant="secondary"
|
||||
:to="{ name: 'dev.tools' }"
|
||||
>
|
||||
Dev tools
|
||||
</b-button> -->
|
||||
|
||||
<b-button
|
||||
href="https://github.com/romancm/gamebrary"
|
||||
target="_blank"
|
||||
block
|
||||
variant="secondary"
|
||||
>
|
||||
<i class="fab fa-github fa-fw" />
|
||||
GitHub
|
||||
</b-button>
|
||||
|
||||
<b-button
|
||||
block
|
||||
variant="secondary"
|
||||
href="https://goo.gl/forms/r0juBCsZaUtJ03qb2"
|
||||
target="_blank"
|
||||
>
|
||||
Submit feedback
|
||||
</b-button>
|
||||
|
||||
<!-- TODO: hide for paid users -->
|
||||
<b-button
|
||||
block
|
||||
variant="outline-primary"
|
||||
href="https://www.paypal.me/RomanCervantes/5"
|
||||
target="_blank"
|
||||
>
|
||||
Buy me a coffee
|
||||
</b-button>
|
||||
|
||||
<hr />
|
||||
|
||||
<!-- <b-list-group-item exact exact-active-class="bg-primary text-white" :to="{ name: 'profile.settings' }">
|
||||
<i class="mr-2 fa-solid fa-user fa-fw" aria-hidden />
|
||||
<small>Profile</small>
|
||||
</b-list-group-item> -->
|
||||
|
||||
<!-- <b-list-group-item exact exact-active-class="bg-primary text-white" :to="{ name: 'steam.settings' }">
|
||||
<i class="mr-2 fab fa-steam fa-fw" aria-hidden></i>
|
||||
<small>Steam</small>
|
||||
</b-list-group-item> -->
|
||||
|
||||
<!-- <hr /> -->
|
||||
|
||||
<!-- <b-list-group-item :to="{ name: 'profiles' }">
|
||||
<i class="mr-2 fa-solid fa-people-group fa-fw" aria-hidden />
|
||||
<small>Profiles</small>
|
||||
</b-list-group-item> -->
|
||||
|
||||
<!-- {{ $t('global.donateMessage') }} -->
|
||||
<!-- <a href="https://www.paypal.me/RomanCervantes/5" target="_blank">
|
||||
{{ $t('global.donating') }}
|
||||
</a> -->
|
||||
|
||||
<small>© 2022 Gamebrary</small>
|
||||
</div>
|
||||
<!-- <language-settings /> -->
|
||||
|
||||
<settings-card
|
||||
title="Wallpapers"
|
||||
description="Manage your wallpapers"
|
||||
icon="fa-images"
|
||||
@click.native="$router.push({ name: 'wallpapers' })"
|
||||
/>
|
||||
|
||||
<settings-card
|
||||
title="Notes"
|
||||
description="View all your notes"
|
||||
icon="fa-note-sticky"
|
||||
@click.native="$router.push({ name: 'notes' })"
|
||||
/>
|
||||
|
||||
<settings-card
|
||||
title="Tags"
|
||||
description="View all your tags"
|
||||
icon="fa-tags"
|
||||
@click.native="$router.push({ name: 'tags' })"
|
||||
/>
|
||||
|
||||
<settings-card
|
||||
title="Account"
|
||||
description="Manage your Gamebrary account"
|
||||
icon="fa-user"
|
||||
@click.native="$router.push({ name: 'account.settings' })"
|
||||
/>
|
||||
|
||||
<!-- TODO: fix -->
|
||||
<b-button
|
||||
block
|
||||
variant="secondary"
|
||||
v-b-modal.keyboard-shortcuts
|
||||
>
|
||||
Keyboard shortcuts
|
||||
</b-button>
|
||||
|
||||
<b-button
|
||||
block
|
||||
variant="secondary"
|
||||
:to="{ name: 'dev.tools' }"
|
||||
>
|
||||
Dev tools
|
||||
</b-button>
|
||||
|
||||
<b-button
|
||||
href="https://github.com/romancm/gamebrary"
|
||||
target="_blank"
|
||||
block
|
||||
variant="secondary"
|
||||
>
|
||||
<i class="fab fa-github fa-fw" />
|
||||
GitHub
|
||||
</b-button>
|
||||
|
||||
<b-button
|
||||
block
|
||||
variant="secondary"
|
||||
href="https://goo.gl/forms/r0juBCsZaUtJ03qb2"
|
||||
target="_blank"
|
||||
>
|
||||
Submit feedback
|
||||
</b-button>
|
||||
|
||||
<!-- TODO: hide for paid users -->
|
||||
<b-button
|
||||
block
|
||||
variant="outline-primary"
|
||||
href="https://www.paypal.me/RomanCervantes/5"
|
||||
target="_blank"
|
||||
>
|
||||
Buy me a coffee
|
||||
</b-button>
|
||||
|
||||
<hr />
|
||||
|
||||
<!-- <b-list-group-item exact exact-active-class="bg-primary text-white" :to="{ name: 'profile.settings' }">
|
||||
<i class="mr-2 fa-solid fa-user fa-fw" aria-hidden />
|
||||
<small>Profile</small>
|
||||
</b-list-group-item> -->
|
||||
|
||||
<!-- <b-list-group-item exact exact-active-class="bg-primary text-white" :to="{ name: 'steam.settings' }">
|
||||
<i class="mr-2 fab fa-steam fa-fw" aria-hidden></i>
|
||||
<small>Steam</small>
|
||||
</b-list-group-item> -->
|
||||
|
||||
<!-- <hr /> -->
|
||||
|
||||
<!-- <b-list-group-item :to="{ name: 'profiles' }">
|
||||
<i class="mr-2 fa-solid fa-people-group fa-fw" aria-hidden />
|
||||
<small>Profiles</small>
|
||||
</b-list-group-item> -->
|
||||
|
||||
<!-- {{ $t('global.donateMessage') }} -->
|
||||
<!-- <a href="https://www.paypal.me/RomanCervantes/5" target="_blank">
|
||||
{{ $t('global.donating') }}
|
||||
</a> -->
|
||||
|
||||
<small>© 2022 Gamebrary</small>
|
||||
</b-col>
|
||||
</b-row>
|
||||
</b-container>
|
||||
|
|
|
@ -1,13 +0,0 @@
|
|||
<template lang="html">
|
||||
<div>
|
||||
test
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" rel="stylesheet/scss" scoped>
|
||||
</style>
|
|
@ -1,194 +1,35 @@
|
|||
<!-- TODO: break this up into components -->
|
||||
<!-- TODO: Move add tag to page -->
|
||||
<!-- TODO: Move edit tag to page -->
|
||||
<template lang="html">
|
||||
<b-container fluid>
|
||||
<b-row>
|
||||
<b-spinner v-if="loading" class="spinner-centered" />
|
||||
|
||||
<b-row v-else>
|
||||
<portal to="headerTitle">Tags</portal>
|
||||
|
||||
<portal to="headerActions">
|
||||
<b-button
|
||||
class="mr-2"
|
||||
variant="light"
|
||||
:to="{ name: 'tag.create' }"
|
||||
>
|
||||
Add tag
|
||||
</b-button>
|
||||
</portal>
|
||||
|
||||
<empty-state
|
||||
v-if="showEmptyState"
|
||||
:title="$t('tags.title')"
|
||||
message="Tags are a great way to organize your collection"
|
||||
>
|
||||
<b-button
|
||||
variant="primary"
|
||||
v-b-modal.addTag
|
||||
variant="light"
|
||||
:to="{ name: 'tag.create' }"
|
||||
>
|
||||
Add tag
|
||||
Create a tag
|
||||
</b-button>
|
||||
</empty-state>
|
||||
|
||||
<b-col v-else>
|
||||
<portal to="headerTitle">Tags</portal>
|
||||
|
||||
<portal to="headerActions">
|
||||
<b-button
|
||||
class="mr-2"
|
||||
variant="light"
|
||||
@click="$bvModal.show('addTag')"
|
||||
>
|
||||
Add tag
|
||||
</b-button>
|
||||
</portal>
|
||||
|
||||
<tags-list
|
||||
v-if="gameTags && localTags"
|
||||
@edit="editTag"
|
||||
@delete="promptDeleteTag"
|
||||
/>
|
||||
<tags-list />
|
||||
</b-col>
|
||||
|
||||
<!-- TODO: move to component -->
|
||||
<b-modal
|
||||
id="editTag"
|
||||
hide-footer
|
||||
>
|
||||
<template v-slot:modal-header="{ close }">
|
||||
<modal-header
|
||||
:title="$t('tags.edit.title')"
|
||||
@close="close"
|
||||
>
|
||||
<b-button
|
||||
variant="danger"
|
||||
@click="promptDeleteTag(editingTagName)"
|
||||
>
|
||||
<i class="fas fa-trash-alt fa-fw" aria-hidden />
|
||||
</b-button>
|
||||
|
||||
<b-button
|
||||
variant="primary"
|
||||
:disabled="isEditedNameDuplicate || !Boolean(editingTagName) || saving"
|
||||
@click="saveTag"
|
||||
>
|
||||
<b-spinner small v-if="saving" />
|
||||
<span v-else>Save</span>
|
||||
</b-button>
|
||||
</modal-header>
|
||||
</template>
|
||||
|
||||
<form
|
||||
ref="editTagForm"
|
||||
@submit.stop.prevent="saveTag"
|
||||
>
|
||||
<b-form-row class="mb-3" v-if="editingTag">
|
||||
<b-col cols="8" md="9">
|
||||
<b-form-input
|
||||
label="test"
|
||||
maxlength="20"
|
||||
:placeholder="$t('tags.form.inputPlaceholder')"
|
||||
required
|
||||
v-model.trim="editingTagName"
|
||||
/>
|
||||
</b-col>
|
||||
|
||||
<b-col cols="4" md="3">
|
||||
<b-input-group>
|
||||
<b-form-input
|
||||
v-model="editingTag.hex"
|
||||
type="color"
|
||||
required
|
||||
/>
|
||||
|
||||
<b-form-input
|
||||
v-model="editingTag.tagTextColor"
|
||||
type="color"
|
||||
required
|
||||
/>
|
||||
</b-input-group>
|
||||
</b-col>
|
||||
</b-form-row>
|
||||
|
||||
<template v-if="editingTagName">
|
||||
Preview:
|
||||
|
||||
<b-badge
|
||||
:style="`background-color: ${editingTag.hex}; color: ${editingTag.tagTextColor}`"
|
||||
>
|
||||
{{ editingTagName }}
|
||||
</b-badge>
|
||||
</template>
|
||||
</form>
|
||||
|
||||
<b-alert
|
||||
class="mt-3 mb-0"
|
||||
:show="isEditedNameDuplicate && !saving"
|
||||
variant="warning"
|
||||
>
|
||||
You already have a tag named <strong>{{ editingTagName }}</strong>
|
||||
</b-alert>
|
||||
</b-modal>
|
||||
|
||||
<b-modal
|
||||
id="addTag"
|
||||
hide-footer
|
||||
@show="open"
|
||||
>
|
||||
<template v-slot:modal-header="{ close }">
|
||||
<modal-header
|
||||
:title="$t('Add tag')"
|
||||
@close="close"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<form
|
||||
ref="newTagForm"
|
||||
@submit.stop.prevent="submit"
|
||||
>
|
||||
<b-form-row class="mb-3">
|
||||
<b-col cols="8" md="9">
|
||||
<b-form-input
|
||||
label="test"
|
||||
maxlength="20"
|
||||
:placeholder="$t('tags.form.inputPlaceholder')"
|
||||
required
|
||||
v-model.trim="tagName"
|
||||
/>
|
||||
|
||||
<b-form-text v-if="tagName" tag="span">
|
||||
{{ $t('tags.form.preview') }}
|
||||
|
||||
<b-badge :style="`background-color: ${hex}; color: ${tagTextColor}`">
|
||||
{{ tagName }}
|
||||
</b-badge>
|
||||
</b-form-text>
|
||||
</b-col>
|
||||
|
||||
<b-col cols="4" md="3">
|
||||
<b-input-group>
|
||||
<b-form-input
|
||||
v-model="hex"
|
||||
type="color"
|
||||
required
|
||||
/>
|
||||
|
||||
<b-form-input
|
||||
v-model="tagTextColor"
|
||||
type="color"
|
||||
required
|
||||
/>
|
||||
</b-input-group>
|
||||
</b-col>
|
||||
</b-form-row>
|
||||
|
||||
<b-button
|
||||
variant="primary"
|
||||
class="d-flex ml-auto"
|
||||
:disabled="isDuplicate || saving || !Boolean(tagName)"
|
||||
@click="submit"
|
||||
>
|
||||
<b-spinner small v-if="saving" />
|
||||
<span v-else>{{ $t('tags.form.addTag')}}</span>
|
||||
</b-button>
|
||||
|
||||
<b-alert
|
||||
class="mt-3 mb-0"
|
||||
:show="isDuplicate"
|
||||
variant="warning"
|
||||
>
|
||||
{{ $t('tags.form.duplicateMessage', { tagName }) }}
|
||||
<strong>{{ tagName }}</strong>
|
||||
</b-alert>
|
||||
</form>
|
||||
</b-modal>
|
||||
</b-row>
|
||||
</b-container>
|
||||
</template>
|
||||
|
@ -206,194 +47,30 @@ export default {
|
|||
|
||||
data() {
|
||||
return {
|
||||
saving: false,
|
||||
tagName: '',
|
||||
hex: '#143D59',
|
||||
tagTextColor: '#F4B41A',
|
||||
colorCombinations: [
|
||||
// [text, bg]
|
||||
['#0d1137', '#e52165'],
|
||||
['#ffffff', '#000000'],
|
||||
['#101820', '#FEE715'],
|
||||
['#F2AA4C', '#101820'],
|
||||
['#F93822', '#FDD20E'],
|
||||
],
|
||||
exclusive: false,
|
||||
editingTag: {},
|
||||
editingTagName: '',
|
||||
editingOriginalTagName: '',
|
||||
localTags: {},
|
||||
loading: false,
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
...mapState(['tags', 'platform', 'games']),
|
||||
...mapState(['tags']),
|
||||
|
||||
showEmptyState() {
|
||||
return Object.keys(this.tags).length === 0;
|
||||
},
|
||||
|
||||
isDuplicate() {
|
||||
const { tagName, localTags } = this;
|
||||
|
||||
const tagNames = Object.keys(localTags)
|
||||
.filter(name => name !== tagName)
|
||||
.map(name => name.toLowerCase());
|
||||
|
||||
return tagNames.includes(tagName.toLowerCase());
|
||||
},
|
||||
|
||||
isEditedNameDuplicate() {
|
||||
const { editingOriginalTagName, editingTagName, localTags } = this;
|
||||
|
||||
const tagNames = Object.keys(localTags)
|
||||
.filter(name => name !== editingOriginalTagName)
|
||||
.map(tagName => tagName.toLowerCase());
|
||||
|
||||
return tagNames.includes(editingTagName.toLowerCase());
|
||||
},
|
||||
|
||||
gameTags() {
|
||||
return Object.keys(this.localTags).length > 0;
|
||||
return !this.loading && this.tags?.length === 0;
|
||||
},
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.localTags = JSON.parse(JSON.stringify(this.tags));
|
||||
|
||||
this.load();
|
||||
},
|
||||
|
||||
methods: {
|
||||
async load() {
|
||||
const gamesInTags = Object.values(this.localTags)
|
||||
.map(({ games }) => games.map(gameId => String(gameId)))
|
||||
.reduce((entireList, games) => entireList.concat(games), []);
|
||||
this.loading = true;
|
||||
|
||||
const cachedGameList = Object.keys(this.games);
|
||||
const deDupedGameList = [...new Set(gamesInTags)];
|
||||
const gamesToLoad = deDupedGameList
|
||||
.filter(gameId => !cachedGameList.includes(gameId))
|
||||
.toString();
|
||||
await this.$store.dispatch('LOAD_TAGS')
|
||||
.catch(() => { this.loading = false; })
|
||||
|
||||
if (gamesToLoad.length === 0) return;
|
||||
|
||||
await this.$store.dispatch('LOAD_GAMES', gamesToLoad)
|
||||
.catch(() => {
|
||||
this.$bvToast.toast('Error loading games', { variant: 'error' });
|
||||
});
|
||||
},
|
||||
|
||||
open() {
|
||||
this.setRandomColors();
|
||||
},
|
||||
|
||||
setRandomColors() {
|
||||
const { colorCombinations } = this;
|
||||
|
||||
const randomNumber = Math.floor(Math.random() * colorCombinations.length);
|
||||
|
||||
this.tagTextColor = colorCombinations[randomNumber][0];
|
||||
this.hex = colorCombinations[randomNumber][1];
|
||||
},
|
||||
|
||||
editTag(tagName) {
|
||||
this.editingTagName = tagName;
|
||||
this.editingOriginalTagName = tagName;
|
||||
this.editingTag = JSON.parse(JSON.stringify(this.localTags[tagName]));
|
||||
this.$bvModal.show('editTag');
|
||||
},
|
||||
|
||||
async saveTag(e) {
|
||||
e.preventDefault();
|
||||
|
||||
if (this.$refs.editTagForm.checkValidity()) {
|
||||
const { editingTagName, editingOriginalTagName, editingTag } = this;
|
||||
|
||||
const renaming = editingTagName.toLowerCase() !== editingOriginalTagName.toLowerCase();
|
||||
|
||||
if (renaming) {
|
||||
this.$delete(this.localTags, editingOriginalTagName);
|
||||
this.$set(this.localTags, editingTagName, editingTag);
|
||||
|
||||
await this.saveTags(true);
|
||||
} else {
|
||||
this.localTags[editingOriginalTagName] = JSON.parse(JSON.stringify(editingTag));
|
||||
|
||||
this.saveTags();
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
submit(e) {
|
||||
e.preventDefault();
|
||||
|
||||
if (this.$refs.newTagForm.checkValidity()) {
|
||||
this.createTag();
|
||||
}
|
||||
},
|
||||
|
||||
reset() {
|
||||
this.tagName = '';
|
||||
this.hex = '#143D59';
|
||||
this.tagTextColor = '#F4B41A';
|
||||
},
|
||||
|
||||
createTag() {
|
||||
const { hex, tagTextColor, tagName } = this;
|
||||
|
||||
const newTag = {
|
||||
games: [],
|
||||
hex,
|
||||
tagTextColor,
|
||||
};
|
||||
|
||||
this.$set(this.localTags, tagName, newTag);
|
||||
this.saveTags();
|
||||
},
|
||||
|
||||
promptDeleteTag(tagName) {
|
||||
this.$bvModal.msgBoxConfirm(this.$t('tags.delete.message'), {
|
||||
title: this.$t('tags.delete.title'),
|
||||
okVariant: 'danger',
|
||||
okTitle: this.$t('tags.delete.buttonLabel'),
|
||||
cancelTitle: this.$t('global.cancel'),
|
||||
headerClass: 'pb-0 border-0',
|
||||
footerClass: 'pt-0 border-0',
|
||||
})
|
||||
.then((value) => {
|
||||
if (value) {
|
||||
this.deleteTag(tagName);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
deleteTag(tagName) {
|
||||
this.$delete(this.localTags, tagName);
|
||||
this.saveTags(true);
|
||||
},
|
||||
|
||||
async saveTags(deleting) {
|
||||
this.saving = true;
|
||||
const action = deleting
|
||||
? 'SAVE_TAGS_NO_MERGE'
|
||||
: 'SAVE_TAGS';
|
||||
|
||||
await this.$store.dispatch(action, this.localTags)
|
||||
.catch(() => {
|
||||
this.saving = false;
|
||||
this.$store.commit('SET_SESSION_EXPIRED', true);
|
||||
});
|
||||
|
||||
const message = deleting
|
||||
? 'Tags saved'
|
||||
: 'Tag added';
|
||||
|
||||
this.$bvModal.hide('editTag');
|
||||
this.$bvModal.hide('addTag');
|
||||
this.$bvToast.toast(message, { title: 'Success', variant: 'success' });
|
||||
this.reset();
|
||||
this.saving = false;
|
||||
this.loading = false;
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
|
@ -10,7 +10,19 @@
|
|||
/>
|
||||
|
||||
<template v-else>
|
||||
<portal to="headerTitle">Wallpapers</portal>
|
||||
<portal to="headerTitle">
|
||||
<div>
|
||||
<b-button
|
||||
:to="{ name: 'settings' }"
|
||||
variant="light"
|
||||
class="mr-2"
|
||||
>
|
||||
<i class="fa-solid fa-chevron-left" />
|
||||
</b-button>
|
||||
|
||||
Wallpapers
|
||||
</div>
|
||||
</portal>
|
||||
|
||||
<portal to="headerActions">
|
||||
<b-button
|
||||
|
|
|
@ -22,6 +22,7 @@ import NotesPage from '@/pages/NotesPage';
|
|||
import GameNotesPage from '@/pages/GameNotesPage';
|
||||
import EditListPage from '@/pages/EditListPage';
|
||||
import GameTagsPage from '@/pages/GameTagsPage';
|
||||
import CreateTagPage from '@/pages/CreateTagPage';
|
||||
import GameProgressPage from '@/pages/GameProgressPage';
|
||||
import PrivacyPolicyPage from '@/pages/PrivacyPolicyPage';
|
||||
import ProfilePage from '@/pages/ProfilePage';
|
||||
|
@ -33,7 +34,7 @@ import SearchPage from '@/pages/SearchPage';
|
|||
import SteamSettingsPage from '@/pages/SteamSettingsPage';
|
||||
// import GeneralSettingsPage from '@/pages/GeneralSettingsPage';
|
||||
import TagsPage from '@/pages/TagsPage';
|
||||
import TagEditPage from '@/pages/TagEditPage';
|
||||
import EditTagPage from '@/pages/EditTagPage';
|
||||
import TermsPage from '@/pages/TermsPage';
|
||||
import WallpapersPage from '@/pages/WallpapersPage';
|
||||
|
||||
|
@ -101,10 +102,18 @@ const routes = [
|
|||
title: 'Tags',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'tag.create',
|
||||
path: '/tags/create',
|
||||
component: CreateTagPage,
|
||||
meta: {
|
||||
title: 'Edit tag',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'tag.edit',
|
||||
path: '/tags/:id',
|
||||
component: TagEditPage,
|
||||
component: EditTagPage,
|
||||
meta: {
|
||||
title: 'Edit tag',
|
||||
},
|
||||
|
|
|
@ -76,7 +76,6 @@ export default {
|
|||
|
||||
LOAD_STEAM_GAME({ commit }, steamGameId) {
|
||||
return new Promise((resolve, reject) => {
|
||||
console.log('action', steamGameId);
|
||||
axios.get(`${API_BASE}/steam-game?gameId=${steamGameId}`)
|
||||
.then(({ data }) => {
|
||||
// TODO: move this logic to cloud function
|
||||
|
@ -572,9 +571,11 @@ export default {
|
|||
const db = firestore();
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
console.log(tags);
|
||||
console.log(state.user.uid);
|
||||
db.collection('tags')
|
||||
.doc(state.user.uid)
|
||||
.set(tags, { merge: true })
|
||||
.set({ tags }, { merge: false })
|
||||
.then(() => resolve())
|
||||
.catch(reject);
|
||||
});
|
||||
|
@ -595,17 +596,17 @@ export default {
|
|||
});
|
||||
},
|
||||
|
||||
SAVE_TAGS_NO_MERGE({ state }, tags) {
|
||||
const db = firestore();
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
db.collection('tags')
|
||||
.doc(state.user.uid)
|
||||
.set(tags, { merge: false })
|
||||
.then(() => resolve())
|
||||
.catch(reject);
|
||||
});
|
||||
},
|
||||
// SAVE_TAGS_NO_MERGE({ state }, tags) {
|
||||
// const db = firestore();
|
||||
//
|
||||
// return new Promise((resolve, reject) => {
|
||||
// db.collection('tags')
|
||||
// .doc(state.user.uid)
|
||||
// .set(tags, { merge: false })
|
||||
// .then(() => resolve())
|
||||
// .catch(reject);
|
||||
// });
|
||||
// },
|
||||
|
||||
SAVE_SETTINGS({ commit, state }, settings) {
|
||||
const db = firestore();
|
||||
|
@ -720,37 +721,52 @@ export default {
|
|||
.doc(state.user.uid)
|
||||
.get()
|
||||
.then((doc) => {
|
||||
if (doc.exists) {
|
||||
const tags = doc.data();
|
||||
if (!doc.exists) return reject();
|
||||
|
||||
commit('SET_TAGS', tags);
|
||||
resolve();
|
||||
const { tags } = doc.data();
|
||||
|
||||
if (typeof tags === 'object') {
|
||||
console.warn('Legacy tag detected');
|
||||
|
||||
const formattedTags = Object.entries(tags).map(([ ,tag]) => ({ ...tag }));
|
||||
|
||||
commit('SET_TAGS', formattedTags);
|
||||
resolve(formattedTags);
|
||||
} else {
|
||||
commit('SET_SESSION_EXPIRED', true);
|
||||
reject();
|
||||
console.log('is type', typeof tags);
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
SYNC_LOAD_TAGS({ commit, state }) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const db = firestore();
|
||||
|
||||
db.collection('tags')
|
||||
.doc(state.user.uid)
|
||||
.onSnapshot((doc) => {
|
||||
if (doc.exists) {
|
||||
const tags = doc.data();
|
||||
|
||||
commit('SET_TAGS', tags);
|
||||
resolve();
|
||||
} else {
|
||||
reject();
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
// SYNC_LOAD_TAGS({ commit, state }) {
|
||||
// return new Promise((resolve, reject) => {
|
||||
// const db = firestore();
|
||||
//
|
||||
// db.collection('tags')
|
||||
// .doc(state.user.uid)
|
||||
// .onSnapshot((doc) => {
|
||||
// if (doc.exists) {
|
||||
// const tags = doc.data();
|
||||
//
|
||||
// const sortedTags = Object.keys(tags)
|
||||
// .sort()
|
||||
// .reduce((res, key) => (res[key] = tags[key], res), {});
|
||||
//
|
||||
// const mappedTags = Object.entries(sortedTags).map((t) => {
|
||||
// const [name, tag] = t;
|
||||
//
|
||||
// return { name, ...tag };
|
||||
// })
|
||||
//
|
||||
// commit('SET_TAGS', mappedTags);
|
||||
// resolve();
|
||||
// } else {
|
||||
// reject();
|
||||
// }
|
||||
// });
|
||||
// });
|
||||
// },
|
||||
|
||||
SYNC_LOAD_NOTES({ commit, state }) {
|
||||
return new Promise((resolve, reject) => {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
export default {
|
||||
user: null,
|
||||
tags: {},
|
||||
tags: [],
|
||||
notes: {},
|
||||
profile: {},
|
||||
profiles: [],
|
||||
|
|
|
@ -1,29 +1,9 @@
|
|||
.card-columns {
|
||||
@media(min-width: 420px) {
|
||||
column-count: 3;
|
||||
}
|
||||
|
||||
@media(min-width: 680px) {
|
||||
column-count: 4;
|
||||
}
|
||||
|
||||
@media(min-width: 1024px) {
|
||||
column-count: 5;
|
||||
}
|
||||
|
||||
@media(min-width: 1280px) {
|
||||
column-count: 7;
|
||||
}
|
||||
.field {
|
||||
width: 320px;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.modal {
|
||||
padding: 1rem 0 !important;
|
||||
|
||||
@media(max-width: 600px) {
|
||||
padding: 0 !important;
|
||||
}
|
||||
}
|
||||
|
||||
.toast-header {
|
||||
display: none;
|
||||
.spinner-centered {
|
||||
position: absolute;
|
||||
left: calc(50% - 16px);
|
||||
}
|
|
@ -10030,6 +10030,11 @@ vue-style-loader@^4.1.0, vue-style-loader@^4.1.3:
|
|||
hash-sum "^1.0.2"
|
||||
loader-utils "^1.0.2"
|
||||
|
||||
vue-swatches@^2.1.1:
|
||||
version "2.1.1"
|
||||
resolved "https://registry.yarnpkg.com/vue-swatches/-/vue-swatches-2.1.1.tgz#26c467fb7648ff4ee0887aea36d1e03b15032b83"
|
||||
integrity sha512-YugkNbByxMz1dnx1nZyHSL3VSf/TnBH3/NQD+t8JKxPSqUmX87sVGBxjEaqH5IMraOLfVmU0pHCHl2BfXNypQg==
|
||||
|
||||
vue-template-compiler@^2.6.14:
|
||||
version "2.6.14"
|
||||
resolved "https://registry.yarnpkg.com/vue-template-compiler/-/vue-template-compiler-2.6.14.tgz#a2f0e7d985670d42c9c9ee0d044fed7690f4f763"
|
||||
|
|
Loading…
Reference in a new issue