Lots of clean up and minor improvements (#58)

This commit is contained in:
Roman Cervantes 2019-03-28 14:23:09 -07:00 committed by GitHub
parent 2d994e44fe
commit 4d43536854
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
25 changed files with 1139 additions and 365 deletions

View file

@ -4,7 +4,9 @@ const axios = require('axios');
axios.defaults.headers.common['user-key'] = functions.config().igdb.key;
const igdbUrl = 'https://api-endpoint.igdb.com';
const igdbV3Url = 'https://api-v3.igdb.com/search/';
const igdbFields = 'id,name,slug,created_at,updated_at,summary,rating,category,player_perspectives,release_dates,name,cover,platforms,screenshots,videos,websites,esrb,pegi,themes.name,game.name&expand=game,themes,developers,publishers,game_engines,game_modes,genres,platforms,player_perspectives';
const igdbV3Fields = 'fields alternative_name,character,collection,company,description,game,name,person,platform,popularity,published_at,test_dummy,theme;';
const igdbFieldsMinimal = 'id,name,slug,rating,name,cover';
exports.search = functions.https.onRequest((req, res) => {
@ -46,3 +48,34 @@ exports.platform = functions.https.onRequest((req, res) => {
.then(({ data }) => { res.status(200).send(data) })
.catch(() => { res.send(400) });
});
//
// // IGDB V3
// exports.searchV3 = functions.https.onRequest((req, res) => {
// res.set('Access-Control-Allow-Origin', "*")
//
// const { searchText, platform } = req.query;
//
// const data = `
// search "${searchText}";
// fields game.*;
// where game != null;
// `;
//
// console.log(data);
//
// axios({
// url: igdbV3Url,
// method: 'POST',
// headers: {
// 'Accept': 'application/json',
// 'user-key': '3b516a46c3af209bb6e287e9090d720c'
// },
// data,
// })
// .then(({ data }) => { res.status(200).send(data) })
// .catch(() => { res.send(400) });
// // res.send(req.body)
// // axios.get(`${igdbV3Url}/games/?search=${searchText}&fields=${igdbFields}&filter[platforms][eq]=${platformId}&limit=20&order=popularity:desc`)
// // .then(({ data }) => { res.status(200).send(data) })
// // .catch(() => { res.send(400) });
// });

View file

@ -29,6 +29,7 @@
"vue-analytics": "^5.16.0",
"vue-axios": "^2.1.1",
"vue-gallery": "^1.5.0",
"vue-github-button": "^1.0.5",
"vue-gravatar": "^1.2.1",
"vue-i18n": "^8.0.0",
"vue-markdown": "^2.2.4",

View file

@ -13,12 +13,15 @@
</div>
<toast />
<copyright-footer />
</div>
</template>
<script>
import NavHeader from '@/components/NavHeader/NavHeader';
import Toast from '@/components/Toast/Toast';
import CopyrightFooter from '@/components/CopyrightFooter/CopyrightFooter';
import firebase from 'firebase/app';
import { debounce } from 'lodash';
import 'firebase/auth';
@ -42,6 +45,7 @@ export default {
components: {
NavHeader,
Toast,
CopyrightFooter,
},
computed: {

View file

@ -0,0 +1,91 @@
<template lang="html">
<footer :class="{ fixed }">
<section>
<i class="far fa-copyright" /> {{ moment().format('YYYY') }} Gamebrary.
</section>
<section>
<i class="fas fa-code" />
{{ $t('global.by') }}
<a href="https://twitter.com/romancm" target="_blank">@romancm</a>
</section>
<section>
<modal title="Releases" large padded v-if="latestRelease">
<strong>
<i class="fab fa-github" />
{{ latestRelease }}
</strong>
<releases slot="content" />
</modal>
</section>
</footer>
</template>
<script>
import moment from 'moment';
import Releases from '@/components/Releases/Releases';
import Modal from '@/components/Modal/Modal';
import { mapState } from 'vuex';
export default {
components: {
Releases,
Modal,
},
data() {
return {
moment,
};
},
computed: {
...mapState(['releases']),
fixed() {
return this.$route.name === 'game-board';
},
latestRelease() {
return this.releases && this.releases.length
? this.releases[0].tag_name
: 'nothing';
},
},
mounted() {
this.$store.dispatch('LOAD_RELEASES');
},
};
</script>
<style lang="scss" rel="stylesheet/scss" scoped>
@import "~styles/styles.scss";
footer {
bottom: 0;
width: 100%;
display: grid;
grid-gap: $gp / 2;
grid-template-columns: auto auto auto;
justify-content: center;
padding: $gp / 2 0;
color: $color-dark-gray;
font-size: 10px;
&.fixed {
position: fixed;
}
a {
color: $color-dark-gray;
font-weight: bold;
}
strong {
cursor: pointer;
}
}
</style>

View file

@ -3,9 +3,12 @@
<img :src="coverUrl" @click="openDetails" :alt="game.name">
<div class="game-info">
<a @click="openDetails">
<h4 v-text="game.name" />
</a>
<a v-text="game.name" @click="openDetails" />
<i
v-if="!searchResult"
class="fas fa-grip-vertical game-drag-handle"
/>
<game-rating
v-if="showGameRatings"
@ -14,10 +17,14 @@
@click.native="openDetails"
/>
<div class="tags" v-if="!searchResult && tags">
<div v-for="({ games, hex }, name) in tags" :key="name">
<div class="tags game-tag" v-if="!searchResult && tags">
<div
v-for="({ games, hex }, name) in tags"
:key="name"
v-if="games.includes(game.id)"
>
<button
class="tag small"
class="tag small game-tag"
:style="`background-color: ${hex}`"
v-if="games.includes(game.id)"
@click="openTags"
@ -25,7 +32,6 @@
{{ name }}
</button>
</div>
</div>
<button
@ -37,42 +43,21 @@
Add to {{ addToLabel }}
</button>
<div :class="['game-card-options', { dark: darkModeEnabled}]" v-else>
<button
v-if="!searchResult"
:class="['small tiny game-drag-handle', {
'accent filled': !darkModeEnabled,
info: darkModeEnabled
}]"
title="Drag game"
>
<i class="far fa-hand-paper" />
</button>
<button
<div v-else>
<i
v-if="hasTags"
:class="['small tiny', {
'accent filled': !darkModeEnabled,
info: darkModeEnabled
}]"
class="fas fa-tag tags"
@click="openTags"
>
<i class="fas fa-tag" />
</button>
/>
<button
<i
class="far fa-trash-alt delete-game"
v-if="list.games.includes(gameId)"
@click="removeGame"
title="Delete game"
:class="['small tiny error', {
filled: !darkModeEnabled,
info: darkModeEnabled
}]"
>
<i class="far fa-trash-alt" />
</button>
@click="removeGame"
/>
<button v-else @click="addGame" title="Add game">
<button v-if="!list.games.includes(gameId)" @click="addGame" title="Add game">
<i class="fas fa-plus" />
</button>
</div>
@ -99,8 +84,14 @@ export default {
searchResult: Boolean,
},
data() {
return {
showEditOptions: false,
};
},
computed: {
...mapState(['settings', 'games', 'gameLists', 'platform', 'user', 'tags']),
...mapState(['settings', 'games', 'gameLists', 'platform', 'user', 'tags', 'activeList']),
...mapGetters(['darkModeEnabled']),
@ -249,11 +240,12 @@ export default {
.game-info {
padding: $gp / 2 $gp;
width: 100%;
display: grid;
grid-gap: 4px;
display: flex;
flex-direction: column;
.game-rating, a {
display: inline-flex;
font-weight: bold;
}
&:hover {
@ -265,23 +257,43 @@ export default {
a {
color: $color-darkest-gray;
cursor: pointer;
margin-right: $gp / 2;
}
}
&:hover {
.game-card-options {
opacity: 1;
.game-drag-handle {
@include drag-cursor;
position: absolute;
color: $color-light-gray;
right: $gp / 3;
top: $gp / 3;
&:hover {
color: $color-gray;
}
}
.game-card-options {
opacity: 0;
width: auto;
transition: all 200ms linear;
.delete-game {
position: absolute;
color: $color-light-gray;
bottom: $gp / 3;
right: $gp / 3;
.game-drag-handle {
@include drag-cursor;
&:hover {
color: $color-red;
}
}
.tags {
color: $color-light-gray;
&:hover {
color: $color-blue;
}
}
.game-tag {
margin-bottom: $gp / 3;
}
}
</style>

View file

@ -1,7 +1,6 @@
<template lang="html">
<div class="amazon">
<div class="amazon" v-if="affiliateLinks[game.id]">
<a
v-if="affiliateLinks[game.id]"
:href="affiliateLinks[game.id]"
target="_blank"
class="link warning small"

View file

@ -1,5 +1,5 @@
<template lang="html">
<div class="links" v-if="game && game.websites">
<div class="links" v-if="hasWebsites">
<a
v-for="{ category, url } in game.websites"
:key="category"
@ -38,6 +38,10 @@ export default {
},
computed: {
...mapState(['game']),
hasWebsites() {
return this.game && this.game.websites;
},
},
methods: {

View file

@ -1,6 +1,5 @@
<template lang="html">
<div :class="['game-rating', { small, dark }]">
<span v-for="n in 5" :key="`star-${n}`">
<i class="fas fa-star" v-if="(roundedRating - n) + 1 >= 1" />
<i class="fas fa-star-half" v-if="(roundedRating - n) + 1 === .5" />
@ -38,6 +37,7 @@ export default {
.game-rating {
color: $color-orange;
font-size: 20px;
margin: $gp / 4 0;
&.small {
font-size: 12px;

View file

@ -5,7 +5,12 @@
>
<h3>Screenshots</h3>
<vue-gallery :images="screenshots" :index="index" @close="close" />
<vue-gallery
:images="screenshots"
:index="index"
:options="options"
@close="close"
/>
<img
v-for="(image, index) in thumbnails"
@ -28,6 +33,9 @@ export default {
data() {
return {
index: null,
options: {
// TODO: customize look and feel
},
};
},

View file

@ -32,17 +32,10 @@
<div class="search-actions">
<button class="small filled info" @click="back" title="back">
<i class="fas fa-chevron-left" />
Back
</button>
<igdb-credit linkable />
<button
class="small filled info"
@click="clear"
:title="$t('clearResults')"
>
<i class="fas fa-broom" />
</button>
</div>
</form>
</template>

View file

@ -73,7 +73,7 @@
<modal
:action-text="`Delete forever`"
:message="`Your ${platform.name} collection will be deleted forever.`"
title="Are you sure?"
:title="`Delete ${platform.name} collection`"
padded
show-close
@action="deletePlatform"

View file

@ -4,9 +4,14 @@
<slot />
</div>
<div :class="['modal', { show }]" @click="close">
<div :class="['modal', { show, popover }]" @click="close">
<i
v-if="popover"
:class="['fas fa-caret-up popover-arrow', { dark: darkModeEnabled }]"
/>
<div :class="['content', { large, padded, dark: darkModeEnabled }]" @click.stop>
<header>
<header v-if="!popover">
<h2 v-if="title">{{ title }}</h2>
<i class="close fas fa-times" @click="close" />
</header>
@ -56,6 +61,7 @@ export default {
type: Boolean,
default: false,
},
popover: Boolean,
large: Boolean,
padded: Boolean,
},
@ -109,52 +115,64 @@ export default {
visibility: hidden;
cursor: pointer;
&.popover {
background: none;
justify-content: flex-end;
align-items: baseline;
}
&.show {
visibility: visible;
transition: all 100ms linear;
opacity: 1;
}
}
.content {
position: relative;
background-color: $color-white;
height: auto;
width: 380px;
max-height: calc(85vh);
max-width: 100%;
overflow: auto;
margin: 0 $gp;
padding: 0;
border-radius: $border-radius;
cursor: default;
.content {
position: relative;
background-color: $color-white;
height: auto;
width: 380px;
max-height: calc(85vh);
max-width: 100%;
overflow: auto;
margin: 0 $gp;
padding: 0;
border-radius: $border-radius;
cursor: default;
&.dark {
background-color: $color-darkest-gray;
color: $color-gray;
}
&.dark {
background-color: $color-darkest-gray;
color: $color-gray;
border: 1px solid $color-gray;
}
&.large {
width: 780px;
&.large {
width: 780px;
@media($small) {
width: 100vw;
}
}
&.padded {
> section {
padding: 0 $gp $gp;
}
}
@media($small) {
height: auto;
margin: 0;
max-height: 100vh;
height: 100vh;
border-radius: 0;
width: 100vw;
}
}
&.padded {
> section {
padding: 0 $gp $gp;
}
}
@media($small) {
height: auto;
margin: 0;
max-height: 100vh;
height: 100vh;
border-radius: 0;
width: 100vw;
&.popover .content {
max-width: 280px;
margin: $gp * 3 $gp 0;
}
}
@ -186,4 +204,16 @@ header {
grid-gap: $gp;
grid-template-columns: auto auto;
}
.popover-arrow {
color: $color-white;
font-size: 25px;
position: absolute;
top: $gp * 2;
right: $gp * 1.5;
&.dark {
color: $color-gray;
}
}
</style>

View file

@ -1,53 +1,32 @@
<template lang="html">
<nav :class="{ dark: darkModeEnabled }">
<button class="logo" v-if="isShareList">
<img src='/static/gamebrary-logo.png' />
GAMEBRARY
</button>
<router-link
v-else
tag="button"
class="logo"
:to="{ name: homeRoute }"
>
<img src='/static/gamebrary-logo.png' />
{{ logoText }}
{{ title }}
</router-link>
<div class="links" v-if="user">
<modal title="Releases" large padded>
<button :class="whatsNewClass">
<i class="fas fa-bullhorn" />
What's new
</button>
<modal padded popover>
<gravatar :email="user.email" class="avatar" />
<releases slot="content" />
</modal>
<modal
padded
title="Settings"
>
<button class="hollow small">
<i class="fas fa-cog" />
</button>
<settings slot="content" v-if="settings" />
</modal>
</div>
<settings slot="content" v-if="settings && user" />
</modal>
</nav>
</template>
<script>
import Releases from '@/components/Releases/Releases';
import Modal from '@/components/Modal/Modal';
import { mapState, mapGetters } from 'vuex';
import Modal from '@/components/Modal/Modal';
import Settings from '@/components/Settings/Settings';
import Gravatar from 'vue-gravatar';
export default {
components: {
Releases,
Gravatar,
Settings,
Modal,
},
@ -56,29 +35,23 @@ export default {
...mapState(['user', 'platform', 'settings']),
...mapGetters(['darkModeEnabled']),
isAuthRoute() {
return this.$route.name === 'auth';
},
title() {
if (this.$route.name === 'share-list') {
return this.$route.query && this.$route.query.list
? this.$route.query.list.split('-').join(' ')
: 'GAMEBRARY';
}
isShareList() {
return this.$route.name === 'share-list';
},
logoText() {
return this.$route.name === 'game-board' && this.platform
? this.platform.name
: 'GAMEBRARY';
},
whatsNewClass() {
const buttonStyle = this.darkModeEnabled
? 'accent'
: 'info';
return `filled small ${buttonStyle}`;
},
homeRoute() {
if (this.$route.name === 'share-list') {
return null;
}
if (this.$route.name === 'game-detail' && this.platform) {
return 'game-board';
}
@ -103,7 +76,7 @@ export default {
justify-content: space-between;
align-items: center;
padding: 0 $gp;
color: $color-dark-gray;
color: $color-darkest-gray;
.logo {
height: $navHeight;
@ -111,6 +84,7 @@ export default {
display: flex;
align-items: center;
margin-left: -$gp;
text-transform: capitalize;
img {
height: 24px;
@ -121,10 +95,17 @@ export default {
&.dark {
color: $color-gray !important;
}
}
.links {
display: flex;
align-items: center;
img.avatar {
width: 30px;
height: 30px;
border-radius: 100%;
border: 1px solid $color-darkest-gray;
@media($small) {
width: 30px;
height: 30px;
}
}
</style>

View file

@ -1,8 +1,12 @@
<template lang="html">
<div class="releases">
<github-button href="https://github.com/romancmx/gamebrary/subscription" data-show-count="true" aria-label="Watch romancmx/gamebrary on GitHub">Watch</github-button>
<github-button href="https://github.com/romancmx/gamebrary" data-show-count="true" aria-label="Star romancmx/gamebrary on GitHub">Star</github-button>
<github-button href="https://github.com/romancmx/gamebrary/fork" data-show-count="true" aria-label="Fork romancmx/gamebrary on GitHub">Fork</github-button>
<github-button href="https://github.com/romancmx/gamebrary/issues" data-show-count="true" aria-label="Issue romancmx/gamebrary on GitHub">Issue</github-button>
<div
class="release"
v-if="loaded"
v-for="notification in releases"
:key="notification.id"
>
@ -19,8 +23,6 @@
<vue-markdown :source="notification.body" />
</div>
<releases-placeholder v-else />
</div>
</template>
@ -29,43 +31,20 @@ import moment from 'moment';
import VueMarkdown from 'vue-markdown';
import ReleasesPlaceholder from '@/components/Releases/ReleasesPlaceholder';
import { mapState } from 'vuex';
import GithubButton from 'vue-github-button';
export default {
components: {
VueMarkdown,
GithubButton,
ReleasesPlaceholder,
},
data() {
return {
loaded: false,
};
},
computed: {
...mapState(['releases']),
},
mounted() {
this.loadReleases();
},
methods: {
loadReleases() {
this.loaded = false;
this.$store.dispatch('LOAD_RELEASES')
.then(() => {
this.loaded = true;
})
.catch(() => {
this.$bus.$emit('TOAST', {
message: 'Error loading releases',
type: 'error',
});
});
},
formattedDate(date) {
return moment(date).fromNow();
},

View file

@ -3,7 +3,7 @@
class="settings"
:class="{ dark: darkModeEnabled }"
>
<section>
<!-- <section>
<div class="profile">
<gravatar :email="user.email" />
@ -12,7 +12,7 @@
{{ user.email }}
</div>
</div>
</section>
</section> -->
<!-- <section>
<i class="fas fa-language" />
@ -55,7 +55,7 @@
</section>
<section class="actions">
<button class="small info hollow" @click="signOut">
<button class="small tiny accent hollow" @click="signOut">
<i class="fas fa-sign-out-alt" />
{{ $t('settings.signOut') }}
</button>
@ -66,43 +66,12 @@
:action-text="$t('settings.deleteAccount')"
@action="deleteAccount"
>
<button class="small error">
<button class="small tiny error hollow">
<i class="fas fa-exclamation-triangle" />
{{ $t('settings.deleteAccount') }}
</button>
</modal>
</section>
<section>
<small>
Gamebrary is free and open source, consider helping its development by
<a href="https://www.paypal.me/RomanCervantes/5" target="_blank">
{{ $t('settings.donate') }}
</a>
,
<a href="https://github.com/romancmx/gamebrary/issues" target="_blank">
{{ $t('settings.reportBugs') }}
</a>
or
<a href="https://goo.gl/forms/r0juBCsZaUtJ03qb2" target="_blank">
{{ $t('settings.submitFeedback') }}
</a>
.
</small>
</section>
<footer>
<small>
<i class="far fa-copyright" /> 2018 Gamebrary.
<i class="fas fa-code" />
{{ $t('global.with') }}
<i class="fas fa-heart" /> {{ $t('global.by') }}
<a href="https://twitter.com/romancm" target="_blank">@romancm</a>
</small>
<igdb-credit />
</footer>
</div>
</template>
@ -117,9 +86,11 @@ import ToggleSwitch from '@/components/ToggleSwitch/ToggleSwitch';
import IgdbCredit from '@/components/IgdbCredit/IgdbCredit';
import Modal from '@/components/Modal/Modal';
import moment from 'moment';
import ListOptions from '@/components/Lists/ListOptions';
export default {
components: {
ListOptions,
Panel,
ToggleSwitch,
IgdbCredit,
@ -141,6 +112,10 @@ export default {
return moment(this.user.dateJoined).format('LL');
},
isGameBoard() {
return this.$route.name === 'game-board';
},
exitUrl() {
return process.env.NODE_ENV === 'development'
? 'http://localhost:3000'
@ -248,17 +223,10 @@ export default {
width: 600px;
margin: 0 auto;
max-width: 100%;
border-bottom: 1px solid $color-light-gray;
padding: $gp;
display: flex;
align-items: center;
&.actions {
display: grid;
grid-gap: $gp;
grid-template-columns: 1fr 1fr;
}
&.active {
color: $color-green;
}
@ -273,6 +241,14 @@ export default {
}
}
.actions {
border-top: 1px solid $color-light-gray;
display: grid;
grid-gap: $gp / 2;
padding-bottom: 0;
grid-template-columns: 1fr 1fr;
}
.share-link {
max-width: 340px;
margin: 0;
@ -283,19 +259,9 @@ export default {
color: $color-gray;
}
section {
border-bottom: 1px solid $color-dark-gray;
.actions {
border-top: 1px solid $color-gray;
}
}
}
footer {
display: flex;
align-items: center;
flex-direction: column;
small {
margin-top: $gp / 2;
}
}
</style>

View file

@ -1,5 +1,5 @@
<template lang="html">
<div class="tags">
<div class="tags-modal">
<div class="content">
<div class="my-tags">
<h3>My tags</h3>
@ -66,6 +66,9 @@
>
<i class="fas fa-fill-drip" />
</button>
<input type="color" :value="tagHex" @change="updateColor" class="color-picker">
<pre>{{ tagHex }}</pre>
</section>
<section>
@ -109,11 +112,11 @@ export default {
'#073b4c',
],
suggestions: [
'completed',
'digital',
'physical',
'playing',
'abandoned',
'Completed',
'Digital',
'Physical',
'Playing',
'Abandoned',
],
};
},
@ -141,6 +144,10 @@ export default {
},
methods: {
updateColor(e) {
this.tagHex = e.srcElement.value;
},
createTag() {
this.$set(this.localTags, this.tagName, this.newTag);
this.$bus.$emit('SAVE_TAGS', this.localTags);
@ -201,4 +208,8 @@ export default {
.my-tags {
// display: grid;
}
.color-picker {
width: 50px;
}
</style>

View file

@ -85,7 +85,6 @@ export default {
max-width: 300px;
opacity: 0;
z-index: 1;
border-radius: $border-radius;
padding: $gp;
transition: all 200ms linear;

View file

@ -2,7 +2,6 @@ module.exports = {
searchPlaceholder: 'Search here',
share: 'Share',
empty: 'empty',
clearResults: 'Clear results',
back: 'Back',
global: {
cancel: 'Cancel',
@ -12,7 +11,6 @@ module.exports = {
by: 'by',
},
platforms: {
ownLists: 'My lists only',
computersArcade: 'Computers and Arcade',
generation: 'Generation',
options: {

View file

@ -1,105 +1,124 @@
<template lang="html">
<div :class="['platforms-page', { dark: darkModeEnabled }]">
<div class="tools">
<div class="sorting">
<select v-model="showBy">
<option value="generation">{{ $t('platforms.options.generation') }}</option>
<option value="">{{ $t('platforms.options.alphabetically') }}</option>
</select>
<aside>
<div class="button-group">
<button
class="small tiny info"
@click="mineOnly = true"
:class="{ hollow: !mineOnly }"
>
Mine
</button>
<button
class="small tiny info"
@click="mineOnly = false"
:class="{ hollow: mineOnly }"
>
All
</button>
</div>
<input
type="text"
class="platform-filter"
autofocus
v-model="filterText"
:placeholder="$t('global.filter')"
/>
<br>
<br>
<toggle-switch
id="ownedOnly"
v-model="ownedListsOnly"
:label="$t('platforms.ownLists')"
/>
</div>
<div class="button-group">
<button
class="small tiny info"
@click="sortBy = 'generation'"
:class="{ hollow: sortBy !== 'generation' }"
>
Chronologically
</button>
<div :class="['groups', { reverse: showBy === 'generation'}]">
<div
v-for="(group, label) in filteredPlatforms"
:key="label"
>
<div v-if="showBy === 'generation'">
<h3 v-if="label == 0">{{ $t('platforms.computersArcade') }}</h3>
<h3 v-else>{{ ordinalSuffix(label) }} {{ $t('platforms.generation') }}</h3>
</div>
<button
class="small tiny info"
@click="sortBy = 'chronological'"
:class="{ hollow: sortBy !== 'chronological' }"
>
Alphabetically
</button>
</div>
</aside>
<div class="platforms">
<a
v-for="platform in group"
:key="platform.name"
:style="`background-color: ${platform.hex || '#fff'}`"
@click="changePlatform(platform)"
>
<div
v-if="!ownedListsOnly && ownedPlatform(platform.code)"
class="owned-platform"
<main>
<div class="platform-list" :class="{ reverse: sortBy === 'generation'}">
<div
v-for="(group, label) in filteredPlatforms"
:key="label"
>
<div v-if="sortBy === 'generation'">
<h3 v-if="label == 0">{{ $t('platforms.computersArcade') }}</h3>
<h3 v-else>{{ ordinalSuffix(label) }} {{ $t('platforms.generation') }}</h3>
</div>
<div class="platforms">
<a
v-for="platform in group"
:key="platform.name"
:style="`background-color: ${platform.hex || '#fff'}`"
@click="changePlatform(platform)"
>
<i class="fas fa-check" />
</div>
<div
v-if="ownedPlatform(platform.code)"
class="owned-platform"
>
<i class="fas fa-check" />
</div>
<img
:src='`/static/img/platforms/${platform.code}.svg`'
:alt="platform.name"
/>
</a>
<img
:src='`/static/img/platforms/${platform.code}.svg`'
:alt="platform.name"
/>
</a>
</div>
</div>
</div>
</div>
<div class="consoles-book">
<!-- eslint-disable-next-line -->
<a target="_blank" href="https://www.amazon.com/gp/product/1593277431/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=1593277431&linkCode=as2&tag=gamebrary-20&linkId=a253bbe3bfebd787ead2adc20dbb272b">
<!-- eslint-disable-next-line -->
<img src="//ws-na.amazon-adsystem.com/widgets/q?_encoding=UTF8&MarketPlace=US&ASIN=1593277431&ServiceVersion=20070822&ID=AsinImage&WS=1&Format=_SL160_&tag=gamebrary-20">
</a>
<div class="open-source-message">
<small>
Gamebrary is free and open source, consider helping its development by
<a href="https://www.paypal.me/RomanCervantes/5" target="_blank">
{{ $t('settings.donate') }}
</a>
,
<a href="https://github.com/romancmx/gamebrary/issues" target="_blank">
{{ $t('settings.reportBugs') }}
</a>
or
<a href="https://goo.gl/forms/r0juBCsZaUtJ03qb2" target="_blank">
{{ $t('settings.submitFeedback') }}
</a>
.
</small>
<div class="description">
<h3>Book recommendation</h3>
<p>
<strong>
<a href="https://www.amazon.com/gp/product/1593277431/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=1593277431&linkCode=as2&tag=gamebrary-20&linkId=a253bbe3bfebd787ead2adc20dbb272b" target="_blank">The Game Console: A Photographic History from Atari to Xbox</a>
</strong>
</p>
<igdb-credit gray />
<p>
<small>
<strong>GAMEBRARY</strong> gets a small referral commission when
you use our affiliate link to purchase this book. the earnings will go
towards supporting the ongoing development of Gamebrary.
</small>
</p>
</div>
</div>
</main>
</div>
</template>
<script>
import platforms from '@/shared/platforms';
import ToggleSwitch from '@/components/ToggleSwitch/ToggleSwitch';
import IgdbCredit from '@/components/IgdbCredit/IgdbCredit';
import Panel from '@/components/Panel/Panel';
import { groupBy, sortBy } from 'lodash';
import { mapState, mapGetters } from 'vuex';
export default {
components: {
ToggleSwitch,
IgdbCredit,
Panel,
},
data() {
return {
platforms,
filterText: '',
showBy: 'generation',
ownedListsOnly: false,
sortBy: 'generation',
mineOnly: false,
};
},
@ -108,18 +127,19 @@ export default {
...mapGetters(['darkModeEnabled']),
filteredPlatforms() {
const availableLists = this.ownedListsOnly
const availableLists = this.mineOnly
? this.platforms.filter(({ code }) => this.gameLists[code])
: this.platforms;
if (this.filterText.length > 0) {
// eslint-disable-next-line
return groupBy(availableLists.filter(({ name }) => name.toLowerCase().includes(this.filterText.toLowerCase())), this.showBy);
if (this.sortBy === 'generation') {
return groupBy(availableLists, 'generation');
}
return this.showBy
? groupBy(availableLists, this.showBy)
: groupBy(sortBy(availableLists, 'name'), '');
if (this.sortBy === 'chronological') {
return groupBy(sortBy(availableLists, 'name'), '');
}
return groupBy(sortBy(availableLists, 'name'), '');
},
},
@ -160,18 +180,17 @@ export default {
.platforms-page {
padding: 0 $gp $gp;
color: $color-dark-gray;
display: grid;
grid-template-columns: 200px auto;
grid-gap: $gp * 2;
min-height: calc(100vh - #{$navHeight});
&.dark {
color: $color-gray;
}
.groups {
display: flex;
flex-direction: column;
&.reverse {
flex-direction: column-reverse;
}
@media($small) {
grid-template-columns: auto;
}
h3 {
@ -179,30 +198,46 @@ export default {
}
}
.tools {
margin-top: $gp;
aside {
position: sticky;
top: $gp;
height: 200px;
margin-top: $gp * 2;
}
.recommendations {
background: $color-white;
border-radius: $border-radius;
overflow: hidden;
max-width: 100%;
width: 400px;
display: grid;
grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
grid-gap: $gp;
align-items: center;
grid-template-columns: 120px auto;
margin-top: $gp;
.sorting {
align-items: center;
select {
margin-bottom: 0;
}
img {
max-width: 120px;
display: block;
}
.platform-filter {
margin: 0;
.description {
padding: 0 $gp / 2 $gp / 2;
}
}
.platform-list {
display: flex;
flex-direction: column;
&.reverse {
flex-direction: column-reverse;
}
}
.platforms {
margin-top: $gp;
display: grid;
grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
grid-gap: $gp;
@ -211,7 +246,7 @@ export default {
cursor: pointer;
border-radius: $border-radius;
width: auto;
height: 100px;
height: 80px;
padding: $gp;
display: flex;
align-items: center;
@ -257,28 +292,14 @@ export default {
}
}
.consoles-book {
max-width: 100%;
background: $color-white;
display: grid;
grid-gap: $gp;
width: 400px;
margin-top: $gp * 2;
grid-template-columns: 180px auto;
border-radius: $border-radius;
overflow: hidden;
.open-source-message {
margin-top: $gp;
justify-content: center;
display: flex;
align-items: center;
h3 {
margin-top: $gp;
}
img {
width: 100%;
display: block;
}
.description {
padding-right: $gp;
a {
color: $color-dark-gray;
}
}
</style>

View file

@ -93,11 +93,12 @@ export default {
if (gameList.length > 0) {
this.$store.dispatch('LOAD_PUBLIC_GAMES', gameList)
.catch(() => {
this.handleError();
})
.finally(() => {
.then(() => {
this.loading = false;
})
.catch(() => {
this.loading = false;
this.$bus.$emit('TOAST', { message: 'Error loading data', type: 'error' });
});
} else {
this.loading = false;
@ -118,9 +119,8 @@ export default {
height: calc(100vh - #{$navHeight});
overflow-x: auto;
overflow-x: overlay;
padding: $gp;
padding: 0 $gp;
user-select: none;
@include drag-cursor;
}
section {

View file

@ -361,3 +361,621 @@ export default [
generation: 4,
},
];
// {
// name: 'Linux',
// id: 3,
// },
// {
// name: 'Nintendo 64',
// id: 4,
// },
// {
// name: 'Wii',
// id: 5,
// },
// {
// name: 'PC (Microsoft Windows)',
// id: 6,
// },
// {
// name: 'PlayStation',
// id: 7,
// },
// {
// name: 'PlayStation 2',
// id: 8,
// },
// {
// name: 'PlayStation 3',
// id: 9,
// },
// {
// name: 'Xbox',
// id: 11,
// },
// {
// name: 'Xbox 360',
// id: 12,
// },
// {
// name: 'PC DOS',
// id: 13,
// },
// {
// name: 'Mac',
// id: 14,
// },
// {
// name: 'Commodore C64/128',
// id: 15,
// },
// {
// name: 'Amiga',
// id: 16,
// },
// {
// name: 'Nintendo Entertainment System (NES)',
// id: 18,
// },
// {
// name: 'Super Nintendo Entertainment System (SNES)',
// id: 19,
// },
// {
// name: 'Nintendo DS',
// id: 20,
// },
// {
// name: 'Nintendo GameCube',
// id: 21,
// },
// {
// name: 'Game Boy Color',
// id: 22,
// },
// {
// name: 'Dreamcast',
// id: 23,
// },
// {
// name: 'Game Boy Advance',
// id: 24,
// },
// {
// name: 'Amstrad CPC',
// id: 25,
// },
// {
// name: 'ZX Spectrum',
// id: 26,
// },
// {
// name: 'MSX',
// id: 27,
// },
// {
// name: 'Sega Mega Drive/Genesis',
// id: 29,
// },
// {
// name: 'Sega 32X',
// id: 30,
// },
// {
// name: 'Sega Saturn',
// id: 32,
// },
// {
// name: 'Game Boy',
// id: 33,
// },
// {
// name: 'Android',
// id: 34,
// },
// {
// name: 'Sega Game Gear',
// id: 35,
// },
// {
// name: 'Xbox Live Arcade',
// id: 36,
// },
// {
// name: 'Nintendo 3DS',
// id: 37,
// },
// {
// name: 'PlayStation Portable',
// id: 38,
// },
// {
// name: 'iOS',
// id: 39,
// },
// {
// name: 'Wii U',
// id: 41,
// },
// {
// name: 'N-Gage',
// id: 42,
// },
// {
// name: 'Tapwave Zodiac',
// id: 44,
// },
// {
// name: 'PlayStation Network',
// id: 45,
// },
// {
// name: 'PlayStation Vita',
// id: 46,
// },
// {
// name: 'Virtual Console (Nintendo)',
// id: 47,
// },
// {
// name: 'PlayStation 4',
// id: 48,
// },
// {
// name: 'Xbox One',
// id: 49,
// },
// {
// name: '3DO Interactive Multiplayer',
// id: 50,
// },
// {
// name: 'Family Computer Disk System',
// id: 51,
// },
// {
// name: 'Arcade',
// id: 52,
// },
// {
// name: 'MSX2',
// id: 53,
// },
// {
// name: 'Mobile',
// id: 55,
// },
// {
// name: 'WiiWare',
// id: 56,
// },
// {
// name: 'WonderSwan',
// id: 57,
// },
// {
// name: 'Super Famicom',
// id: 58,
// },
// {
// name: 'Atari 2600',
// id: 59,
// },
// {
// name: 'Atari 7800',
// id: 60,
// },
// {
// name: 'Atari Lynx',
// id: 61,
// },
// {
// name: 'Atari Jaguar',
// id: 62,
// },
// {
// name: 'Atari ST/STE',
// id: 63,
// },
// {
// name: 'Sega Master System',
// id: 64,
// },
// {
// name: 'Atari 8-bit',
// id: 65,
// },
// {
// name: 'Atari 5200',
// id: 66,
// },
// {
// name: 'Intellivision',
// id: 67,
// },
// {
// name: 'ColecoVision',
// id: 68,
// },
// {
// name: 'BBC Microcomputer System',
// id: 69,
// },
// {
// name: 'Vectrex',
// id: 70,
// },
// {
// name: 'Commodore VIC-20',
// id: 71,
// },
// {
// name: 'Ouya',
// id: 72,
// },
// {
// name: 'BlackBerry OS',
// id: 73,
// },
// {
// name: 'Windows Phone',
// id: 74,
// },
// {
// name: 'Apple II',
// id: 75,
// },
// {
// name: 'Sharp X1',
// id: 77,
// },
// {
// name: 'Sega CD',
// id: 78,
// },
// {
// name: 'Neo Geo MVS',
// id: 79,
// },
// {
// name: 'Neo Geo AES',
// id: 80,
// },
// {
// name: 'Web browser',
// id: 82,
// },
// {
// name: 'SG-1000',
// id: 84,
// },
// {
// name: 'Donner Model 30',
// id: 85,
// },
// {
// name: 'TurboGrafx-16/PC Engine',
// id: 86,
// },
// {
// name: 'Virtual Boy',
// id: 87,
// },
// {
// name: 'Odyssey',
// id: 88,
// },
// {
// name: 'Microvision',
// id: 89,
// },
// {
// name: 'Commodore PET',
// id: 90,
// },
// {
// name: 'Bally Astrocade',
// id: 91,
// },
// {
// name: 'SteamOS',
// id: 92,
// },
// {
// name: 'Commodore 16',
// id: 93,
// },
// {
// name: 'Commodore Plus/4',
// id: 94,
// },
// {
// name: 'PDP-1',
// id: 95,
// },
// {
// name: 'PDP-10',
// id: 96,
// },
// {
// name: 'PDP-8',
// id: 97,
// },
// {
// name: 'DEC GT40',
// id: 98,
// },
// {
// name: 'Family Computer (FAMICOM)',
// id: 99,
// },
// {
// name: 'Analogue electronics',
// id: 100,
// },
// {
// name: 'Ferranti Nimrod Computer',
// id: 101,
// },
// {
// name: 'EDSAC',
// id: 102,
// },
// {
// name: 'PDP-7',
// id: 103,
// },
// {
// name: 'HP 2100',
// id: 104,
// },
// {
// name: 'HP 3000',
// id: 105,
// },
// {
// name: 'SDS Sigma 7',
// id: 106,
// },
// {
// name: 'Call-A-Computer time-shared mainframe computer system',
// id: 107,
// },
// {
// name: 'PDP-11',
// id: 108,
// },
// {
// name: 'CDC Cyber 70',
// id: 109,
// },
// {
// name: 'PLATO',
// id: 110,
// },
// {
// name: 'Imlac PDS-1',
// id: 111,
// },
// {
// name: 'Microcomputer',
// id: 112,
// },
// {
// name: 'OnLive Game System',
// id: 113,
// },
// {
// name: 'Amiga CD32',
// id: 114,
// },
// {
// name: 'Apple IIGS',
// id: 115,
// },
// {
// name: 'Acorn Archimedes',
// id: 116,
// },
// {
// name: 'Philips CD-i',
// id: 117,
// },
// {
// name: 'FM Towns',
// id: 118,
// },
// {
// name: 'Neo Geo Pocket',
// id: 119,
// },
// {
// name: 'Neo Geo Pocket Color',
// id: 120,
// },
// {
// name: 'Sharp X68000',
// id: 121,
// },
// {
// name: 'Nuon',
// id: 122,
// },
// {
// name: 'WonderSwan Color',
// id: 123,
// },
// {
// name: 'SwanCrystal',
// id: 124,
// },
// {
// name: 'PC-8801',
// id: 125,
// },
// {
// name: 'TRS-80',
// id: 126,
// },
// {
// name: 'Fairchild Channel F',
// id: 127,
// },
// {
// name: 'PC Engine SuperGrafx',
// id: 128,
// },
// {
// name: 'Texas Instruments TI-99',
// id: 129,
// },
// {
// name: 'Nintendo Switch',
// id: 130,
// },
// {
// name: 'Nintendo PlayStation',
// id: 131,
// },
// {
// name: 'Amazon Fire TV',
// id: 132,
// },
// {
// name: 'Philips Videopac G7000',
// id: 133,
// },
// {
// name: 'Acorn Electron',
// id: 134,
// },
// {
// name: 'Hyper Neo Geo 64',
// id: 135,
// },
// {
// name: 'Neo Geo CD',
// id: 136,
// },
// {
// name: 'New Nintendo 3DS',
// id: 137,
// },
// {
// name: 'VC 4000',
// id: 138,
// },
// {
// name: '1292 Advanced Programmable Video System',
// id: 139,
// },
// {
// name: 'AY-3-8500',
// id: 140,
// },
// {
// name: 'AY-3-8610',
// id: 141,
// },
// {
// name: 'PC-50X Family',
// id: 142,
// },
// {
// name: 'AY-3-8760',
// id: 143,
// },
// {
// name: 'AY-3-8710',
// id: 144,
// },
// {
// name: 'AY-3-8603',
// id: 145,
// },
// {
// name: 'AY-3-8605',
// id: 146,
// },
// {
// name: 'AY-3-8606',
// id: 147,
// },
// {
// name: 'AY-3-8607',
// id: 148,
// },
// {
// name: 'PC-98',
// id: 149,
// },
// {
// name: 'Turbografx-16/PC Engine CD',
// id: 150,
// },
// {
// name: 'TRS-80 Color Computer',
// id: 151,
// },
// {
// name: 'FM-7',
// id: 152,
// },
// {
// name: 'Dragon 32/64',
// id: 153,
// },
// {
// name: 'Amstrad PCW',
// id: 154,
// },
// {
// name: 'Tatung Einstein',
// id: 155,
// },
// {
// name: 'Thomson MO5',
// id: 156,
// },
// {
// name: 'NEC PC-6000 Series',
// id: 157,
// },
// {
// name: 'Commodore CDTV',
// id: 158,
// },
// {
// name: 'Nintendo DSi',
// id: 159,
// },
// {
// name: 'Nintendo eShop',
// id: 160,
// },
// {
// name: 'Windows Mixed Reality',
// id: 161,
// },
// {
// name: 'Oculus VR',
// id: 162,
// },
// {
// name: 'SteamVR',
// id: 163,
// },
// {
// name: 'Daydream',
// id: 164,
// },
// {
// name: 'PlayStation VR',
// id: 165,
// },
// {
// name: 'PokĂŠmon mini',
// id: 166,
// },

View file

@ -14,6 +14,21 @@ a.link {
}
}
.button-group {
border-radius: $border-radius;
overflow: hidden;
margin: 0 $gp / 2;
display: inline-flex;
align-items: center;
border: 1px solid $color-dark-gray;
button {
float: left;
border-radius: 0;
border: 0 !important;
}
}
button, a.link {
outline: none;
border: none;
@ -174,7 +189,7 @@ button, a.link {
}
&.info {
color: $color-dark-gray;
color: $color-darkest-gray;
background: transparent;
&:hover {

View file

@ -5,6 +5,7 @@ input, select {
height: 32px;
padding: 0 $gp / 2;
width: 100%;
font-size: 16px;
margin-bottom: $gp;
&.small {

BIN
static/img/tag.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

View file

@ -3802,6 +3802,10 @@ getpass@^0.1.1:
dependencies:
assert-plus "^1.0.0"
github-buttons@^2.1.0:
version "2.2.5"
resolved "https://infusionsoft.jfrog.io/infusionsoft/api/npm/npm/github-buttons/-/github-buttons-2.2.5.tgz#976aa347920cd41c88353d617cdf52ce7641d2e9"
glob-base@^0.3.0:
version "0.3.0"
resolved "https://registry.yarnpkg.com/glob-base/-/glob-base-0.3.0.tgz#dbb164f6221b1c0b1ccf82aea328b497df0ea3c4"
@ -8557,6 +8561,12 @@ vue-gallery@^1.5.0:
dependencies:
blueimp-gallery "^2.33.0"
vue-github-button@^1.0.5:
version "1.0.5"
resolved "https://infusionsoft.jfrog.io/infusionsoft/api/npm/npm/vue-github-button/-/vue-github-button-1.0.5.tgz#fd1d5f9cf7ab3d4faa5a767219a47af9af9a7f0a"
dependencies:
github-buttons "^2.1.0"
vue-gravatar@^1.2.1:
version "1.3.0"
resolved "https://registry.yarnpkg.com/vue-gravatar/-/vue-gravatar-1.3.0.tgz#bbb362d24cd2957b57a475376608780d3161b3b0"