mirror of
https://github.com/romancm/gamebrary
synced 2024-11-12 22:47:14 +00:00
UI updates
This commit is contained in:
parent
2ba26242aa
commit
74f484ec27
30 changed files with 873 additions and 391 deletions
|
@ -189,7 +189,7 @@ export default {
|
|||
mutation: 'SET_PLATFORMS',
|
||||
});
|
||||
} catch (e) {
|
||||
this.$bvToast.toast('There was an error loading platforms', { variant: 'error' });
|
||||
this.$bvToast.toast('There was an error loading platforms');
|
||||
}
|
||||
},
|
||||
|
||||
|
|
|
@ -122,8 +122,9 @@ export default {
|
|||
|
||||
try {
|
||||
await this.$store.dispatch('SAVE_GAME_BOARD', board);
|
||||
this.$bvToast.toast(`There was an error removing "${this.game.name}"`);
|
||||
} catch (e) {
|
||||
// this.$bvToast.toast(`There was an error removing "${this.game.name}"`, { title: list.name, variant: 'danger' });
|
||||
this.$bvToast.toast(`There was an error removing "${this.game.name}"`);
|
||||
}
|
||||
},
|
||||
},
|
||||
|
|
276
src/components/Board/GridBoard.vue
Normal file
276
src/components/Board/GridBoard.vue
Normal file
|
@ -0,0 +1,276 @@
|
|||
<template lang="html">
|
||||
<div class="standard-board pb-5">
|
||||
GRID!@@
|
||||
<!-- TODO: clean this up, remove all isGrid references -->
|
||||
<!-- TODO: update isGrid to use board?.grid -->
|
||||
<div class="standard-list" :class="{ 'grid': isGrid }">
|
||||
<p v-if="isEmpty">
|
||||
This board is empty.
|
||||
</p>
|
||||
|
||||
<draggable
|
||||
class="games"
|
||||
:class="{ 'game-grid': isGrid }"
|
||||
handle=".card"
|
||||
ghost-class="card-placeholder"
|
||||
drag-class="border-success"
|
||||
chosen-class="border-primary"
|
||||
filter=".drag-filter"
|
||||
delay="50"
|
||||
animation="500"
|
||||
:list="list.games"
|
||||
:move="validateMove"
|
||||
:disabled="draggingDisabled"
|
||||
:group="{ name: 'games' }"
|
||||
@end="dragEnd"
|
||||
@start="dragStart"
|
||||
>
|
||||
<GameCard
|
||||
v-for="(game, index) in listGames"
|
||||
:key="index"
|
||||
:list="list"
|
||||
:ref="game.id"
|
||||
:game-id="game.id"
|
||||
:ranked="board.ranked"
|
||||
:rank="index + 1"
|
||||
:vertical="isGrid"
|
||||
:hide-platforms="isGrid"
|
||||
:class="isGrid ? null: 'mb-3'"
|
||||
@click.native="openGame(game.id, list)"
|
||||
/>
|
||||
|
||||
<template v-if="isBoardOwner">
|
||||
<b-card
|
||||
v-if="isGrid"
|
||||
body-class="align-content-center text-center"
|
||||
:bg-variant="darkTheme ? 'dark' : 'light'"
|
||||
:text-variant="darkTheme ? 'light' : 'dark'"
|
||||
@click="openGameSelectorSidebar"
|
||||
>
|
||||
Expand your collection!
|
||||
|
||||
<b-button
|
||||
class="mt-2"
|
||||
:variant="darkTheme ? 'success' : 'primary'"
|
||||
>
|
||||
Add games
|
||||
</b-button>
|
||||
</b-card>
|
||||
|
||||
<b-button
|
||||
v-else
|
||||
class="py-3"
|
||||
block
|
||||
:variant="darkTheme ? 'success' : 'primary'"
|
||||
@click="openGameSelectorSidebar"
|
||||
>
|
||||
Add games
|
||||
</b-button>
|
||||
</template>
|
||||
</draggable>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { HIGHLIGHTED_GAME_TIMEOUT, BOARD_TYPE_GRID } from '@/constants';
|
||||
import draggable from 'vuedraggable';
|
||||
import slugify from 'slugify'
|
||||
import { mapState, mapGetters } from 'vuex';
|
||||
import GameCard from '@/components/GameCard';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
draggable,
|
||||
GameCard,
|
||||
},
|
||||
|
||||
computed: {
|
||||
...mapState(['cachedGames', 'dragging', 'progresses', 'board', 'user', 'settings', 'highlightedGame']),
|
||||
...mapGetters(['isBoardOwner', 'darkTheme']),
|
||||
|
||||
list() {
|
||||
const [firstList] = this.board?.lists;
|
||||
|
||||
return firstList || [];
|
||||
},
|
||||
|
||||
needsFlattening() {
|
||||
return this.board?.lists?.length > 0;
|
||||
},
|
||||
|
||||
isGrid() {
|
||||
return this.board?.type === BOARD_TYPE_GRID;
|
||||
},
|
||||
|
||||
filter() {
|
||||
return this.listGames || [];
|
||||
},
|
||||
|
||||
draggingDisabled() {
|
||||
return !this.user || !this.isBoardOwner;
|
||||
},
|
||||
|
||||
listGames() {
|
||||
return this.list?.games
|
||||
?.map((id) => this.cachedGames?.[id])
|
||||
?.filter(({ id }) => Boolean(id));
|
||||
},
|
||||
|
||||
isEmpty() {
|
||||
return this.listGames.length === 0;
|
||||
},
|
||||
|
||||
gameSelectorEventName() {
|
||||
return `SELECT_GAME_LIST_${this.listIndex}`;
|
||||
},
|
||||
},
|
||||
|
||||
mounted() {
|
||||
if (this.needsFlattening) this.flattenAndSaveBoard();
|
||||
if (this.highlightedGame) this.highlightGame();
|
||||
|
||||
this.$bus.$on(this.gameSelectorEventName, this.selectGame);
|
||||
},
|
||||
|
||||
destroyed() {
|
||||
this.$bus.$off(this.gameSelectorEventName, this.selectGame);
|
||||
},
|
||||
|
||||
methods: {
|
||||
// TODO: update this to work
|
||||
highlightGame() {
|
||||
const lists = Object.values(this.$refs);
|
||||
|
||||
console.log('lists', lists)
|
||||
|
||||
lists.forEach(([list], index) => {
|
||||
console.log('list.$refs', list.$refs);
|
||||
|
||||
const [gameRef] = list.$refs[this.highlightedGame];
|
||||
|
||||
console.log()
|
||||
|
||||
if (gameRef) {
|
||||
console.log('gameRef', gameRef);
|
||||
|
||||
setTimeout(() => {
|
||||
gameRef?.$el.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
||||
}, index * 1000);
|
||||
}
|
||||
});
|
||||
|
||||
// setTimeout(() => {
|
||||
// this.$store.commit('SET_HIGHLIGHTED_GAME', null);
|
||||
// }, HIGHLIGHTED_GAME_TIMEOUT);
|
||||
},
|
||||
|
||||
async flattenAndSaveBoard() {
|
||||
const mergedGamesList = [...new Set(this.board?.lists?.map(({ games }) => games)?.flat())];
|
||||
|
||||
const payload = {
|
||||
...this.board,
|
||||
lastUpdated: Date.now(),
|
||||
lists: [{ name: '', games: mergedGamesList }],
|
||||
}
|
||||
|
||||
this.$store.commit('SET_GAME_BOARD', payload);
|
||||
|
||||
await this.$store.dispatch('SAVE_BOARD');
|
||||
},
|
||||
|
||||
openGameSelectorSidebar() {
|
||||
this.$store.commit('SET_GAME_SELECTOR_DATA', {
|
||||
title: `Add games to ${this.board.name}`,
|
||||
filter: this.filter,
|
||||
eventName: this.gameSelectorEventName,
|
||||
})
|
||||
},
|
||||
|
||||
selectGame(gameId) {
|
||||
if (this.list.games.includes(gameId)) {
|
||||
this.removeGame(gameId);
|
||||
} else {
|
||||
this.addGame(gameId);
|
||||
}
|
||||
},
|
||||
|
||||
async addGame(gameId) {
|
||||
const board = JSON.parse(JSON.stringify(this.board));
|
||||
|
||||
board?.lists?.[0]?.games.push(gameId);
|
||||
|
||||
try {
|
||||
await this.$store.dispatch('SAVE_GAME_BOARD', board);
|
||||
await this.$store.dispatch('LOAD_BOARD', board?.id);
|
||||
await this.$store.dispatch('LOAD_IGDB_GAMES', [gameId]);
|
||||
} catch (e) {
|
||||
// this.$bvToast.toast(`There was an error adding "${this.game.name}"`, { title: list.name, variant: 'danger' });
|
||||
}
|
||||
},
|
||||
|
||||
async removeGame(gameId) {
|
||||
const { boardId, listIndex } = this.$route?.query;
|
||||
const boardIndex = this.boards.findIndex(({ id }) => id === boardId);
|
||||
const board = this.boards[boardIndex];
|
||||
const gameIndex = board?.lists?.[listIndex]?.games?.indexOf(gameId);
|
||||
|
||||
board.lists[listIndex].games.splice(gameIndex, 1);
|
||||
|
||||
try {
|
||||
await this.$store.dispatch('SAVE_GAME_BOARD', board);
|
||||
await this.$store.dispatch('LOAD_BOARD', board?.id)
|
||||
} catch (e) {
|
||||
// this.$bvToast.toast(`There was an error removing "${this.game.name}"`, { title: list.name, variant: 'danger' });
|
||||
}
|
||||
},
|
||||
|
||||
openGame(id, list) {
|
||||
const slug = slugify(this.cachedGames[id].slug, { lower: true });
|
||||
|
||||
this.$router.push({
|
||||
name: 'game',
|
||||
params: {
|
||||
id,
|
||||
slug,
|
||||
boardId: this.board?.id,
|
||||
},
|
||||
});
|
||||
},
|
||||
|
||||
validateMove({ from, to }) {
|
||||
const sameList = from.id === to.id;
|
||||
const notInList = !this.board?.lists?.[to.id]?.games?.includes(Number(this.draggingId));
|
||||
|
||||
return sameList || notInList && !sameList;
|
||||
},
|
||||
|
||||
dragStart({ item }) {
|
||||
this.$store.commit('SET_DRAGGING_STATUS', true);
|
||||
this.draggingId = item.id;
|
||||
},
|
||||
|
||||
dragEnd() {
|
||||
this.$store.commit('SET_DRAGGING_STATUS', false);
|
||||
this.saveBoard();
|
||||
},
|
||||
|
||||
async saveBoard() {
|
||||
await this.$store.dispatch('SAVE_BOARD')
|
||||
.catch(() => {
|
||||
this.$store.commit('SET_SESSION_EXPIRED', true);
|
||||
});
|
||||
},
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.standard-board {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
overflow-y: auto;
|
||||
flex-direction: column;
|
||||
}
|
||||
</style>
|
|
@ -43,10 +43,11 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import { BOARD_TYPE_STANDARD, BOARD_TYPE_TIER, IMAGE_SIZE_THUMB } from '@/constants';
|
||||
import { BOARD_TYPE_STANDARD, BOARD_TYPE_TIER, BOARD_TYPE_GRID, IMAGE_SIZE_THUMB } from '@/constants';
|
||||
import { mapGetters, mapState } from 'vuex';
|
||||
import { getImageUrl } from '@/utils';
|
||||
import StandardMiniBoard from '@/components/MiniBoards/StandardMiniBoard';
|
||||
import GridMiniBoard from '@/components/MiniBoards/GridMiniBoard';
|
||||
import KanbanMiniBoard from '@/components/MiniBoards/KanbanMiniBoard';
|
||||
import TierMiniBoard from '@/components/MiniBoards/TierMiniBoard';
|
||||
|
||||
|
@ -59,6 +60,7 @@ export default {
|
|||
|
||||
components: {
|
||||
StandardMiniBoard,
|
||||
GridMiniBoard,
|
||||
KanbanMiniBoard,
|
||||
TierMiniBoard,
|
||||
},
|
||||
|
@ -82,6 +84,7 @@ export default {
|
|||
miniBoardComponent() {
|
||||
if (this.board?.type === BOARD_TYPE_TIER) return 'TierMiniBoard';
|
||||
if (this.board?.type === BOARD_TYPE_STANDARD) return 'StandardMiniBoard';
|
||||
if (this.board?.type === BOARD_TYPE_GRID) return 'GridMiniBoard';
|
||||
|
||||
return 'KanbanMiniBoard';
|
||||
},
|
||||
|
|
|
@ -1,26 +1,66 @@
|
|||
<template lang="html">
|
||||
<div class="standard-board pb-5">
|
||||
<StandardList
|
||||
v-for="(list, listIndex) in board.lists"
|
||||
:ref="`list-${listIndex}`"
|
||||
:key="list.id"
|
||||
:listIndex="listIndex"
|
||||
:list="list"
|
||||
/>
|
||||
<div class="standard-board pb-5 d-flex align-items-center flex-column">
|
||||
<p v-if="isEmpty">
|
||||
This board is empty.
|
||||
</p>
|
||||
|
||||
<draggable
|
||||
class="games"
|
||||
handle=".game-card"
|
||||
ghost-class="card-placeholder"
|
||||
drag-class="border-success"
|
||||
chosen-class="border-primary"
|
||||
filter=".drag-filter"
|
||||
delay="50"
|
||||
animation="500"
|
||||
:list="list.games"
|
||||
:move="validateMove"
|
||||
:disabled="draggingDisabled"
|
||||
:group="{ name: 'games' }"
|
||||
@end="dragEnd"
|
||||
@start="dragStart"
|
||||
>
|
||||
<GameCard
|
||||
v-for="(gameId, index) in listGames"
|
||||
:key="index"
|
||||
:list="list"
|
||||
:ref="gameId"
|
||||
:game-id="gameId"
|
||||
:ranked="board.ranked"
|
||||
:rank="index + 1"
|
||||
class="mb-3"
|
||||
@click.native="openGame(gameId, list)"
|
||||
/>
|
||||
|
||||
<b-button
|
||||
v-if="isBoardOwner"
|
||||
class="py-3"
|
||||
block
|
||||
:variant="darkTheme ? 'success' : 'primary'"
|
||||
@click="openGameSelectorSidebar"
|
||||
>
|
||||
Add games
|
||||
</b-button>
|
||||
</draggable>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapState } from 'vuex';
|
||||
import StandardList from '@/components/Lists/StandardList';
|
||||
import { HIGHLIGHTED_GAME_TIMEOUT } from '@/constants';
|
||||
import draggable from 'vuedraggable';
|
||||
import slugify from 'slugify'
|
||||
import { mapState, mapGetters } from 'vuex';
|
||||
import GameCard from '@/components/GameCard';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
StandardList,
|
||||
draggable,
|
||||
GameCard,
|
||||
},
|
||||
|
||||
computed: {
|
||||
...mapState(['board', 'highlightedGame']),
|
||||
...mapState(['cachedGames', 'dragging', 'progresses', 'board', 'user', 'settings', 'highlightedGame']),
|
||||
...mapGetters(['isBoardOwner', 'darkTheme']),
|
||||
|
||||
list() {
|
||||
const [firstList] = this.board?.lists;
|
||||
|
@ -29,34 +69,59 @@ export default {
|
|||
},
|
||||
|
||||
needsFlattening() {
|
||||
return this.board?.lists?.length && this.board.type === 'standard';
|
||||
return this.board?.lists?.length > 0;
|
||||
},
|
||||
|
||||
hasLists() {
|
||||
return this.board?.lists?.length > 0;
|
||||
filter() {
|
||||
return this.listGames || [];
|
||||
},
|
||||
|
||||
draggingDisabled() {
|
||||
return !this.user || !this.isBoardOwner;
|
||||
},
|
||||
|
||||
listGames() {
|
||||
return this.list?.games;
|
||||
},
|
||||
|
||||
isEmpty() {
|
||||
return this.listGames.length === 0;
|
||||
},
|
||||
|
||||
gameSelectorEventName() {
|
||||
return `SELECT_GAME_LIST_${this.listIndex}`;
|
||||
},
|
||||
},
|
||||
|
||||
mounted() {
|
||||
if (this.needsFlattening) this.flattenAndSaveBoard();
|
||||
if (this.highlightedGame) this.highlightGame();
|
||||
|
||||
this.$bus.$on(this.gameSelectorEventName, this.selectGame);
|
||||
},
|
||||
|
||||
destroyed() {
|
||||
this.$bus.$off(this.gameSelectorEventName, this.selectGame);
|
||||
},
|
||||
|
||||
methods: {
|
||||
highlightGame() {
|
||||
const lists = Object.values(this.$refs);
|
||||
// TODO: update this to work
|
||||
// const lists = Object.values(this.$refs);
|
||||
|
||||
lists.forEach(([list], index) => {
|
||||
const [gameRef] = list.$refs[`${index}-${this.highlightedGame}`];
|
||||
// lists.forEach(([list], index) => {
|
||||
// console.log('list.$refs', list.$refs);
|
||||
|
||||
if (gameRef) {
|
||||
console.log('gameRef', gameRef);
|
||||
// const [gameRef] = list.$refs[this.highlightedGame];
|
||||
|
||||
setTimeout(() => {
|
||||
gameRef?.$el.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
||||
}, index * 1000);
|
||||
}
|
||||
});
|
||||
// if (gameRef) {
|
||||
// console.log('gameRef', gameRef);
|
||||
|
||||
// setTimeout(() => {
|
||||
// gameRef?.$el.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
||||
// }, index * 1000);
|
||||
// }
|
||||
// });
|
||||
|
||||
setTimeout(() => {
|
||||
this.$store.commit('SET_HIGHLIGHTED_GAME', null);
|
||||
|
@ -77,19 +142,88 @@ export default {
|
|||
await this.$store.dispatch('SAVE_BOARD');
|
||||
},
|
||||
|
||||
flattenBoard() {
|
||||
this.$store.commit('FLATTEN_BOARD');
|
||||
openGameSelectorSidebar() {
|
||||
this.$store.commit('SET_GAME_SELECTOR_DATA', {
|
||||
title: `Add games to ${this.board.name}`,
|
||||
filter: this.filter,
|
||||
eventName: this.gameSelectorEventName,
|
||||
})
|
||||
},
|
||||
|
||||
selectGame(gameId) {
|
||||
if (this.list.games.includes(gameId)) {
|
||||
this.removeGame(gameId);
|
||||
} else {
|
||||
this.addGame(gameId);
|
||||
}
|
||||
},
|
||||
|
||||
async addGame(gameId) {
|
||||
const board = JSON.parse(JSON.stringify(this.board));
|
||||
|
||||
board?.lists?.[0]?.games.push(gameId);
|
||||
|
||||
try {
|
||||
await this.$store.dispatch('SAVE_GAME_BOARD', board);
|
||||
await this.$store.dispatch('LOAD_BOARD', board?.id);
|
||||
await this.$store.dispatch('LOAD_IGDB_GAMES', [gameId]);
|
||||
} catch (e) {
|
||||
// this.$bvToast.toast(`There was an error adding "${this.game.name}"`, { title: list.name, variant: 'danger' });
|
||||
}
|
||||
},
|
||||
|
||||
async removeGame(gameId) {
|
||||
const { boardId, listIndex } = this.$route?.query;
|
||||
const boardIndex = this.boards.findIndex(({ id }) => id === boardId);
|
||||
const board = this.boards[boardIndex];
|
||||
const gameIndex = board?.lists?.[listIndex]?.games?.indexOf(gameId);
|
||||
|
||||
board.lists[listIndex].games.splice(gameIndex, 1);
|
||||
|
||||
try {
|
||||
await this.$store.dispatch('SAVE_GAME_BOARD', board);
|
||||
await this.$store.dispatch('LOAD_BOARD', board?.id)
|
||||
} catch (e) {
|
||||
// this.$bvToast.toast(`There was an error removing "${this.game.name}"`, { title: list.name, variant: 'danger' });
|
||||
}
|
||||
},
|
||||
|
||||
openGame(id, list) {
|
||||
const slug = slugify(this.cachedGames[id].slug, { lower: true });
|
||||
|
||||
this.$router.push({
|
||||
name: 'game',
|
||||
params: {
|
||||
id,
|
||||
slug,
|
||||
boardId: this.board?.id,
|
||||
},
|
||||
});
|
||||
},
|
||||
|
||||
validateMove({ from, to }) {
|
||||
const sameList = from.id === to.id;
|
||||
const notInList = !this.board?.lists?.[to.id]?.games?.includes(Number(this.draggingId));
|
||||
|
||||
return sameList || notInList && !sameList;
|
||||
},
|
||||
|
||||
dragStart({ item }) {
|
||||
this.$store.commit('SET_DRAGGING_STATUS', true);
|
||||
this.draggingId = item.id;
|
||||
},
|
||||
|
||||
dragEnd() {
|
||||
this.$store.commit('SET_DRAGGING_STATUS', false);
|
||||
this.saveBoard();
|
||||
},
|
||||
|
||||
async saveBoard() {
|
||||
await this.$store.dispatch('SAVE_BOARD')
|
||||
.catch(() => {
|
||||
this.$store.commit('SET_SESSION_EXPIRED', true);
|
||||
});
|
||||
},
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.standard-board {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
overflow-y: auto;
|
||||
flex-direction: column;
|
||||
}
|
||||
</style>
|
||||
|
|
119
src/components/CloneBoardSidebar.vue
Normal file
119
src/components/CloneBoardSidebar.vue
Normal file
|
@ -0,0 +1,119 @@
|
|||
<template lang="html">
|
||||
<b-sidebar
|
||||
id="clone-board-sidebar"
|
||||
v-bind="sidebarRightProps"
|
||||
@shown="setBoardName"
|
||||
>
|
||||
<template #default="{ hide }">
|
||||
<SidebarHeader @hide="hide" title="Clone board" />
|
||||
|
||||
<form @submit.prevent="cloneBoard" class="px-3">
|
||||
<b-form-group label="Board name:" label-for="boardName">
|
||||
<b-form-input
|
||||
id="boardName"
|
||||
v-model.trim="boardName"
|
||||
autofocus
|
||||
required
|
||||
/>
|
||||
</b-form-group>
|
||||
|
||||
<MiniBoard
|
||||
class="mb-2"
|
||||
:board="board"
|
||||
no-link
|
||||
/>
|
||||
|
||||
<b-button
|
||||
:variant="darkTheme ? 'secondary' : 'primary'"
|
||||
:disabled="saving"
|
||||
type="submit"
|
||||
>
|
||||
<b-spinner small v-if="saving" />
|
||||
<span v-else>Clone board</span>
|
||||
</b-button>
|
||||
</form>
|
||||
|
||||
</template>
|
||||
</b-sidebar>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import SidebarHeader from '@/components/SidebarHeader';
|
||||
import MiniBoard from '@/components/Board/MiniBoard';
|
||||
import { mapState, mapGetters } from 'vuex';
|
||||
import { BOARD_TYPE_KANBAN } from '@/constants';
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
boardName: '',
|
||||
saving: false,
|
||||
}
|
||||
},
|
||||
|
||||
components: {
|
||||
SidebarHeader,
|
||||
MiniBoard,
|
||||
},
|
||||
|
||||
computed: {
|
||||
...mapState(['board', 'user']),
|
||||
...mapGetters(['sidebarRightProps', 'darkTheme']),
|
||||
|
||||
payload() {
|
||||
const dateCreated = Date.now();
|
||||
|
||||
const isBoardOwner = this.board.owner === this.user.uid;
|
||||
|
||||
const backgroundUrl = isBoardOwner
|
||||
? this.board.backgroundUrl
|
||||
: null;
|
||||
|
||||
console.log('isBoardOwner', isBoardOwner);
|
||||
|
||||
// TODO: clone wallpaper when cloning board?
|
||||
return {
|
||||
type: this.board.type || BOARD_TYPE_KANBAN,
|
||||
lists: this.board.lists,
|
||||
ranked: this.board.ranked || false,
|
||||
backgroundUrl: backgroundUrl || null,
|
||||
backgroundColor: this.board?.backgroundColor || null,
|
||||
lastUpdated: this.board.lastUpdated || dateCreated,
|
||||
darkTheme: this.board.darkTheme || false,
|
||||
dateCreated: this.board.dateCreated || dateCreated,
|
||||
originalOwnerId: this.board.owner,
|
||||
isPublic: false,
|
||||
dateCreated,
|
||||
lastUpdated: dateCreated,
|
||||
originalBoardId: this.board.id,
|
||||
originalDateCreated: this.board.dateCreated || dateCreated,
|
||||
owner: this.user.uid,
|
||||
name: this.boardName,
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
setBoardName() {
|
||||
this.boardName = this.board.name || '';
|
||||
},
|
||||
|
||||
async cloneBoard() {
|
||||
try {
|
||||
this.saving = true;
|
||||
|
||||
console.log(this.payload);
|
||||
|
||||
debugger;
|
||||
|
||||
const { id } = await this.$store.dispatch('CREATE_BOARD', this.payload);
|
||||
|
||||
this.saving = false;
|
||||
this.$router.push({ name: 'board', params: { id } });
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
|
@ -1,8 +1,7 @@
|
|||
<template lang="html">
|
||||
<b-sidebar
|
||||
id="create-board-sidebar"
|
||||
v-bind="sidebarProps"
|
||||
right
|
||||
v-bind="sidebarRightProps"
|
||||
@hidden="saving = false"
|
||||
>
|
||||
<template #default="{ hide }">
|
||||
|
@ -112,7 +111,7 @@ export default {
|
|||
|
||||
computed: {
|
||||
...mapState(['user']),
|
||||
...mapGetters(['sidebarProps']),
|
||||
...mapGetters(['sidebarRightProps']),
|
||||
|
||||
sampleBoard() {
|
||||
if (this.board.type === BOARD_TYPE_KANBAN) return DEFAULT_BOARD_KANBAN;
|
||||
|
@ -153,6 +152,8 @@ export default {
|
|||
owner: this.user.uid,
|
||||
}
|
||||
|
||||
console.log('payload', payload)
|
||||
|
||||
const { id } = await this.$store.dispatch('CREATE_BOARD', payload);
|
||||
|
||||
this.$router.push({ name: 'board', params: { id } });
|
||||
|
@ -163,3 +164,19 @@ export default {
|
|||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" rel="stylesheet/scss">
|
||||
.game-cover {
|
||||
width: 120px;
|
||||
}
|
||||
|
||||
.standard-list {
|
||||
width: 100%;
|
||||
max-width: 600px;
|
||||
overflow-x: hidden;
|
||||
|
||||
&.grid {
|
||||
max-width: 1280px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
<template lang="html">
|
||||
<b-sidebar
|
||||
id="create-tag-sidebar"
|
||||
v-bind="sidebarProps"
|
||||
right
|
||||
v-bind="sidebarRightProps"
|
||||
@hidden="saving = false"
|
||||
>
|
||||
<template #default="{ hide }">
|
||||
|
@ -75,7 +74,7 @@ export default {
|
|||
|
||||
computed: {
|
||||
...mapState(['tags']),
|
||||
...mapGetters(['sidebarProps', 'swatchesProps', 'darkTheme']),
|
||||
...mapGetters(['sidebarRightProps', 'swatchesProps', 'darkTheme']),
|
||||
},
|
||||
|
||||
methods: {
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
<template lang="html">
|
||||
<b-sidebar
|
||||
id="edit-board-sidebar"
|
||||
v-bind="sidebarProps"
|
||||
right
|
||||
v-bind="sidebarRightProps"
|
||||
>
|
||||
<template #default="{ hide }">
|
||||
<SidebarHeader @hide="hide" title="Edit board" />
|
||||
|
@ -10,8 +9,7 @@
|
|||
<form @submit.stop.prevent="saveBoard" class="p-3">
|
||||
<b-sidebar
|
||||
id="select-board-wallpaper"
|
||||
v-bind="sidebarProps"
|
||||
right
|
||||
v-bind="sidebarRightProps"
|
||||
>
|
||||
<template #default="{ hide }">
|
||||
<SidebarHeader @hide="hide" title="Select board background" />
|
||||
|
@ -65,15 +63,6 @@
|
|||
Ranked
|
||||
</b-form-checkbox>
|
||||
|
||||
<b-form-checkbox
|
||||
v-if="board.type === $options.BOARD_TYPE_STANDARD"
|
||||
v-model="board.grid"
|
||||
class="mb-3"
|
||||
switch
|
||||
>
|
||||
Grid
|
||||
</b-form-checkbox>
|
||||
|
||||
<b-form-checkbox
|
||||
v-model="board.darkTheme"
|
||||
class="mb-3"
|
||||
|
@ -185,7 +174,7 @@ export default {
|
|||
|
||||
computed: {
|
||||
...mapState(['user']),
|
||||
...mapGetters(['darkTheme', 'sidebarProps', 'swatchesProps']),
|
||||
...mapGetters(['darkTheme', 'sidebarRightProps', 'swatchesProps']),
|
||||
|
||||
boardId() {
|
||||
return this.$route?.params?.id;
|
||||
|
|
|
@ -2,8 +2,7 @@
|
|||
<b-sidebar
|
||||
id="profile-sidebar"
|
||||
:visible="editProfileSidebarOpen"
|
||||
right
|
||||
v-bind="sidebarProps"
|
||||
v-bind="sidebarRightProps"
|
||||
@shown="loadProfile"
|
||||
@hidden="$store.commit('SET_PROFILE_SIDEBAR_OPEN', false)"
|
||||
>
|
||||
|
@ -198,7 +197,7 @@
|
|||
|
||||
<b-sidebar
|
||||
id="boardWallpaper"
|
||||
v-bind="sidebarProps"
|
||||
v-bind="sidebarRightProps"
|
||||
right
|
||||
>
|
||||
<template #default="{ hide }">
|
||||
|
@ -271,7 +270,7 @@ export default {
|
|||
|
||||
computed: {
|
||||
...mapState(['user', 'editProfileSidebarOpen']),
|
||||
...mapGetters(['sidebarProps', 'darkTheme']),
|
||||
...mapGetters(['sidebarRightProps', 'darkTheme']),
|
||||
|
||||
style() {
|
||||
return this.wallpaperImage
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
<template lang="html">
|
||||
<b-sidebar
|
||||
:visible="activeTagIndex !== null"
|
||||
v-bind="sidebarProps"
|
||||
right
|
||||
v-bind="sidebarRightProps"
|
||||
@shown="load"
|
||||
@hidden="closeSidebar"
|
||||
>
|
||||
|
@ -110,7 +109,7 @@ export default {
|
|||
|
||||
computed: {
|
||||
...mapState(['tags', 'cachedGames', 'activeTagIndex']),
|
||||
...mapGetters(['sidebarProps', 'swatchesProps', 'darkTheme']),
|
||||
...mapGetters(['sidebarRightProps', 'swatchesProps', 'darkTheme']),
|
||||
|
||||
isEmpty() {
|
||||
return this.tag?.games?.length === 0;
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
<template lang="html">
|
||||
<b-sidebar
|
||||
id="gameTagsSidebar"
|
||||
v-bind="sidebarProps"
|
||||
right
|
||||
v-bind="sidebarRightProps"
|
||||
>
|
||||
<template #default="{ hide }">
|
||||
<SidebarHeader @hide="hide" :title="sidebarTitle" />
|
||||
|
@ -68,7 +67,7 @@ export default {
|
|||
|
||||
computed: {
|
||||
...mapState(['tags', 'game']),
|
||||
...mapGetters(['sidebarProps']),
|
||||
...mapGetters(['sidebarRightProps']),
|
||||
|
||||
isEmpty() {
|
||||
return this.tags.length === 0 || !this.game;
|
||||
|
|
|
@ -65,11 +65,12 @@
|
|||
<small v-else>{{ gameProgress }}%</small>
|
||||
</b-badge>
|
||||
|
||||
|
||||
<h2
|
||||
v-if="!hideTitle || hideCover"
|
||||
:class="['text-wrap',
|
||||
{
|
||||
'text-success' : gameCompleted, 'mb-1': !board.grid,
|
||||
'text-success' : gameCompleted, 'mb-1': board.type !== $options.BOARD_TYPE_GRID,
|
||||
'mt-2': vertical,
|
||||
}
|
||||
]"
|
||||
|
@ -130,12 +131,13 @@
|
|||
<script>
|
||||
import { mapState, mapGetters } from 'vuex';
|
||||
import { getImageUrl } from '@/utils';
|
||||
import { IMAGE_SIZE_COVER_SMALL, PLATFORMS } from '@/constants';
|
||||
import { IMAGE_SIZE_COVER_SMALL, PLATFORMS, BOARD_TYPE_GRID } from '@/constants';
|
||||
import GameRibbon from '@/components/GameRibbon';
|
||||
import slugify from 'slugify';
|
||||
|
||||
export default {
|
||||
IMAGE_SIZE_COVER_SMALL,
|
||||
BOARD_TYPE_GRID,
|
||||
getImageUrl,
|
||||
|
||||
props: {
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
<template lang="html">
|
||||
<b-sidebar
|
||||
:visible="visible"
|
||||
v-bind="sidebarProps"
|
||||
right
|
||||
v-bind="sidebarRightProps"
|
||||
@hidden="closeSidebar"
|
||||
>
|
||||
<template #default="{ hide }">
|
||||
|
@ -77,7 +76,7 @@ export default {
|
|||
|
||||
computed: {
|
||||
...mapState(['board', 'gameSelectorData']),
|
||||
...mapGetters(['isBoardOwner', 'sidebarProps', 'darkTheme']),
|
||||
...mapGetters(['isBoardOwner', 'sidebarRightProps', 'darkTheme']),
|
||||
|
||||
title() {
|
||||
return this.gameSelectorData?.title || 'Select a game';
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<template lang="html">
|
||||
<b-sidebar
|
||||
id="keyboard-shortcuts-sidebar"
|
||||
v-bind="sidebarProps"
|
||||
v-bind="sidebarLeftProps"
|
||||
z-index="2001"
|
||||
>
|
||||
<template #default="{ hide }">
|
||||
|
@ -57,7 +57,7 @@ export default {
|
|||
|
||||
computed: {
|
||||
...mapState(['user']),
|
||||
...mapGetters(['darkTheme', 'sidebarProps']),
|
||||
...mapGetters(['darkTheme', 'sidebarLeftProps']),
|
||||
},
|
||||
|
||||
mounted() {
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
<template>
|
||||
<b-sidebar
|
||||
id="edit-list-modal"
|
||||
v-bind="sidebarProps"
|
||||
right
|
||||
v-bind="sidebarRightProps"
|
||||
:visible="activeBoardListIndex !== null"
|
||||
@shown="openEditListSidebar"
|
||||
@hidden="closeSidebar"
|
||||
|
@ -193,7 +192,7 @@ export default {
|
|||
|
||||
computed: {
|
||||
...mapState(['board', 'activeBoardListIndex']),
|
||||
...mapGetters(['darkTheme', 'sidebarProps', 'swatchesProps']),
|
||||
...mapGetters(['darkTheme', 'sidebarRightProps', 'swatchesProps']),
|
||||
},
|
||||
|
||||
methods: {
|
||||
|
|
|
@ -1,240 +0,0 @@
|
|||
<template lang="html">
|
||||
<div class="standard-list" :class="{ 'grid': isGrid }" >
|
||||
<p v-if="isEmpty">
|
||||
This board is empty.
|
||||
</p>
|
||||
|
||||
<draggable
|
||||
class="games"
|
||||
:class="{ 'game-grid': isGrid }"
|
||||
handle=".card"
|
||||
ghost-class="card-placeholder"
|
||||
drag-class="border-success"
|
||||
chosen-class="border-primary"
|
||||
filter=".drag-filter"
|
||||
delay="50"
|
||||
animation="500"
|
||||
:list="list.games"
|
||||
:move="validateMove"
|
||||
:disabled="draggingDisabled"
|
||||
:group="{ name: 'games' }"
|
||||
@end="dragEnd"
|
||||
@start="dragStart"
|
||||
>
|
||||
<GameCard
|
||||
v-for="(game, index) in listGames"
|
||||
:key="index"
|
||||
:list="list"
|
||||
:ref="`${listIndex}-${game.id}`"
|
||||
:game-id="game.id"
|
||||
:ranked="board.ranked"
|
||||
:rank="index + 1"
|
||||
:vertical="isGrid"
|
||||
:hide-platforms="isGrid"
|
||||
:class="isGrid ? null: 'mb-3'"
|
||||
@click.native="openGame(game.id, list)"
|
||||
/>
|
||||
|
||||
<template v-if="isBoardOwner">
|
||||
<b-card
|
||||
v-if="isGrid"
|
||||
body-class="align-content-center text-center"
|
||||
:bg-variant="darkTheme ? 'dark' : 'light'"
|
||||
:text-variant="darkTheme ? 'light' : 'dark'"
|
||||
@click="openGameSelectorSidebar"
|
||||
>
|
||||
Expand your collection!
|
||||
|
||||
<b-button
|
||||
class="mt-2"
|
||||
:variant="darkTheme ? 'success' : 'primary'"
|
||||
>
|
||||
Add games
|
||||
</b-button>
|
||||
</b-card>
|
||||
|
||||
<b-button
|
||||
v-else
|
||||
class="py-3"
|
||||
block
|
||||
:variant="darkTheme ? 'success' : 'primary'"
|
||||
@click="openGameSelectorSidebar"
|
||||
>
|
||||
Add games
|
||||
</b-button>
|
||||
</template>
|
||||
</draggable>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import draggable from 'vuedraggable';
|
||||
import slugify from 'slugify'
|
||||
import { mapState, mapGetters } from 'vuex';
|
||||
import GameCard from '@/components/GameCard';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
draggable,
|
||||
GameCard,
|
||||
},
|
||||
|
||||
props: {
|
||||
listIndex: Number,
|
||||
list: {
|
||||
type: Object,
|
||||
default: () => {},
|
||||
},
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
draggingId: null,
|
||||
editing: false,
|
||||
};
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.$bus.$on(this.gameSelectorEventName, this.selectGame);
|
||||
},
|
||||
|
||||
destroyed() {
|
||||
this.$bus.$off(this.gameSelectorEventName, this.selectGame);
|
||||
},
|
||||
|
||||
computed: {
|
||||
...mapState(['cachedGames', 'dragging', 'progresses', 'board', 'user', 'settings']),
|
||||
...mapGetters(['isBoardOwner', 'darkTheme']),
|
||||
|
||||
isGrid() {
|
||||
return this.board?.grid;
|
||||
},
|
||||
|
||||
filter() {
|
||||
return this.list?.games || [];
|
||||
},
|
||||
|
||||
draggingDisabled() {
|
||||
return !this.user || !this.isBoardOwner;
|
||||
},
|
||||
|
||||
autoSortEnabled() {
|
||||
return ['sortByName', 'sortByRating', 'sortByReleaseDate', 'sortByProgress'].includes(this.list?.sortOrder);
|
||||
},
|
||||
|
||||
listGames() {
|
||||
return this.list?.games?.map((id) => this.cachedGames?.[id]) || []
|
||||
.filter(({ id }) => Boolean(id));
|
||||
},
|
||||
|
||||
isEmpty() {
|
||||
return this.list.games.length === 0;
|
||||
},
|
||||
|
||||
gameSelectorEventName() {
|
||||
return `SELECT_GAME_LIST_${this.listIndex}`;
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
openGameSelectorSidebar() {
|
||||
this.$store.commit('SET_GAME_SELECTOR_DATA', {
|
||||
title: `Add games to ${this.board.name}`,
|
||||
filter: this.filter,
|
||||
eventName: this.gameSelectorEventName,
|
||||
})
|
||||
},
|
||||
|
||||
selectGame(gameId) {
|
||||
if (this.list.games.includes(gameId)) {
|
||||
this.removeGame(gameId);
|
||||
} else {
|
||||
this.addGame(gameId);
|
||||
}
|
||||
},
|
||||
|
||||
async addGame(gameId) {
|
||||
const board = JSON.parse(JSON.stringify(this.board));
|
||||
|
||||
board?.lists?.[0]?.games.push(gameId);
|
||||
|
||||
try {
|
||||
await this.$store.dispatch('SAVE_GAME_BOARD', board);
|
||||
await this.$store.dispatch('LOAD_BOARD', board?.id);
|
||||
await this.$store.dispatch('LOAD_IGDB_GAMES', [gameId]);
|
||||
} catch (e) {
|
||||
// this.$bvToast.toast(`There was an error adding "${this.game.name}"`, { title: list.name, variant: 'danger' });
|
||||
}
|
||||
},
|
||||
|
||||
async removeGame(gameId) {
|
||||
const { boardId, listIndex } = this.$route?.query;
|
||||
const boardIndex = this.boards.findIndex(({ id }) => id === boardId);
|
||||
const board = this.boards[boardIndex];
|
||||
const gameIndex = board?.lists?.[listIndex]?.games?.indexOf(gameId);
|
||||
|
||||
board.lists[listIndex].games.splice(gameIndex, 1);
|
||||
|
||||
try {
|
||||
await this.$store.dispatch('SAVE_GAME_BOARD', board);
|
||||
await this.$store.dispatch('LOAD_BOARD', board?.id)
|
||||
} catch (e) {
|
||||
// this.$bvToast.toast(`There was an error removing "${this.game.name}"`, { title: list.name, variant: 'danger' });
|
||||
}
|
||||
},
|
||||
|
||||
openGame(id, list) {
|
||||
const slug = slugify(this.cachedGames[id].slug, { lower: true });
|
||||
|
||||
this.$router.push({
|
||||
name: 'game',
|
||||
params: {
|
||||
id,
|
||||
slug,
|
||||
boardId: this.board?.id,
|
||||
},
|
||||
});
|
||||
},
|
||||
|
||||
validateMove({ from, to }) {
|
||||
const sameList = from.id === to.id;
|
||||
const notInList = !this.board?.lists?.[to.id]?.games?.includes(Number(this.draggingId));
|
||||
|
||||
return sameList || notInList && !sameList;
|
||||
},
|
||||
|
||||
dragStart({ item }) {
|
||||
this.$store.commit('SET_DRAGGING_STATUS', true);
|
||||
this.draggingId = item.id;
|
||||
},
|
||||
|
||||
dragEnd() {
|
||||
this.$store.commit('SET_DRAGGING_STATUS', false);
|
||||
this.saveBoard();
|
||||
},
|
||||
|
||||
async saveBoard() {
|
||||
await this.$store.dispatch('SAVE_BOARD')
|
||||
.catch(() => {
|
||||
this.$store.commit('SET_SESSION_EXPIRED', true);
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" rel="stylesheet/scss">
|
||||
.game-cover {
|
||||
width: 120px;
|
||||
}
|
||||
|
||||
.standard-list {
|
||||
width: 100%;
|
||||
max-width: 600px;
|
||||
overflow-x: hidden;
|
||||
|
||||
&.grid {
|
||||
max-width: 1280px;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -1,8 +1,8 @@
|
|||
<template lang="html">
|
||||
<template lang="html">
|
||||
<b-sidebar
|
||||
id="mainMenu"
|
||||
:visible="menuOpen"
|
||||
v-bind="sidebarProps"
|
||||
v-bind="sidebarLeftProps"
|
||||
@hidden="hideSidebar"
|
||||
>
|
||||
<template #default="{ hide }">
|
||||
|
@ -22,58 +22,84 @@
|
|||
|
||||
<div class="p-3">
|
||||
<b-button
|
||||
:variant="routeName === 'boards' ? 'primary' : darkTheme ? 'dark' : 'light'"
|
||||
:variant="routeName === 'boards' ? activeVariant : variant"
|
||||
block
|
||||
class="text-left align-items-center d-flex"
|
||||
:to="{ name: 'boards' }"
|
||||
>
|
||||
<i class="fa-regular fa-rectangle-list" />
|
||||
<span class="ml-2">Boards</span>
|
||||
|
||||
<b-badge v-if="boards.length" class="ml-auto" variant="light">
|
||||
{{ boards.length }}
|
||||
</b-badge>
|
||||
</b-button>
|
||||
|
||||
<b-button
|
||||
:variant="routeName === 'games' ? 'primary' : darkTheme ? 'dark' : 'light'"
|
||||
:variant="routeName === 'games' ? activeVariant : variant"
|
||||
:to="{ name: 'games' }"
|
||||
block
|
||||
class="text-left align-items-center d-flex"
|
||||
>
|
||||
<i class="fa-regular fa-gamepad fa-fw" />
|
||||
Games
|
||||
<i class="fa-regular fa-gamepad fa-fw" />
|
||||
<span class="ml-2">Games</span>
|
||||
|
||||
<b-badge v-if="gameCount" class="ml-auto" variant="light">
|
||||
{{ gameCount }}
|
||||
</b-badge>
|
||||
</b-button>
|
||||
|
||||
<b-button
|
||||
block
|
||||
:variant="routeName === 'tags' ? 'primary' : darkTheme ? 'dark' : 'light'"
|
||||
:to="{ name: 'tags' }"
|
||||
:variant="routeName === 'tags' ? activeVariant : variant"
|
||||
class="text-left align-items-center d-flex"
|
||||
block
|
||||
>
|
||||
<i class="fa-light fa-tags fa-fw" />
|
||||
<span class="ml-2">Tags</span>
|
||||
|
||||
<b-badge v-if="tags.length" class="ml-auto" variant="light">
|
||||
{{ tags.length }}
|
||||
</b-badge>
|
||||
</b-button>
|
||||
|
||||
<b-button
|
||||
:to="{ name: 'notes' }"
|
||||
:variant="routeName === 'notes' ? 'primary' : darkTheme ? 'dark' : 'light'"
|
||||
:variant="routeName === 'notes' ? activeVariant : variant"
|
||||
class="text-left align-items-center d-flex"
|
||||
block
|
||||
>
|
||||
<i class="fa-regular fa-notes"></i>
|
||||
<i class="fa-regular fa-notes fa-fw" />
|
||||
|
||||
<span class="ml-2">Notes</span>
|
||||
|
||||
<b-badge v-if="notesCount" class="ml-auto" variant="light">
|
||||
{{ notesCount }}
|
||||
</b-badge>
|
||||
</b-button>
|
||||
|
||||
<b-button
|
||||
:variant="routeName === 'wallpapers' ? 'primary' : darkTheme ? 'dark' : 'light'"
|
||||
:variant="routeName === 'wallpapers' ? activeVariant : variant"
|
||||
:to="{ name: 'wallpapers' }"
|
||||
class="text-left align-items-center d-flex"
|
||||
block
|
||||
>
|
||||
<i class="fa-solid fa-images"></i>
|
||||
<i class="fa-solid fa-images fa-fw" />
|
||||
<span class="ml-2">Wallpapers</span>
|
||||
|
||||
<b-badge v-if="wallpaperCount" class="ml-auto" variant="light">
|
||||
{{ wallpaperCount }}
|
||||
</b-badge>
|
||||
</b-button>
|
||||
|
||||
<b-button
|
||||
block
|
||||
:variant="routeName === 'settings' ? 'primary' : darkTheme ? 'dark' : 'light'"
|
||||
class="text-left"
|
||||
:variant="routeName === 'settings' ? activeVariant : variant"
|
||||
v-b-toggle.settingsSidebar
|
||||
>
|
||||
<i class="fa-regular fa-gear fa-fw" />
|
||||
Settings
|
||||
<span class="ml-2">Settings</span>
|
||||
</b-button>
|
||||
</div>
|
||||
</template>
|
||||
|
@ -86,6 +112,7 @@
|
|||
|
||||
<script>
|
||||
import { mapState, mapGetters } from 'vuex';
|
||||
import { THUMBNAIL_PREFIX } from '@/constants';
|
||||
import ProfileDockMenu from '@/components/Dock/ProfileDockMenu';
|
||||
import SidebarHeader from '@/components/SidebarHeader';
|
||||
import MainSidebarFooter from '@/components/MainSidebarFooter';
|
||||
|
@ -101,11 +128,33 @@ export default {
|
|||
|
||||
computed: {
|
||||
...mapState(['user', 'board', 'boards', 'settings', 'user', 'games', 'notes', 'tags', 'wallpapers', 'menuOpen']),
|
||||
...mapGetters(['navPosition', 'latestRelease', 'darkTheme', 'transparencyEnabled', 'sidebarProps']),
|
||||
...mapGetters(['navPosition', 'latestRelease', 'darkTheme', 'transparencyEnabled', 'sidebarLeftProps']),
|
||||
|
||||
routeName() {
|
||||
return this.$route?.name;
|
||||
},
|
||||
|
||||
variant() {
|
||||
return this.darkTheme ? 'dark' : 'light';
|
||||
},
|
||||
|
||||
activeVariant() {
|
||||
return this.darkTheme ? 'success' : 'primary';
|
||||
},
|
||||
|
||||
gameCount() {
|
||||
return Object.keys(this.games).length;
|
||||
},
|
||||
|
||||
notesCount() {
|
||||
return Object.keys(this.notes).length;
|
||||
},
|
||||
|
||||
wallpaperCount() {
|
||||
const wallpapers = this.wallpapers?.filter((wallpaper) => !wallpaper?.fullPath?.includes(THUMBNAIL_PREFIX));
|
||||
|
||||
return wallpapers.length;
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
|
|
|
@ -1,13 +1,6 @@
|
|||
<template>
|
||||
<div class="p-3">
|
||||
<div class="d-flex justify-content-end">
|
||||
<!-- <b-button disabled>
|
||||
<i class="fa-solid fa-language" />
|
||||
<span class="ml-2">Change language</span>
|
||||
</b-button> -->
|
||||
</div>
|
||||
|
||||
<div class="mt-1 text-center d-flex justify-content-between small">
|
||||
<div class="mt-1 text-center d-flex justify-content-between">
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<img
|
||||
src="/logo.png"
|
||||
|
@ -26,21 +19,17 @@
|
|||
target="_blank"
|
||||
title="GitHub"
|
||||
v-b-tooltip.hover
|
||||
size="sm"
|
||||
class="ml-1"
|
||||
>
|
||||
<i class="fa-brands fa-github fa-fw" />
|
||||
<i class="fa-brands fa-github" />
|
||||
</b-button>
|
||||
|
||||
<b-button
|
||||
v-b-toggle.keyboard-shortcuts-sidebar
|
||||
:variant="darkTheme ? 'dark' : 'light'"
|
||||
title="Keyboard Shortcuts"
|
||||
size="sm"
|
||||
v-b-tooltip.hover
|
||||
class="ml-1"
|
||||
>
|
||||
<i class="fa-solid fa-command fa-fw" />
|
||||
<i class="fa-solid fa-command" />
|
||||
</b-button>
|
||||
|
||||
<b-button
|
||||
|
@ -48,23 +37,19 @@
|
|||
id="help"
|
||||
title="Help"
|
||||
:variant="darkTheme ? 'dark' : 'light'"
|
||||
size="sm"
|
||||
v-b-tooltip.hover
|
||||
class="ml-1"
|
||||
>
|
||||
<i class="fa-regular fa-circle-info fa-fw" />
|
||||
<i class="fa-regular fa-circle-info" />
|
||||
</b-button>
|
||||
|
||||
<b-button
|
||||
@click="toggleTheme"
|
||||
:variant="darkTheme ? 'dark' : 'light'"
|
||||
size="sm"
|
||||
v-b-tooltip.hover
|
||||
class="ml-1"
|
||||
title="Toggle theme"
|
||||
>
|
||||
<i v-if="darkTheme" class="fa-solid fa-sun fa-fw" />
|
||||
<i v-else class="fa-solid fa-moon fa-fw" />
|
||||
<i v-if="darkTheme" class="fa-solid fa-sun" />
|
||||
<i v-else class="fa-solid fa-moon" />
|
||||
</b-button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
112
src/components/MiniBoards/GridMiniBoard.vue
Normal file
112
src/components/MiniBoards/GridMiniBoard.vue
Normal file
|
@ -0,0 +1,112 @@
|
|||
<template>
|
||||
<div v-if="isGrid" class="grid">
|
||||
<b-avatar
|
||||
v-for="(game, index) in firstList.games"
|
||||
v-b-tooltip.hover
|
||||
:key="index"
|
||||
:style="`border-radius: 4px !important;`"
|
||||
:variant="darkTheme ? 'black' : 'light'"
|
||||
:title="game.name"
|
||||
:src="showGameThumbnails ? game.src : null"
|
||||
text=" "
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-else
|
||||
class="board d-flex rounded overflow-hidden justify-content-center"
|
||||
>
|
||||
<b-card
|
||||
body-class="p-0"
|
||||
:bg-variant="darkTheme ? 'black' : 'transparent'"
|
||||
:text-variant="darkTheme ? 'light' : 'dark'"
|
||||
style="width: 80px"
|
||||
class="overflow-hidden align-self-start"
|
||||
>
|
||||
<template v-if="firstList.games.length">
|
||||
<div
|
||||
v-for="(game, index) in firstList.games"
|
||||
:key="index"
|
||||
:class="[
|
||||
currentGameId === game.id
|
||||
? 'border bg-danger border-danger'
|
||||
: darkTheme
|
||||
? 'border-black bg-dark'
|
||||
: 'border-light bg-white',
|
||||
{ 'border-bottom': index !== firstList.games.length - 1 },
|
||||
]"
|
||||
class=""
|
||||
>
|
||||
<b-avatar
|
||||
:style="`border-radius: 4px !important;`"
|
||||
text=" "
|
||||
:variant="darkTheme ? 'black' : 'light'"
|
||||
class="m-1"
|
||||
v-b-tooltip.hover
|
||||
:title="game.name"
|
||||
:src="showGameThumbnails ? game.src : null"
|
||||
size="20"
|
||||
/>
|
||||
|
||||
<small v-if="board.ranked">{{ index + 1 }}</small>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div
|
||||
v-else
|
||||
class="rounded overflow-hidden"
|
||||
style="height: 22px; width: 60px;"
|
||||
/>
|
||||
</b-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters, mapState } from 'vuex';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
board: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
|
||||
computed: {
|
||||
...mapGetters(['darkTheme', 'showGameThumbnails']),
|
||||
...mapState(['routeName', 'game']),
|
||||
|
||||
currentGameId() {
|
||||
return this.routeName === 'game'
|
||||
? this.game?.id
|
||||
: null;
|
||||
},
|
||||
|
||||
isGrid() {
|
||||
return this.board?.grid;
|
||||
},
|
||||
|
||||
firstList() {
|
||||
return this.board?.lists?.[0] || {};
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.grid {
|
||||
grid-gap: .5rem;
|
||||
display: grid;
|
||||
max-width: 296px;
|
||||
padding: .5rem;
|
||||
margin: 0 auto;
|
||||
grid-template-columns: repeat(6, 1fr);
|
||||
|
||||
@media(max-width: 768px) {
|
||||
justify-content: start;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
width: 152px;
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -1,8 +1,7 @@
|
|||
<template lang="html">
|
||||
<b-sidebar
|
||||
id="filtersSidebar"
|
||||
v-bind="sidebarProps"
|
||||
right
|
||||
v-bind="sidebarRightProps"
|
||||
>
|
||||
<template #default="{ hide }">
|
||||
<SidebarHeader @hide="hide" title="Filter search results" />
|
||||
|
@ -151,7 +150,7 @@ export default {
|
|||
|
||||
computed: {
|
||||
...mapState(['platforms']),
|
||||
...mapGetters(['darkTheme', 'sidebarProps']),
|
||||
...mapGetters(['darkTheme', 'sidebarRightProps']),
|
||||
|
||||
sortedPlatforms() {
|
||||
return orderby(this.platforms, [platform => platform.name]);
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<template lang="html">
|
||||
<b-sidebar
|
||||
id="settingsSidebar"
|
||||
v-bind="sidebarProps"
|
||||
v-bind="sidebarLeftProps"
|
||||
z-index="2001"
|
||||
>
|
||||
<template #default="{ hide }">
|
||||
|
@ -115,7 +115,7 @@ export default {
|
|||
|
||||
computed: {
|
||||
...mapState(['settings']),
|
||||
...mapGetters(['darkTheme', 'showGameThumbnails', 'transparencyEnabled', 'ageRating', 'navPosition', 'sidebarProps']),
|
||||
...mapGetters(['darkTheme', 'showGameThumbnails', 'transparencyEnabled', 'ageRating', 'navPosition', 'sidebarLeftProps']),
|
||||
|
||||
ageRatingOptions() {
|
||||
return AGE_RATINGS.map((rating) => ({
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
<template lang="html">
|
||||
<b-sidebar
|
||||
id="wallpaper-details-sidebar"
|
||||
v-bind="sidebarProps"
|
||||
right
|
||||
v-bind="sidebarRightProps"
|
||||
:visible="visible"
|
||||
@hidden="closeSidebar"
|
||||
>
|
||||
|
@ -126,7 +125,7 @@ export default {
|
|||
|
||||
computed: {
|
||||
...mapState(['boards', 'wallpapers', 'activeWallpaper']),
|
||||
...mapGetters(['darkTheme', 'sidebarProps']),
|
||||
...mapGetters(['darkTheme', 'sidebarRightProps']),
|
||||
|
||||
formattedBoards() {
|
||||
return this.boards.map((board) => ({ ...board, backgroundUrl: this.getWallpaperUrl(board.backgroundUrl) }));
|
||||
|
|
|
@ -22,6 +22,7 @@ export const NO_IMAGE_PATH = '/placeholder.gif';
|
|||
export const BOARD_TYPE_STANDARD = 'standard';
|
||||
export const BOARD_TYPE_KANBAN = 'kanban';
|
||||
export const BOARD_TYPE_TIER = 'tier';
|
||||
export const BOARD_TYPE_GRID = 'grid';
|
||||
|
||||
export const SORT_TYPE_ALPHABETICALLY = 'alphabetically';
|
||||
export const SORT_TYPE_RATING = 'rating';
|
||||
|
@ -32,7 +33,8 @@ export const HIGHLIGHTED_GAME_TIMEOUT = 5000;
|
|||
|
||||
export const BOARD_TYPES = [
|
||||
{ text: 'Kanban', value: BOARD_TYPE_KANBAN },
|
||||
{ text: 'Standard', value: BOARD_TYPE_STANDARD },
|
||||
{ text: 'List', value: BOARD_TYPE_STANDARD },
|
||||
{ text: 'Grid', value: BOARD_TYPE_GRID },
|
||||
{ text: 'Tier', value: BOARD_TYPE_TIER },
|
||||
];
|
||||
|
||||
|
@ -47,8 +49,9 @@ export const DEFAULT_BOARD_BASE = {
|
|||
name: '',
|
||||
ranked: false,
|
||||
isPublic: false,
|
||||
grid: false,
|
||||
darkTheme: false,
|
||||
backgroundColor: null,
|
||||
backgroundUrl: null,
|
||||
}
|
||||
|
||||
export const DEFAULT_PROFILE = {
|
||||
|
|
|
@ -38,6 +38,9 @@ Vue.use(BootstrapVue, {
|
|||
BButton: { variant: 'secondary' },
|
||||
BAvatar: { variant: 'muted' },
|
||||
BDropdown: { variant: 'primary' },
|
||||
BToast: {
|
||||
noCloseButton: true,
|
||||
},
|
||||
});
|
||||
|
||||
Vue.component('ModalHeader', ModalHeader);
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
<div v-else-if="hasAccess">
|
||||
<EditListSidebar v-if="isBoardOwner && board.type !== $options.BOARD_TYPE_STANDARD" />
|
||||
<CloneBoardSidebar v-if="user" />
|
||||
|
||||
<portal to="pageTitle">
|
||||
<div class="d-flex flex-column">
|
||||
|
@ -31,17 +32,28 @@
|
|||
</portal>
|
||||
|
||||
<portal to="headerActions">
|
||||
<b-button
|
||||
v-if="canEdit"
|
||||
:variant="darkTheme ? 'success' : 'primary'"
|
||||
v-b-toggle.edit-board-sidebar
|
||||
<b-dropdown
|
||||
:variant="darkTheme ? 'black' : 'light'"
|
||||
right
|
||||
no-caret
|
||||
>
|
||||
<i class="fa-solid fa-pen" />
|
||||
</b-button>
|
||||
<template #button-content>
|
||||
<i class="fa-solid fa-ellipsis-vertical px-1" />
|
||||
</template>
|
||||
|
||||
<b-dropdown-item v-if="canEdit" v-b-toggle.edit-board-sidebar>
|
||||
<i class="fa-solid fa-pen fa-fw" /> Edit board
|
||||
</b-dropdown-item>
|
||||
|
||||
<b-dropdown-item v-b-toggle.clone-board-sidebar>
|
||||
<i class="fa-regular fa-clone fa-fw" /> Clone board
|
||||
</b-dropdown-item>
|
||||
</b-dropdown>
|
||||
</portal>
|
||||
|
||||
<StandardBoard v-if="board.type === $options.BOARD_TYPE_STANDARD" />
|
||||
<TierBoard v-else-if="board.type === $options.BOARD_TYPE_TIER" />
|
||||
<GridBoard v-else-if="board.type === $options.BOARD_TYPE_GRID" />
|
||||
<KanbanBoard v-else />
|
||||
</div>
|
||||
|
||||
|
@ -57,24 +69,34 @@
|
|||
<script>
|
||||
import BoardPlaceholder from '@/components/Board/BoardPlaceholder';
|
||||
import KanbanBoard from '@/components/Board/KanbanBoard';
|
||||
import GridBoard from '@/components/Board/GridBoard';
|
||||
import TierBoard from '@/components/Board/TierBoard';
|
||||
import StandardBoard from '@/components/Board/StandardBoard';
|
||||
import EditListSidebar from '@/components/Lists/EditListSidebar';
|
||||
import CloneBoardSidebar from '@/components/CloneBoardSidebar';
|
||||
import chunk from 'lodash.chunk';
|
||||
import { getImageThumbnail } from '@/utils';
|
||||
import { BOARD_TYPE_STANDARD, BOARD_TYPE_TIER, MAX_QUERY_LIMIT } from '@/constants';
|
||||
import {
|
||||
BOARD_TYPE_STANDARD,
|
||||
BOARD_TYPE_TIER,
|
||||
BOARD_TYPE_GRID,
|
||||
MAX_QUERY_LIMIT
|
||||
} from '@/constants';
|
||||
import { mapState, mapGetters } from 'vuex';
|
||||
|
||||
export default {
|
||||
BOARD_TYPE_STANDARD,
|
||||
BOARD_TYPE_TIER,
|
||||
BOARD_TYPE_GRID,
|
||||
|
||||
components: {
|
||||
BoardPlaceholder,
|
||||
KanbanBoard,
|
||||
TierBoard,
|
||||
CloneBoardSidebar,
|
||||
EditListSidebar,
|
||||
GridBoard,
|
||||
KanbanBoard,
|
||||
StandardBoard,
|
||||
TierBoard,
|
||||
},
|
||||
|
||||
data() {
|
||||
|
|
|
@ -274,7 +274,6 @@
|
|||
</article>
|
||||
|
||||
<aside>
|
||||
<AmazonLink class="mr-3" />
|
||||
<GameInBoards class="d-none d-lg-inline" />
|
||||
|
||||
<template v-if="highlightedAchievements">
|
||||
|
|
|
@ -32,14 +32,30 @@ export default {
|
|||
return game?.websites?.map(({ url, category }) => ({ url, ...LINKS_CATEGORIES[category] })) || [];
|
||||
},
|
||||
|
||||
sidebarProps(state, getters) {
|
||||
sidebarRightProps(state, getters) {
|
||||
return {
|
||||
scrollable: true,
|
||||
shadow: true,
|
||||
backdrop: true,
|
||||
'no-header': true,
|
||||
'bg-variant': getters?.darkTheme ? 'dark' : 'light',
|
||||
'text-variant': getters?.darkTheme ? 'light' : 'dark',
|
||||
right: true,
|
||||
noHeader: true,
|
||||
bgVariant: getters?.darkTheme ? 'dark' : 'light',
|
||||
textVariant: getters?.darkTheme ? 'light' : 'dark',
|
||||
sidebarClass: ['rounded-left border border-right-0', getters?.darkTheme ? 'border-black' : 'border-white'],
|
||||
bodyClass: 'rounded-left',
|
||||
}
|
||||
},
|
||||
|
||||
sidebarLeftProps(state, getters) {
|
||||
return {
|
||||
scrollable: true,
|
||||
shadow: true,
|
||||
backdrop: true,
|
||||
noHeader: true,
|
||||
bgVariant: getters?.darkTheme ? 'dark' : 'light',
|
||||
textVariant: getters?.darkTheme ? 'light' : 'dark',
|
||||
sidebarClass: ['rounded-right border border-left-0', getters?.darkTheme ? 'border-black' : 'border-white'],
|
||||
bodyClass: 'rounded-right',
|
||||
}
|
||||
},
|
||||
|
||||
|
|
|
@ -58,11 +58,11 @@ body.dark {
|
|||
height: 10px;
|
||||
width: 10px;
|
||||
}
|
||||
|
||||
|
||||
*::-webkit-scrollbar-track { background-color: var(--dark); }
|
||||
*::-webkit-scrollbar-track:hover { background-color: var(--dark); }
|
||||
*::-webkit-scrollbar-track:active { background-color: var(--dark); }
|
||||
*::-webkit-scrollbar-thumb { background-color: var(--info); }
|
||||
*::-webkit-scrollbar-thumb:hover { background-color: var(--info); }
|
||||
*::-webkit-scrollbar-thumb:active { background-color: darken($info, 5%); }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -854,15 +854,15 @@ $modal-scale-transform: scale(1.5) !default;
|
|||
|
||||
// Toasts
|
||||
// $toast-max-width: 200px !default;
|
||||
// $toast-padding-x: .5rem !default;
|
||||
// $toast-padding-y: .5rem !default;
|
||||
// $toast-font-size: $font-size-base !default;
|
||||
// $toast-color: $white !default;
|
||||
// $toast-background-color: $black;
|
||||
// $toast-border-width: 0 !default;
|
||||
// // $toast-border-color: rgba(0, 0, 0, .1) !default;
|
||||
// $toast-border-radius: $border-radius !default;
|
||||
// $toast-box-shadow: none !default;
|
||||
$toast-padding-x: .5rem !default;
|
||||
$toast-padding-y: .5rem !default;
|
||||
$toast-font-size: $font-size-base !default;
|
||||
$toast-color: $light !default;
|
||||
$toast-background-color: $black;
|
||||
$toast-border-width: 0 !default;
|
||||
// $toast-border-color: rgba(0, 0, 0, .1) !default;
|
||||
$toast-border-radius: $border-radius !default;
|
||||
$toast-box-shadow: none !default;
|
||||
// $toast-header-color: $white !default;
|
||||
// $toast-header-background-color: $black !default;
|
||||
// $toast-header-border-color: transparent !default;
|
||||
|
|
Loading…
Reference in a new issue