Merge branch 'pro'

This commit is contained in:
Gamebrary 2020-11-02 13:38:34 -07:00
commit a619f4d92d
21 changed files with 622 additions and 448 deletions

View file

@ -1,17 +1,9 @@
# Large header
# Tech stack
# Build Process
- instructions for yarn translate
# Add table of content
# Features
# Feedback
# Contributors
<p align="center">
<img width="200" src="https://user-images.githubusercontent.com/60666270/93530966-567d1200-f8f3-11ea-8fd3-131976105d06.png" alt="Gamebrary logo">
<h2 align="center">Gamebrary</h2>
<p align="center">Open source tool to organize video game collections.</p>
</p>
- test
<p align="center">
<img alt="All Contributors" src="https://img.shields.io/badge/all_contributors-13-orange.svg?style=flat-square">
@ -25,6 +17,20 @@
Gamebrary is an open source tool that helps organize video game collections. Written in javascript using [VueJS](https://github.com/vuejs/vue).
# Table of content
Take me to [pookie](#pookie)
- <a name="features">Features</a>
# Features
# Tech stack
# Build Process
- instructions for yarn translate
# Add table of content
# Feedback
# Contributors
## Get started
```bash
@ -86,3 +92,5 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
<!-- ALL-CONTRIBUTORS-LIST:END -->
This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome!
### <a name="pookie"></a>Some heading

View file

@ -27,7 +27,6 @@
"lodash.groupby": "^4.6.0",
"lodash.orderby": "^4.6.0",
"lodash.sortby": "^4.7.0",
"moment": "^2.22.1",
"node-sass": "^4.8.3",
"raven-js": "^3.27.0",
"sass-loader": "^7.0.1",

View file

@ -4,6 +4,7 @@
:dir="dir"
>
<page-header v-if="user" />
<main
:class="{ 'authorizing': !user, 'bg-dark text-white': nightMode }"
>

View file

@ -1,170 +0,0 @@
<template lang="html">
<b-card
class="mt-4"
no-body
:bg-variant="nightMode ? 'dark' : ''"
:text-variant="nightMode ? 'white' : ''"
>
<dl class="row">
<dt class="col-sm-3">{{ $t('board.gameModal.platforms') }}</dt>
<dd class="col-sm-9">
<b-skeleton />
</dd>
<dt class="col-sm-3">{{ $t('board.gameModal.genres') }}</dt>
<dd class="col-sm-9">
<b-skeleton />
</dd>
<dt class="col-sm-3">{{ $t('board.gameModal.gameModes') }}</dt>
<dd class="col-sm-9">
<b-skeleton />
</dd>
<dt class="col-sm-3">{{ $t('board.gameModal.developers') }}</dt>
<dd class="col-sm-9">
<b-skeleton />
</dd>
<dt class="col-sm-3">{{ $t('board.gameModal.publishers') }}</dt>
<dd class="col-sm-9">
<b-skeleton />
</dd>
<dt class="col-sm-3">{{ $t('board.gameModal.perspective') }}</dt>
<dd class="col-sm-9">
<b-skeleton />
</dd>
<dt class="col-sm-3">{{ $t('board.gameModal.timeToBeat') }}</dt>
<dd class="col-sm-9">
<b-skeleton />
</dd>
<dt class="col-sm-3">{{ $t('board.gameModal.ageRatings') }}</dt>
<dd class="col-sm-9">
<b-skeleton />
</dd>
</dl>
</b-card>
</template>
<script>
import { mapState, mapGetters } from 'vuex';
export default {
props: {
id: {
type: [Number, String],
default: null,
},
},
computed: {
...mapState(['games']),
...mapGetters(['nightMode']),
gamePreviewData() {
return this.games[this.id];
},
coverUrl() {
const game = this.games[this.id];
return game.cover && game.cover.image_id
? `https://images.igdb.com/igdb/image/upload/t_cover_small_2x/${game.cover.image_id}.jpg`
: '/static/no-image.jpg';
},
},
};
</script>
<style lang="scss" rel="stylesheet/scss" scoped>
.game-detail-placeholder {
display: flex;
justify-content: center;
background: var(--modal-background);
min-height: calc(100vh - 48px);
}
.game-hero {
position: absolute;
width: 100%;
left: 0;
height: 400px;
z-index: 2;
@media(max-width: 780px) {
display: none;
}
}
.game-cover {
border: 5px solid #a5a2a2;
background-size: contain;
width: 100%;
height: auto;
@media(max-width: 780px) {
border: 3px solid #a5a2a2;
height: auto;
width: auto;
min-width: auto;
max-width: 100%;
}
}
.game-detail-container {
-webkit-box-shadow: 0 0 2px 0 #a5a2a2;
box-shadow: 0 0 2px 0 #a5a2a2;
width: 900px;
max-width: 100%;
z-index: 2;
margin: 3rem;
padding: 1rem 0;
border-radius: var(--border-radius);
@media(max-width: 780px) {
margin: 0;
padding-top: 3rem;
border-radius: 0;
}
}
.game-detail {
display: grid;
grid-template-columns: 180px auto;
grid-gap: 2rem;
margin: 0 1rem;
@media(max-width: 780px) {
grid-template-columns: auto;
}
}
.game-cover {
--placeholder-image-width: 175px;
--placeholder-image-height: 220px;
@media(max-width: 780px) {
--placeholder-image-width: 240px;
--placeholder-image-height: 300px;
width: 240px;
margin: 0 auto;
}
}
.game-title {
--placeholder-text-height: 30px;
width: 50%;
@media(max-width: 780px) {
width: 50%;
margin: 0 auto;
}
}
.game-rating {
margin-bottom: 1rem;
}
</style>

View file

@ -1,33 +1,31 @@
<template lang="html">
<div>
<dl class="row">
<!-- TODO: plural vs singular translations? -->
<dt class="col-sm-5">{{ $t('board.gameModal.platforms') }}</dt>
<dd class="col-sm-9 text-wrap">{{ platforms }}</dd>
<dl>
<!-- TODO: plural vs singular translations? -->
<dt class="w-100">{{ $t('board.gameModal.platforms') }}</dt>
<dd class="text-wrap">{{ platforms }}</dd>
<dt class="col-sm-3">{{ $t('board.gameModal.genres') }}</dt>
<dd class="col-sm-9 text-wrap">{{ genres }}</dd>
<dt class="w-100">{{ $t('board.gameModal.genres') }}</dt>
<dd class="text-wrap">{{ genres }}</dd>
<dt class="col-sm-3">{{ $t('board.gameModal.gameModes') }}</dt>
<dd class="col-sm-9 text-wrap">{{ gameModes }}</dd>
<dt class="w-100">{{ $t('board.gameModal.gameModes') }}</dt>
<dd class="text-wrap">{{ gameModes }}</dd>
<dt class="col-sm-3">{{ $t('board.gameModal.developers') }}</dt>
<dd class="col-sm-9 text-wrap">{{ gameDevelopers }}</dd>
<dt class="w-100">{{ $t('board.gameModal.developers') }}</dt>
<dd class="text-wrap">{{ gameDevelopers }}</dd>
<dt class="col-sm-3">{{ $t('board.gameModal.publishers') }}</dt>
<dd class="col-sm-9 text-wrap">{{ gamePublishers }}</dd>
<dt class="w-100">{{ $t('board.gameModal.publishers') }}</dt>
<dd class="text-wrap">{{ gamePublishers }}</dd>
<dt class="col-sm-3">{{ $t('board.gameModal.perspective') }}</dt>
<dd class="col-sm-9 text-wrap">{{ playerPerspectives }}</dd>
<dt class="w-100">{{ $t('board.gameModal.perspective') }}</dt>
<dd class="text-wrap">{{ playerPerspectives }}</dd>
<dt class="col-sm-3">{{ $t('board.gameModal.ageRatings') }}</dt>
<dd class="col-sm-9 text-wrap">{{ ageRatings }}</dd>
<dt class="w-100">{{ $t('board.gameModal.ageRatings') }}</dt>
<dd class="text-wrap">{{ ageRatings }}</dd>
<!-- TODO: add release dates -->
<!-- {{ $t('board.gameModal.releaseDate') }} -->
<!-- <pre>{{ game.release_dates }}</pre> -->
</dl>
</div>
<!-- TODO: add release dates -->
<!-- {{ $t('board.gameModal.releaseDate') }} -->
<!-- <pre>{{ game.release_dates }}</pre> -->
</dl>
</template>
<script>

View file

@ -80,15 +80,6 @@
size="lg"
no-border
/>
<!-- <h6>
<b-badge
v-if="releaseDate"
variant="secondary"
>
Releases in
{{ releaseDate }}
</b-badge>
</h6> -->
<br />
@ -126,8 +117,6 @@
<template v-if="loading">
<b-skeleton v-for="n in 3" :key="n" />
<game-detail-placeholder />
</template>
<template v-else>
@ -147,9 +136,7 @@
</template>
<script>
import moment from 'moment';
import { mapState, mapGetters } from 'vuex';
import GameDetailPlaceholder from '@/components/Game/GameDetailPlaceholder';
import GameDetails from '@/components/Game/GameDetails';
import GameNotesTab from '@/components/Game/GameNotesTab';
import GameScreenshots from '@/components/Game/GameScreenshots';
@ -165,7 +152,6 @@ export default {
components: {
GameTags,
IgdbLogo,
GameDetailPlaceholder,
GameDetails,
GameNotesTab,
GameScreenshots,
@ -189,20 +175,6 @@ export default {
...mapState(['gameModalData', 'games', 'platform', 'progresses', 'tags']),
...mapGetters(['nightMode']),
releaseDate() {
const releaseDate = this.game
&& this.game.release_dates
&& this.game.release_dates.find(({ platform }) => this.platform.id === platform);
const formattedDate = releaseDate && releaseDate.date
? moment.unix(releaseDate.date)
: null;
return moment(formattedDate).isAfter()
? formattedDate.toNow(true)
: null;
},
progress() {
const { gameId, progresses } = this;

View file

@ -1,10 +1,10 @@
<template lang="html">
<div v-if="game.websites">
<dl class="row mb-0" v-for="link in game.websites" :key="link.id">
<dl v-for="link in game.websites" :key="link.id">
<!-- TODO: research which links can be leveraged to get API data,
e.g. wikipedia article, wikia, etc -->
<dt class="col-sm-3">{{ linkTypes[link.category]}}</dt>
<dd class="col-sm-9"><a :href="link.url" target="_blank">{{ link.url }}</a></dd>
<dt class="w-100">{{ linkTypes[link.category]}}</dt>
<dd class="text-truncate d-block"><a :href="link.url" target="_blank">{{ link.url }}</a></dd>
</dl>
</div>
</template>

View file

@ -1,5 +1,4 @@
// TODO: dissolve
// import moment from 'moment';
import { mapState, mapGetters } from 'vuex';
export default {

View file

@ -1,6 +1,6 @@
<template lang="html">
<div
:class="['list mr-3', viewClass, { dragging, 'single': singleBoard }]"
:class="['list mr-3', viewClass, { dragging, 'unique': singleList }]"
:id="listIndex"
>
<b-card
@ -156,8 +156,8 @@ export default {
return this.list.games.length === 0;
},
singleBoard() {
return this.list.games.length === 1;
singleList() {
return this.board.lists.length === 1;
},
view() {
@ -246,9 +246,10 @@ export default {
width: 300px;
border-radius: 3px;
&.single {
&.unique {
@media(max-width: 780px) {
width: calc(100vw - 140px);
// background: #ccf;
}
}

View file

@ -51,5 +51,6 @@ h5 {
.actions {
margin-left: auto;
align-self: flex-start;
}
</style>

View file

@ -22,6 +22,15 @@
<icon name="tag" />
</b-button>
<b-button
variant="link"
:title="$t('navMenu.notes')"
:to="{ name: 'notes' }"
v-b-tooltip.hover.right
>
<icon name="note" />
</b-button>
<b-button
variant="link"
:title="$t('navMenu.wallpapers')"
@ -67,6 +76,15 @@
<icon :name="nightMode ? 'moon' : 'sun'" />
</b-button>
<!-- <b-button
:title="$t('navMenu.upgrade')"
:to="{ name: 'upgrade' }"
variant="link"
v-b-tooltip.hover.right
>
<icon name="star-fill" />
</b-button> -->
<router-link
:title="$t('settings.account')"
class="mb-2 mt-3 d-block"

View file

@ -26,6 +26,8 @@
"language": "Language",
"releases": "Releases",
"about": "About",
"upgrade": "Upgrade",
"notes": "Notes",
"account": "Account",
"theme": "Switch theme"
},
@ -238,5 +240,13 @@
"name": "Alphabetically",
"generation": "Generation / Date relased",
"type": "Type"
},
"upgrade": {
"title": "Upgrade to Gamebrary Pro",
"subtitle": "..."
},
"notes": {
"title": "Notes",
"subtitle": "Notes!"
}
}

View file

@ -56,7 +56,6 @@
<script>
import VueMarkdown from 'vue-markdown';
import moment from 'moment';
import { mapGetters } from 'vuex';
export default {
@ -84,10 +83,6 @@ export default {
},
methods: {
formatDate(date) {
return moment(date).format('LL');
},
async load() {
this.readme = await this.$store.dispatch('LOAD_GITHUB_README');
this.repo = await this.$store.dispatch('LOAD_GITHUB_REPOSITORY');

View file

@ -68,7 +68,6 @@
<script>
import SignOut from '@/components/Settings/SignOut';
import DeleteAccount from '@/components/Settings/DeleteAccount';
import moment from 'moment';
import { mapState, mapGetters } from 'vuex';
export default {
@ -84,7 +83,7 @@ export default {
methods: {
formatDate(date) {
return moment(date).format('LL');
return new Intl.DateTimeFormat().format(new Date(date));
},
},
};

140
src/pages/Notes.vue Normal file
View file

@ -0,0 +1,140 @@
<template lang="html">
<div>
<b-jumbotron
:header="$t('notes.title')"
:lead="$t('notes.subtitle')"
:bg-variant="nightMode ? 'dark' : ''"
:text-variant="nightMode ? 'white' : ''"
:border-variant="nightMode ? 'dark' : ''"
header-level="5"
fluid
/>
<b-container>
<!-- TODO: add skeleton -->
<!-- TODO: finish search, include game title? -->
<!-- <b-row class="mb-3">
<b-form-input
type="search"
placeholder="Search notes"
v-model="search"
/>
</b-row> -->
<b-row>
<template v-if="loaded && games && notes">
<b-card
v-for="(note, gameId) in notes"
:key="gameId"
class="mb-3 w-100 note"
:img-src="getCoverUrl(gameId)"
:img-alt="games[gameId].name"
img-left
>
<div v-if="games[gameId]">
<h5>{{ games[gameId] && games[gameId].name ? games[gameId].name : '' }}</h5>
<b-alert show variant="warning" class="mt-2">
<vue-markdown :source="note.text ? note.text : note" />
</b-alert>
</div>
<div v-else>
...
</div>
</b-card>
</template>
<b-card
v-else
v-for="n in 3"
:key="n"
no-body
img-left
class="w-100 mb-3"
>
<b-skeleton-img
card-img="left"
width="180px"
height="240px"
/>
<b-card-body>
<b-skeleton />
<b-skeleton />
</b-card-body>
</b-card>
</b-row>
</b-container>
</div>
</template>
<script>
import VueMarkdown from 'vue-markdown';
import { mapState, mapGetters } from 'vuex';
export default {
components: {
VueMarkdown,
},
data() {
return {
loaded: false,
search: '',
};
},
computed: {
...mapState(['notes', 'games']),
...mapGetters(['nightMode']),
},
mounted() {
this.loadGames();
},
methods: {
async loadGames() {
const gamesList = Object.keys(this.notes).length
? Object.keys(this.notes).toString()
: null;
if (!gamesList) return;
// TODO: get list of games that aren't currently cached
await this.$store.dispatch('LOAD_BOARD_GAMES', gamesList)
.catch(() => {
this.$bvToast.toast('Error loading games', { title: 'Error', variant: 'error' });
});
this.loaded = true;
},
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`
: '/static/no-image.jpg';
},
},
};
</script>
<style lang="scss" rel="stylesheet/scss" scoped>
.note {
img {
align-self: baseline;
}
}
</style>
<style lang="scss" rel="stylesheet/scss">
img {
max-width: 100%;
}
</style>

View file

@ -10,53 +10,30 @@
class="position-sticky"
/>
<b-container fluid>
<b-form-row>
<b-col cols="2" md="3">
<b-list-group>
<b-list-group-item
button
class="d-flex justify-content-center justify-content-md-between
align-items-center p-2"
v-for="release in releases"
:key="release.id"
:variant="nightMode ? 'dark' : null"
:active="selectedRelease && release.id === selectedRelease.id"
@click="selectedRelease = release"
>
<h6 class="m-0">
<b-badge>{{ release.tag_name }}</b-badge>
<span class="d-none d-md-inline">{{ release.name }}</span>
</h6>
</b-list-group-item>
</b-list-group>
</b-col>
<b-container>
<b-card
v-for="release in releases"
:key="release.id"
:bg-variant="nightMode ? 'dark' : null"
:text-variant="nightMode ? 'white' : null"
hide-footer
class="mb-3"
>
<template v-slot:header>
<h6 class="mb-0">
<b-badge>{{ release.tag_name }}</b-badge>
{{ release.name }}
</h6>
</template>
<b-col cols="10" md="9">
<b-card
:bg-variant="nightMode ? 'dark' : null"
:text-variant="nightMode ? 'white' : null"
v-if="selectedRelease"
hide-footer
class="mb-3"
>
<template v-slot:header>
<h6 class="mb-0">
<b-badge>{{ selectedRelease.tag_name }}</b-badge>
{{ selectedRelease.name }}
</h6>
</template>
<b-card-text>
<small class="text-muted">
{{ $t('releases.published') }} {{ formatDate(release.published_at) }}
</small>
<small class="text-muted">
{{ $t('releases.published') }} {{ formatDate(selectedRelease.published_at) }}
</small>
<b-card-text>
<vue-markdown :source="selectedRelease.body" class="w-100 releases" />
</b-card-text>
</b-card>
</b-col>
</b-form-row>
<vue-markdown :source="release.body" class="w-100 releases" />
</b-card-text>
</b-card>
</b-container>
</div>
</template>
@ -64,19 +41,12 @@
<script>
import { mapState, mapGetters } from 'vuex';
import VueMarkdown from 'vue-markdown';
import moment from 'moment';
export default {
components: {
VueMarkdown,
},
data() {
return {
selectedRelease: null,
};
},
computed: {
...mapState(['releases', 'notification', 'settings']),
...mapGetters(['nightMode']),
@ -85,8 +55,6 @@ export default {
mounted() {
const [latestRelease] = this.releases;
this.selectedRelease = latestRelease;
if (this.notification && latestRelease && latestRelease.tag_name) {
this.$store.commit('UPDATE_SETTING', { key: 'release', value: latestRelease.tag_name });
@ -104,7 +72,7 @@ export default {
methods: {
formatDate(date) {
return moment(date).format('LL');
return new Intl.DateTimeFormat().format(new Date(date));
},
},
};

255
src/pages/Upgrade.vue Normal file
View file

@ -0,0 +1,255 @@
<template lang="html">
<div>
<b-jumbotron
:header="$t('upgrade.title')"
:lead="$t('upgrade.subtitle')"
:bg-variant="nightMode ? 'dark' : ''"
:text-variant="nightMode ? 'white' : ''"
:border-variant="nightMode ? 'dark' : ''"
header-level="5"
fluid
/>
<b-container>
<table class="table table-striped text-center">
<thead>
<tr>
<th scope="col"></th>
<th scope="col">Standard</th>
<th scope="col">Pro</th>
</tr>
</thead>
<tbody>
<tr>
<td class="text-right">Boards</td>
<td>3</td>
<td>Unlimited</td>
</tr>
<tr>
<td class="text-right">Tags</td>
<td>10</td>
<td>Unlimited</td>
</tr>
<tr>
<td class="text-right">Filters</td>
<td>
<icon name="x" />
</td>
<td>
Board filters
Filter by Tags, Progress, Notes
</td>
</tr>
<tr>
<td class="text-right">Search</td>
<td>
<icon name="x" />
</td>
<td>
Search notes
</td>
</tr>
<tr>
<td class="text-right">Deal alerts</td>
<td>
In app only
</td>
<td>
In app + via email
</td>
</tr>
<tr>
<td class="text-right">Notes</td>
<td>
Basic
</td>
<td>
Advanced:
Notes dashboard, search, rich text, upload images, etc...
</td>
</tr>
<tr>
<td class="text-right">Custom boards</td>
<td><icon name="x" /></td>
<td>Custom wallpaper</td>
</tr>
<tr>
<td class="text-right">Public profile</td>
<td><icon name="x" /></td>
<td>
<icon name="check" />
Claim your profile
</td>
</tr>
<tr>
<td class="text-right">List sorting</td>
<td>Manual</td>
<td>Automatic</td>
</tr>
<tr>
<td class="text-right">Public profile</td>
<td><icon name="x" /></td>
<td>
<icon name="check" />
Claim your profile
</td>
</tr>
<tr>
<td class="text-right">Public profile</td>
<td>
Basic
</td>
<td>
<icon name="check" />
Advanced:
Claim your profile
custom url
</td>
</tr>
<tr>
<td class="text-right">STEAM INTEGRATION</td>
<td>
x
</td>
<td>
<icon name="check" />
</td>
</tr>
<tr>
<td class="text-right">Game Watch (add game to db
, cron querys db and emails results, logs data)</td>
<td>
x
</td>
<td>
<icon name="check" />
</td>
</tr>
<tr>
<td class="text-right">CSV Import / Export</td>
<td>
x
</td>
<td>
<icon name="check" />
</td>
</tr>
<tr>
<td class="text-right">Track game progress</td>
<td><icon name="x" /></td>
<td><icon name="check" /></td>
</tr>
<tr>
<td class="text-right"></td>
<td>
Free
</td>
<td>
$4/Mo or $40/yr (save 2 months)
</td>
</tr>
</tbody>
</table>
<b-button variant="success" size="lg">
Upgrade
</b-button>
<div class="text-center mt-4">
<h2>Title here</h2>
<p class="lead">Check out the perks you'll get.</p>
<b-row class="text-center">
<b-col cols="4">
<b-card
title="Feature"
img-src="https://picsum.photos/600/200/?image=1"
img-alt="Image"
img-top
tag="article"
style="max-width: 20rem;"
class="mb-2"
>
<b-card-text>
Some quick example text to build on the card title
and make up the bulk of the card's content.
</b-card-text>
</b-card>
</b-col>
<b-col cols="4">
<b-card
title="Feature"
img-src="https://picsum.photos/600/200/?image=1"
img-alt="Image"
img-top
tag="article"
style="max-width: 20rem;"
class="mb-2"
>
<b-card-text>
Some quick example text to build on the card title
and make up the bulk of the card's content.
</b-card-text>
</b-card>
</b-col>
<b-col cols="4">
<b-card
title="Feature"
img-src="https://picsum.photos/600/200/?image=1"
img-alt="Image"
img-top
tag="article"
style="max-width: 20rem;"
class="mb-2"
>
<b-card-text>
Some quick example text to build on the card
itle and make up the bulk of the card's content.
</b-card-text>
</b-card>
</b-col>
</b-row>
</div>
</b-container>
</div>
</template>
<script>
import { mapGetters } from 'vuex';
export default {
computed: {
...mapGetters(['nightMode']),
},
};
</script>
<style lang="scss" rel="stylesheet/scss" scoped>
</style>

View file

@ -14,86 +14,54 @@
<b-container>
<!-- TODO: convert to form -->
<!-- TODO: translate "browse" -->
<!-- TODO: display file names -->
<h5>{{ $t('wallpapers.form.label') }}</h5>
<!-- TODO: add skeleton -->
<!-- TODO: add progress bar -->
<b-row class="mb-3">
<b-col cols="12" lg="6" class="mb-4">
<b-form-group
:description="$t('wallpapers.form.helperText')"
:label="$t('wallpapers.form.label')"
>
<b-form-file
v-model="file"
accept="image/*"
:browse-text="$t('wallpapers.form.upload')"
:placeholder="$t('wallpapers.form.placeholder')"
@input="uploadWallpaper"
/>
</b-form-group>
<b-alert v-if="isDuplicate && !saving" show variant="warning">
<b-alert v-if="isDuplicate && !saving && file && file.name" show variant="warning">
{{ $t('wallpapers.form.duplicateMessage', { fileName: file.name }) }}
</b-alert>
<b-button
@click="uploadWallpaper"
variant="primary"
:disabled="!Boolean(file) || saving || isDuplicate"
>
<b-spinner small v-if="saving" />
<span v-else>{{ $t('wallpapers.form.upload') }}</span>
</b-button>
</b-col>
</b-row>
<h5>{{ $t('wallpapers.list.title') }}</h5>
<b-row
<b-progress value="59" max="72" variant="success" class="mb-3" />
<b-card
v-if="wallpapers.length"
v-for="wallpaper in wallpapers"
:key="wallpaper.name"
no-gutters
class="mb-4 border-bottom pb-4"
:img-src="wallpaper.url"
:img-alt="wallpaper.name"
img-left
img-width="180"
class="mb-3"
>
<b-col cols="6" lg="3" class="pr-3">
<b-img
:src="wallpaper.url"
thumbnail
class="rounded-0"
/>
</b-col>
<b-col cols="6">
<p>{{ wallpaper.name }}</p>
<b-button
variant="danger"
size="sm"
@click="confirmDeleteWallpaper(wallpaper)"
>
<icon name="trash" white />
</b-button>
</b-col>
</b-row>
<!-- <b-form-row v-if="wallpapers.length">
<b-col cols="12">
</b-col>
<b-col
v-for="wallpaper in wallpapers"
:key="wallpaper.name"
cols="6"
sm="4"
lg="3"
<p>{{ wallpaper.name }}</p>
{{ bytesToSize(wallpaper.metadata.size) }}
<b-button
variant="danger"
size="sm"
@click="confirmDeleteWallpaper(wallpaper)"
>
<b-card
class="mb-2"
header-class="py-0 px-2"
body-class="d-flex p-0 text-center justify-content-center align-items-center"
header-tag="small"
>
</b-card>
</b-col>
</b-form-row> -->
<icon name="trash" white />
</b-button>
</b-card>
<b-alert show v-else>You don't have any wallpapers.</b-alert>
</b-container>
@ -129,20 +97,38 @@ export default {
},
methods: {
async uploadWallpaper() {
const { file } = this;
bytesToSize(bytes) {
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
if (bytes === 0) return '0 Byte';
const i = parseInt(Math.floor(Math.log(bytes) / Math.log(1024)), 0);
return `${Math.round(bytes / (1024 ** i), 2)} ${sizes[i]}`;
},
uploadWallpaper() {
if (this.isDuplicate) {
return this.$bvToast.toast('File already exists', { title: '!', variant: 'warning' });
}
if (!this.file) {
return false;
}
this.saving = true;
await this.$store.dispatch('UPLOAD_WALLPAPER', file)
return this.$store.dispatch('UPLOAD_WALLPAPER', this.file)
.then(() => {
this.$bvToast.toast('File uploaded', { title: 'Success', variant: 'success' });
this.file = null;
this.saving = false;
this.$bus.$emit('WALLPAPER_UPLOADED');
})
.catch(() => {
this.saving = false;
this.$bvToast.toast('There was an error uploading wallpaper', { title: 'Error', variant: 'danger' });
});
this.file = null;
this.saving = false;
this.$bvToast.toast(file.name, { title: 'File uploaded', variant: 'success' });
this.$bus.$emit('WALLPAPER_UPLOADED');
},
confirmDeleteWallpaper(file) {

View file

@ -5,10 +5,12 @@ import About from '@/pages/About';
import Languages from '@/pages/Languages';
import Wallpapers from '@/pages/Wallpapers';
import Tags from '@/pages/Tags';
import Notes from '@/pages/Notes';
import Account from '@/pages/Account';
import Releases from '@/pages/Releases';
import Auth from '@/pages/Auth';
import Dashboard from '@/pages/Dashboard';
import Upgrade from '@/pages/Upgrade';
import NotFound from '@/pages/NotFound';
Vue.use(Router);
@ -23,6 +25,14 @@ export default new Router({
title: 'Dashboard',
},
},
{
name: 'upgrade',
path: '/upgrade',
component: Upgrade,
meta: {
title: 'Upgrade',
},
},
{
name: 'boards',
path: '/boards',
@ -55,6 +65,14 @@ export default new Router({
title: 'Tags',
},
},
{
name: 'notes',
path: '/notes',
component: Notes,
meta: {
title: 'Notes',
},
},
{
name: 'language',
path: '/language',

View file

@ -153,6 +153,7 @@ export default {
});
// TODO: refactor? there's gotta be a better way to do this
// TODO: for real, refactor this crap, use promise.all or something better
const fetchedUrls = [];
wallpapers.forEach(({ fullPath }, index) => {
@ -165,8 +166,22 @@ export default {
wallpapers[index].url = url;
if (fetchedUrls.length === wallpapers.length) {
commit('SET_WALLPAPERS', wallpapers);
resolve();
const fetchedMetadatas = [];
wallpapers.forEach((wallpaper, i) => {
const forestRef = firebase.storage().ref(wallpaper.fullPath);
forestRef.getMetadata().then((metadata) => {
fetchedMetadatas.push(metadata);
wallpapers[i].metadata = metadata;
if (fetchedMetadatas.length === wallpapers.length) {
commit('SET_WALLPAPERS', wallpapers);
resolve();
}
});
});
}
})
.catch(reject);
@ -275,6 +290,7 @@ export default {
.get()
.then((doc) => {
commit('SET_TWITCH_TOKEN', doc.data());
resolve();
})
.catch(reject);
});

124
yarn.lock
View file

@ -92,10 +92,10 @@
resolved "https://registry.yarnpkg.com/@firebase/analytics-types/-/analytics-types-0.4.0.tgz#d6716f9fa36a6e340bc0ecfe68af325aa6f60508"
integrity sha512-Jj2xW+8+8XPfWGkv9HPv/uR+Qrmq37NPYT352wf7MvE9LrstpLVmFg3LqG6MCRr5miLAom5sen2gZ+iOhVDeRA==
"@firebase/analytics@0.5.0":
version "0.5.0"
resolved "https://registry.yarnpkg.com/@firebase/analytics/-/analytics-0.5.0.tgz#587292ec9a24410ad795a65c07fb1ea238ccef95"
integrity sha512-WyQ8BT6JSoXpg4q7SV9Yg5EPXbGbG8FkkXAIhV/AnslCglhpxegO1FU33qbuT4Grzc525hZJA97oqtQS8tm4Wg==
"@firebase/analytics@0.6.0":
version "0.6.0"
resolved "https://registry.yarnpkg.com/@firebase/analytics/-/analytics-0.6.0.tgz#49f508d3f9f419f08c503f1171ef5fa1c3ba52eb"
integrity sha512-6qYEOPUVYrMhqvJ46Z5Uf1S4uULd6d7vGpMP5Qz+u8kIWuOQGcPdJKQap+Hla6Rq164or9gC2HRXuYXKlgWfpw==
dependencies:
"@firebase/analytics-types" "0.4.0"
"@firebase/component" "0.1.19"
@ -132,10 +132,10 @@
resolved "https://registry.yarnpkg.com/@firebase/auth-types/-/auth-types-0.10.1.tgz#7815e71c9c6f072034415524b29ca8f1d1770660"
integrity sha512-/+gBHb1O9x/YlG7inXfxff/6X3BPZt4zgBv4kql6HEmdzNQCodIRlEYnI+/da+lN+dha7PjaFH7C7ewMmfV7rw==
"@firebase/auth@0.14.9":
version "0.14.9"
resolved "https://registry.yarnpkg.com/@firebase/auth/-/auth-0.14.9.tgz#481db24d5bd6eded8ac2e5aea6edb9307040229c"
integrity sha512-PxYa2r5qUEdheXTvqROFrMstK8W4uPiP7NVfp+2Bec+AjY5PxZapCx/YFDLkU0D7YBI82H74PtZrzdJZw7TJ4w==
"@firebase/auth@0.15.0":
version "0.15.0"
resolved "https://registry.yarnpkg.com/@firebase/auth/-/auth-0.15.0.tgz#45d6def6d6d9444432c005710df442991828275f"
integrity sha512-IFuzhxS+HtOQl7+SZ/Mhaghy/zTU7CENsJFWbC16tv2wfLZbayKF5jYGdAU3VFLehgC8KjlcIWd10akc3XivfQ==
dependencies:
"@firebase/auth-types" "0.10.1"
@ -167,21 +167,21 @@
faye-websocket "0.11.3"
tslib "^1.11.1"
"@firebase/firestore-types@1.13.0":
version "1.13.0"
resolved "https://registry.yarnpkg.com/@firebase/firestore-types/-/firestore-types-1.13.0.tgz#4ab9c40e1e66e8193a929460d64507acd07d9230"
integrity sha512-QF5CAuYOHE6Zbsn1uEg6wkl836iP+i6C0C/Zs3kF60eebxZvTWp8JSZk19Ar+jj4w+ye8/7H5olu5CqDNjWpEA==
"@firebase/firestore-types@1.14.0":
version "1.14.0"
resolved "https://registry.yarnpkg.com/@firebase/firestore-types/-/firestore-types-1.14.0.tgz#4516249d3c181849fd3c856831944dbd5c8c55fc"
integrity sha512-WF8IBwHzZDhwyOgQnmB0pheVrLNP78A8PGxk1nxb/Nrgh1amo4/zYvFMGgSsTeaQK37xMYS/g7eS948te/dJxw==
"@firebase/firestore@1.17.1":
version "1.17.1"
resolved "https://registry.yarnpkg.com/@firebase/firestore/-/firestore-1.17.1.tgz#fba94eef755e48b6aa31a586311e3f0a946aafe5"
integrity sha512-FJlNmFIBr9wrkA5g9JBPJWPmxYIzUhBDfsJGHNTUCxMbwm12pz/1Y6CW56kvZOgl6l+iPNlF911iduUDFzNUqw==
"@firebase/firestore@1.18.0":
version "1.18.0"
resolved "https://registry.yarnpkg.com/@firebase/firestore/-/firestore-1.18.0.tgz#3430e8c60d3e6be1d174b3a258838b1944c93a4d"
integrity sha512-maMq4ltkrwjDRusR2nt0qS4wldHQMp+0IDSfXIjC+SNmjnWY/t/+Skn9U3Po+dB38xpz3i7nsKbs+8utpDnPSw==
dependencies:
"@firebase/component" "0.1.19"
"@firebase/firestore-types" "1.13.0"
"@firebase/firestore-types" "1.14.0"
"@firebase/logger" "0.2.6"
"@firebase/util" "0.3.2"
"@firebase/webchannel-wrapper" "0.3.0"
"@firebase/webchannel-wrapper" "0.4.0"
"@grpc/grpc-js" "^1.0.0"
"@grpc/proto-loader" "^0.5.0"
node-fetch "2.6.1"
@ -192,15 +192,15 @@
resolved "https://registry.yarnpkg.com/@firebase/functions-types/-/functions-types-0.3.17.tgz#348bf5528b238eeeeeae1d52e8ca547b21d33a94"
integrity sha512-DGR4i3VI55KnYk4IxrIw7+VG7Q3gA65azHnZxo98Il8IvYLr2UTBlSh72dTLlDf25NW51HqvJgYJDKvSaAeyHQ==
"@firebase/functions@0.4.51":
version "0.4.51"
resolved "https://registry.yarnpkg.com/@firebase/functions/-/functions-0.4.51.tgz#97be571cfe3b9ee3bf289b9dc5194e3ae49a4819"
integrity sha512-PPx8eZcr4eoU9BITOUGUVurs4WZu8Thj3uCWx766dU3mV1W/7kRgtiptmW0XJUB18FZ1PT3+Hadd6V6vjtLgYw==
"@firebase/functions@0.5.1":
version "0.5.1"
resolved "https://registry.yarnpkg.com/@firebase/functions/-/functions-0.5.1.tgz#fa0568bdcdf7dfa7e5f4f66c1e06e376dc7e25b6"
integrity sha512-yyjPZXXvzFPjkGRSqFVS5Hc2Y7Y48GyyMH+M3i7hLGe69r/59w6wzgXKqTiSYmyE1pxfjxU4a1YqBDHNkQkrYQ==
dependencies:
"@firebase/component" "0.1.19"
"@firebase/functions-types" "0.3.17"
"@firebase/messaging-types" "0.5.0"
isomorphic-fetch "2.2.1"
node-fetch "2.6.1"
tslib "^1.11.1"
"@firebase/installations-types@0.3.4":
@ -246,10 +246,10 @@
resolved "https://registry.yarnpkg.com/@firebase/performance-types/-/performance-types-0.0.13.tgz#58ce5453f57e34b18186f74ef11550dfc558ede6"
integrity sha512-6fZfIGjQpwo9S5OzMpPyqgYAUZcFzZxHFqOyNtorDIgNXq33nlldTL/vtaUZA8iT9TT5cJlCrF/jthKU7X21EA==
"@firebase/performance@0.4.1":
version "0.4.1"
resolved "https://registry.yarnpkg.com/@firebase/performance/-/performance-0.4.1.tgz#4e78f406ef2bc0eec2ce67cdfc57a53a55c31476"
integrity sha512-eAqS3/456xnUwuTg4w58x2fYbvTtQpgt67lpBUX3DuhOqwiM8+JELRte52nDgum2lTaTZWiu5de9mPuAYx2WDg==
"@firebase/performance@0.4.2":
version "0.4.2"
resolved "https://registry.yarnpkg.com/@firebase/performance/-/performance-0.4.2.tgz#d5f134674b429d095ce0edfb50fcb4ab279c3cbe"
integrity sha512-irHTCVWJ/sxJo0QHg+yQifBeVu8ZJPihiTqYzBUz/0AGc51YSt49FZwqSfknvCN2+OfHaazz/ARVBn87g7Ex8g==
dependencies:
"@firebase/component" "0.1.19"
"@firebase/installations" "0.4.17"
@ -306,10 +306,10 @@
dependencies:
tslib "^1.11.1"
"@firebase/webchannel-wrapper@0.3.0":
version "0.3.0"
resolved "https://registry.yarnpkg.com/@firebase/webchannel-wrapper/-/webchannel-wrapper-0.3.0.tgz#d1689566b94c25423d1fb2cb031c5c2ea4c9f939"
integrity sha512-VniCGPIgSGNEgOkh5phb3iKmSGIzcwrccy3IomMFRWPCMiCk2y98UQNJEoDs1yIHtZMstVjYWKYxnunIGzC5UQ==
"@firebase/webchannel-wrapper@0.4.0":
version "0.4.0"
resolved "https://registry.yarnpkg.com/@firebase/webchannel-wrapper/-/webchannel-wrapper-0.4.0.tgz#becce788818d3f47f0ac1a74c3c061ac1dcf4f6d"
integrity sha512-8cUA/mg0S+BxIZ72TdZRsXKBP5n5uRcE3k29TZhZw6oIiHBt9JA7CTb/4pE1uKtE/q5NeTY2tBDcagoZ+1zjXQ==
"@fullhuman/postcss-purgecss@^2.1.2":
version "2.3.0"
@ -3834,13 +3834,6 @@ encodeurl@~1.0.2:
resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59"
integrity sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=
encoding@^0.1.11:
version "0.1.13"
resolved "https://registry.yarnpkg.com/encoding/-/encoding-0.1.13.tgz#56574afdd791f54a8e9b2785c0582a2d26210fa9"
integrity sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==
dependencies:
iconv-lite "^0.6.2"
end-of-stream@^1.0.0, end-of-stream@^1.1.0, end-of-stream@^1.4.1:
version "1.4.4"
resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0"
@ -4719,7 +4712,7 @@ find-up@^4.1.0:
locate-path "^5.0.0"
path-exists "^4.0.0"
firebase-admin@^9.1.1:
firebase-admin@^9.2.0:
version "9.2.0"
resolved "https://registry.yarnpkg.com/firebase-admin/-/firebase-admin-9.2.0.tgz#df5176e2d0c5711df6dbf7012320492a703538ea"
integrity sha512-LhnMYl71B4gP1FlTLfwaYlOWhBCAcNF+byb2CPTfaW/T4hkp4qlXOgo2bws/zbAv5X9GTFqGir3KexMslVGsIA==
@ -4744,21 +4737,21 @@ firebase-functions@^3.11.0:
express "^4.17.1"
lodash "^4.17.14"
firebase@^7.19.0:
version "7.21.1"
resolved "https://registry.yarnpkg.com/firebase/-/firebase-7.21.1.tgz#45c92d6c53136a07d637e9da227726460e86e746"
integrity sha512-ogqWUXIP2/1BTee112QJiAjgch/Ig7pzlAw2mfWOhl9E0IUX46OKv0hypLX62MBgaAKwPHfICIwsWOCxlQ9dZQ==
firebase@^7.23.0:
version "7.24.0"
resolved "https://registry.yarnpkg.com/firebase/-/firebase-7.24.0.tgz#dab53b9c0f1c9538d2d6f4f51769897b0b6d60d8"
integrity sha512-j6jIyGFFBlwWAmrlUg9HyQ/x+YpsPkc/TTkbTyeLwwAJrpAmmEHNPT6O9xtAnMV4g7d3RqLL/u9//aZlbY4rQA==
dependencies:
"@firebase/analytics" "0.5.0"
"@firebase/analytics" "0.6.0"
"@firebase/app" "0.6.11"
"@firebase/app-types" "0.6.1"
"@firebase/auth" "0.14.9"
"@firebase/auth" "0.15.0"
"@firebase/database" "0.6.13"
"@firebase/firestore" "1.17.1"
"@firebase/functions" "0.4.51"
"@firebase/firestore" "1.18.0"
"@firebase/functions" "0.5.1"
"@firebase/installations" "0.4.17"
"@firebase/messaging" "0.7.1"
"@firebase/performance" "0.4.1"
"@firebase/performance" "0.4.2"
"@firebase/polyfill" "0.3.36"
"@firebase/remote-config" "0.1.28"
"@firebase/storage" "0.3.43"
@ -5615,13 +5608,6 @@ iconv-lite@0.4.24, iconv-lite@^0.4.17:
dependencies:
safer-buffer ">= 2.1.2 < 3"
iconv-lite@^0.6.2:
version "0.6.2"
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.6.2.tgz#ce13d1875b0c3a674bd6a04b7f76b01b1b6ded01"
integrity sha512-2y91h5OpQlolefMPmUlivelittSWy0rP+oYVpn6A7GwVHNE8AWzoYOBNmlwks3LobaJxgHCYZAnyNo2GgpNRNQ==
dependencies:
safer-buffer ">= 2.1.2 < 3.0.0"
icss-replace-symbols@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/icss-replace-symbols/-/icss-replace-symbols-1.1.0.tgz#06ea6f83679a7749e386cfe1fe812ae5db223ded"
@ -6243,14 +6229,6 @@ isobject@^3.0.0, isobject@^3.0.1:
resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df"
integrity sha1-TkMekrEalzFjaqH5yNHMvP2reN8=
isomorphic-fetch@2.2.1:
version "2.2.1"
resolved "https://registry.yarnpkg.com/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz#611ae1acf14f5e81f729507472819fe9733558a9"
integrity sha1-YRrhrPFPXoH3KVB0coGf6XM1WKk=
dependencies:
node-fetch "^1.0.1"
whatwg-fetch ">=0.10.0"
isstream@~0.1.2:
version "0.1.2"
resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a"
@ -7412,11 +7390,6 @@ mocha@^3.2.0:
mkdirp "0.5.1"
supports-color "3.1.2"
moment@^2.22.1:
version "2.29.0"
resolved "https://registry.yarnpkg.com/moment/-/moment-2.29.0.tgz#fcbef955844d91deb55438613ddcec56e86a3425"
integrity sha512-z6IJ5HXYiuxvFTI6eiQ9dm77uE0gyy1yXNApVHqTcnIKfY9tIwEjlzsZ6u1LQXvVgKeTnv9Xm7NDvJ7lso3MtA==
move-concurrently@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/move-concurrently/-/move-concurrently-1.0.1.tgz#be2c005fda32e0b29af1f05d7c4b33214c701f92"
@ -7554,14 +7527,6 @@ node-fetch@2.6.1, node-fetch@^2.2.0, node-fetch@^2.3.0, node-fetch@^2.6.0, node-
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.1.tgz#045bd323631f76ed2e2b55573394416b639a0052"
integrity sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==
node-fetch@^1.0.1:
version "1.7.3"
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-1.7.3.tgz#980f6f72d85211a5347c6b2bc18c5b84c3eb47ef"
integrity sha512-NhZ4CsKx7cYm2vSrBAr2PvFOe6sWDf0UYLRqA6svUYg7+/TSfVAu49jYC4BvQ4Sms9SZgdqGBgroqfDhJdTyKQ==
dependencies:
encoding "^0.1.11"
is-stream "^1.0.1"
node-forge@^0.10.0:
version "0.10.0"
resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.10.0.tgz#32dea2afb3e9926f02ee5ce8794902691a676bf3"
@ -9779,7 +9744,7 @@ safe-regex@^1.1.0:
dependencies:
ret "~0.1.10"
"safer-buffer@>= 2.1.2 < 3", "safer-buffer@>= 2.1.2 < 3.0.0", safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0:
"safer-buffer@>= 2.1.2 < 3", safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0:
version "2.1.2"
resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
@ -11594,11 +11559,6 @@ whatwg-fetch@2.0.4:
resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-2.0.4.tgz#dde6a5df315f9d39991aa17621853d720b85566f"
integrity sha512-dcQ1GWpOD/eEQ97k66aiEVpNnapVj90/+R+SXTPYGHpYBBypfKJEQjLrvMZ7YXbKm21gXd4NcuxUTjiv1YtLng==
whatwg-fetch@>=0.10.0:
version "3.4.1"
resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-3.4.1.tgz#e5f871572d6879663fa5674c8f833f15a8425ab3"
integrity sha512-sofZVzE1wKwO+EYPbWfiwzaKovWiZXf4coEzjGP9b2GBVgQRLQUZ2QcuPpQExGDAW5GItpEm6Tl4OU5mywnAoQ==
whet.extend@~0.9.9:
version "0.9.9"
resolved "https://registry.yarnpkg.com/whet.extend/-/whet.extend-0.9.9.tgz#f877d5bf648c97e5aa542fadc16d6a259b9c11a1"