Refactor code and update settings

This commit is contained in:
Gamebrary 2024-02-02 23:22:06 -07:00
parent 98c5603ef4
commit 5524e31407
10 changed files with 334 additions and 310 deletions

View file

@ -6,7 +6,7 @@
"license": "MIT",
"private": true,
"scripts": {
"dev": "vue-cli-service serve --port 10293 --open --host localhost",
"dev": "vue-cli-service serve --port 4000 --open --host localhost",
"build": "vue-cli-service build",
"test:unit": "vue-cli-service test:unit",
"test:e2e": "vue-cli-service test:e2e",

View file

@ -0,0 +1,107 @@
<template>
<b-dropdown
v-b-tooltip.hover.auto="{ delay: { show: 500, hide: 50 } }"
title="Profile"
v-bind="dockDropdownProps"
class="ml-auto"
:toggle-class="avatarImage ? 'p-0' : null"
no-caret
>
<template #button-content>
<template>
<b-avatar
v-if="avatarImage"
:src="avatarImage"
size="40"
rounded
/>
<i
v-else
class="fa fa-solid fa-user fa-fw"
aria-hidden
/>
</template>
</template>
<b-dropdown-text :class="darkTheme ? 'text-success' : 'text-danger'">
{{ profileTitle }}
</b-dropdown-text>
<b-dropdown-item
:to="{ name: 'profile' }"
>
<i class="fa-regular fa-rectangle-list fa-fw"></i>
<span class="ml-2">Edit profile</span>
</b-dropdown-item>
<b-dropdown-item
v-if="profile"
:to="{ name: 'public.profile', params: { userName: profile.userName } }"
>
<i class="fa-regular fa-plus fa-fw" />
<span class="ml-2">View profile</span>
</b-dropdown-item>
<b-dropdown-item
@click="signOut"
>
<i class="fa-regular fa-rectangle-list fa-fw"></i>
<span class="ml-2">Log out</span>
</b-dropdown-item>
</b-dropdown>
</template>
<script>
import { mapState, mapGetters } from 'vuex';
import { getImageThumbnail } from '@/utils';
export default {
data() {
return {
profile: null,
avatarImage: null,
};
},
computed: {
...mapState(['board', 'user']),
...mapGetters(['dockDropdownProps', 'darkTheme']),
profileTitle() {
return this.profile?.userName
? `@${this.profile.userName}`
: 'Profile';
},
},
mounted() {
if (this.user) this.load();
},
methods: {
async load() {
this.profile = await this.$store.dispatch('LOAD_PROFILE').catch(() => {});
if (this.profile?.avatar) this.loadAvatarImage();
},
async loadAvatarImage() {
const thumbnailRef = getImageThumbnail(this.profile?.avatar);
this.avatarImage = await this.$store.dispatch('LOAD_FIREBASE_IMAGE', thumbnailRef);
},
async signOut() {
await this.$store.dispatch('SIGN_OUT');
this.$bvToast.toast('Logged out');
this.$store.commit('CLEAR_SESSION');
this.$router.replace({ name: 'home' });
},
}
}
</script>
<style scoped>
/* Your CSS code here */
</style>

View file

@ -0,0 +1,123 @@
<template>
<b-dropdown
v-if="user"
v-b-tooltip.hover.auto="{ delay: { show: 500, hide: 50 } }"
title="Preferences"
:variant="darkTheme ? 'black' : 'light'"
no-caret
>
<template #button-content>
<i class="fas fa-cog fa-fw" />
<span v-if="!isVerticalNav" class="d-none d-md-inline">
Settings
</span>
</template>
<b-dropdown-form>
<b-form-checkbox
switch
@change="toggleTheme"
:checked="darkTheme"
>
<span :class="darkTheme ? 'text-light' : null">Dark theme</span>
</b-form-checkbox>
<b-form-checkbox
switch
@change="toggleGameThumbnails"
:checked="showGameThumbnails"
>
<span :class="darkTheme ? 'text-light' : null">
Show game thumbnails
</span>
</b-form-checkbox>
</b-dropdown-form>
<b-dropdown-item
:to="{ name: 'settings' }"
>
<i class="fa-regular fa-rectangle-list fa-fw"></i>
<span class="ml-2">Settings</span>
</b-dropdown-item>
<b-dropdown-item
href="https://github.com/romancm/gamebrary/"
target="_blank"
>
<i class="fa-brands fa-github fa-fw"></i>
GitHub
</b-dropdown-item>
<b-dropdown-item v-b-modal.keyboard-shortcuts>
<i class="fa-solid fa-keyboard fa-fw" />
<span class="ml-2">Keyboard Shortcuts</span>
</b-dropdown-item>
<b-dropdown-item
:to="{ name: 'help' }"
id="help"
>
<i class="fa-regular fa-circle-question fa-fw" aria-hidden="true" />
<span class="ml-2">Help</span>
</b-dropdown-item>
<b-dropdown-item disabled>
<i class="fa-solid fa-language" />
<span class="ml-2">Change language</span>
</b-dropdown-item>
<b-dropdown-item
:to="{ name: 'steam.settings' }"
disabled
>
<i class="fab fa-steam fa-fe" aria-hidden />
<span class="ml-2">Steam</span>
</b-dropdown-item>
</b-dropdown>
</template>
<script>
import { mapState, mapGetters } from 'vuex';
export default {
computed: {
...mapState(['user', 'settings']),
...mapGetters(['darkTheme', 'isVerticalNav', 'showGameThumbnails']),
},
methods: {
async toggleTheme() {
const { settings } = this;
const darkTheme = settings?.darkTheme || false;
const payload = {
...settings,
darkTheme: !darkTheme,
};
await this.$store.dispatch('SAVE_SETTINGS', payload)
.catch(() => {
this.$bvToast.toast('There was an error saving your settings', { variant: 'danger' });
this.saving = false;
});
},
async toggleGameThumbnails() {
const { settings } = this;
const showGameThumbnails = settings?.showGameThumbnails || false;
const payload = {
...settings,
showGameThumbnails: !showGameThumbnails,
};
await this.$store.dispatch('SAVE_SETTINGS', payload)
.catch(() => {
this.$bvToast.toast('There was an error saving your settings', { variant: 'danger' });
this.saving = false;
});
},
},
}
</script>

View file

@ -0,0 +1,58 @@
<template>
<b-dropdown
v-if="user"
v-b-tooltip.hover.auto="{ delay: { show: 500, hide: 50 } }"
title="Tags"
v-bind="dockDropdownProps"
>
<template #button-content>
<i class="fa-solid fa-tags fa-fw" />
<span v-if="!isVerticalNav" class="d-none d-md-inline">
Tags
</span>
</template>
<b-dropdown-text
v-for="({ textColor, bgColor, name }, index) in tags"
:key="name"
block
class="rounded mb-1"
:style="`background-color: ${bgColor};`"
@click="$router.push({ name: 'tag.edit', params: { id: index } })"
>
<span :style="`color: ${textColor}`">{{ name }}</span>
</b-dropdown-text>
<!-- <b-dropdown-item
:to="{ name: 'tags' }"
>
<i class="fa-regular fa-rectangle-list fa-fw"></i>
<span class="ml-2">My tags</span>
</b-dropdown-item> -->
<b-dropdown-item
v-if="user"
:to="{ name: 'tag.create' }"
>
<i class="fa-regular fa-plus fa-fw" />
<span class="ml-2">Add tag</span>
</b-dropdown-item>
</b-dropdown>
</template>
<script>
import { mapState, mapGetters } from 'vuex'
export default {
computed: {
...mapState(['user', 'tags']),
...mapGetters(['isVerticalNav', 'dockDropdownProps', 'darkTheme']),
},
}
</script>
<style scoped>
/* Your component's styles here */
</style>

View file

@ -12,9 +12,10 @@
<img src="logo.png" alt="" height="26" />
</b-button>
<BoardsDockDropdown />
<BoardsDockDropdown v-if="user" />
<b-dropdown
v-if="user"
v-b-tooltip.hover.auto="{ delay: { show: 500, hide: 50 } }"
title="Games"
toggle-class="px-2 py-0"
@ -50,8 +51,7 @@
<b-dropdown-item
:to="{ name: 'games' }"
>
<i class="fa-solid fa-heart fa-fw"></i>
<i class="fa-solid fa-heart fa-fw" />
<span class="ml-2">My games</span>
</b-dropdown-item>
@ -74,47 +74,10 @@
</b-dropdown-item>
</b-dropdown>
<b-dropdown
v-b-tooltip.hover.auto="{ delay: { show: 500, hide: 50 } }"
title="Tags"
v-bind="dockDropdownProps"
>
<template #button-content>
<i class="fa-solid fa-tags fa-fw" />
<span v-if="!isVerticalNav" class="d-none d-md-inline">
Tags
</span>
</template>
<b-dropdown-text
v-for="({ textColor, bgColor, name }, index) in tags"
:key="name"
block
class="rounded mb-1"
:style="`background-color: ${bgColor};`"
@click="$router.push({ name: 'tag.edit', params: { id: index } })"
>
<span :style="`color: ${textColor}`">{{ name }}</span>
</b-dropdown-text>
<!-- <b-dropdown-item
:to="{ name: 'tags' }"
>
<i class="fa-regular fa-rectangle-list fa-fw"></i>
<span class="ml-2">My tags</span>
</b-dropdown-item> -->
<b-dropdown-item
v-if="user"
:to="{ name: 'tag.create' }"
>
<i class="fa-regular fa-plus fa-fw" />
<span class="ml-2">Add tag</span>
</b-dropdown-item>
</b-dropdown>
<TagsDockDropdown v-if="user" />
<b-dropdown
v-if="user"
v-b-tooltip.hover.auto="{ delay: { show: 500, hide: 50 } }"
title="Notes"
v-bind="dockDropdownProps"
@ -123,7 +86,7 @@
<i class="fa fa-sticky-note fa-fw" aria-hidden="true" />
<span v-if="!isVerticalNav" class="d-none d-md-inline">
Wallpapers
Notes
</span>
</template>
@ -137,6 +100,7 @@
</b-dropdown>
<b-dropdown
v-if="user"
v-b-tooltip.hover.auto="{ delay: { show: 500, hide: 50 } }"
title="Wallpapers"
v-bind="dockDropdownProps"
@ -159,167 +123,38 @@
<UploadWallpaperButton v-if="user" />
</b-dropdown>
<b-dropdown
v-if="user"
v-b-tooltip.hover.auto="{ delay: { show: 500, hide: 50 } }"
title="Profile"
:variant="darkTheme ? 'black' : 'light'"
no-caret
>
<template #button-content>
<template>
<b-avatar
rounded
v-if="avatarImage"
:src="avatarImage"
size="22"
/>
<SettingsDockDropdown v-if="user" />
<ProfileDockDropdown v-if="user" />
<i
v-else
class="fa fa-solid fa-user fa-fw"
aria-hidden
/>
<!-- {{ profileTitle }} -->
</template>
</template>
<b-dropdown-item
:to="{ name: 'profile' }"
<div v-else class="ml-auto d-flex align-items">
<b-button
v-bind="dockDropdownProps"
:to="{ name: 'search' }"
v-b-tooltip.hover.auto="{ delay: { show: 500, hide: 50 } }"
title="Search"
>
<i class="fa-regular fa-rectangle-list fa-fw"></i>
<span class="ml-2">Edit profile</span>
</b-dropdown-item>
<i class="fa fa-search fa-fw" aria-hidden="true" />
</b-button>
<b-dropdown-item
:to="{ name: 'public.profile', params: { userName: profile.userName } }"
>
<i class="fa-regular fa-plus fa-fw" />
<span class="ml-2">View profile</span>
</b-dropdown-item>
</b-dropdown>
<b-dropdown
v-b-tooltip.hover.auto="{ delay: { show: 500, hide: 50 } }"
v-bind="dockDropdownProps"
>
<template #button-content>
<i class="fa-solid fa-sliders fa-fw" />
<span v-if="!isVerticalNav" class="d-none d-md-inline">
Settings
</span>
</template>
<!-- <b-dropdown-form>
<b-form-group label="Email" label-for="dropdown-form-email" @submit.stop.prevent>
<b-form-checkbox v-model="checked" name="check-button" switch>
Switch Checkbox <b>(Checked: {{ checked }})</b>
</b-form-checkbox>
<b-form-select v-model="selected" :options="options"></b-form-select>
</b-form-group>
<b-form-group label="Password" label-for="dropdown-form-password">
<b-form-input
id="dropdown-form-password"
type="password"
size="sm"
placeholder="Password"
/>
</b-form-group>
<b-form-checkbox class="mb-3">Remember me</b-form-checkbox>
<b-button variant="primary" size="sm" @click="onClick">Sign In</b-button>
</b-dropdown-form> -->
<b-dropdown-divider />
<b-dropdown-item-button>New around here? Sign up</b-dropdown-item-button>
<b-dropdown-item-button>Forgot Password?</b-dropdown-item-button>
</b-dropdown>
<b-dropdown
v-b-tooltip.hover.auto="{ delay: { show: 500, hide: 50 } }"
title="Preferences"
:variant="darkTheme ? 'black' : 'light'"
no-caret
>
<template #button-content>
<i class="fa-solid fa-sliders fa-fw" />
<span v-if="!isVerticalNav" class="d-none d-md-inline">
Settings
</span>
</template>
<b-dropdown-item
:to="{ name: 'settings' }"
>
<i class="fa-regular fa-rectangle-list fa-fw"></i>
<span class="ml-2">Settings</span>
</b-dropdown-item>
<b-dropdown-item
href="https://github.com/romancm/gamebrary/"
target="_blank"
>
<i class="fa-brands fa-github fa-fw"></i>
GitHub
</b-dropdown-item>
<b-dropdown-item v-b-modal.keyboard-shortcuts>
<i class="fa-solid fa-keyboard fa-fw" />
<span class="ml-2">Keyboard Shortcuts</span>
</b-dropdown-item>
<b-dropdown-item
:to="{ name: 'help' }"
id="help"
>
<i class="fa-regular fa-circle-question fa-fw" aria-hidden="true" />
<span class="ml-2">Help</span>
</b-dropdown-item>
<b-dropdown-item disabled>
<i class="fa-solid fa-language" />
<span class="ml-2">Change language</span>
</b-dropdown-item>
<b-dropdown-item
:to="{ name: 'steam.settings' }"
disabled
>
<i class="fab fa-steam fa-fe" aria-hidden />
<span class="ml-2">Steam</span>
</b-dropdown-item>
</b-dropdown>
<b-button
class="ml-auto"
:to="{ name: 'search' }"
v-b-tooltip.hover.auto="{ delay: { show: 500, hide: 50 } }"
title="Search"
>
<i class="fa fa-search fa-fw" aria-hidden="true" />
</b-button>
<b-button
v-if="!user"
class="ml-auto"
variant="danger"
:to="{ name: 'auth' }"
>
Get started <span class="d-none d-sm-inline"> it's free!</span>
</b-button>
<b-button
variant="danger"
class="ml-2"
:to="{ name: 'auth' }"
>
Get started <span class="d-none d-sm-inline"> it's free!</span>
</b-button>
</div>
</div>
</template>
<script>
import { mapState, mapGetters } from 'vuex';
import { getImageThumbnail, getImageUrl } from '@/utils';
import { getImageUrl } from '@/utils';
import UploadWallpaperButton from '@/components/UploadWallpaperButton';
import BoardsDockDropdown from '@/components/Dock/BoardsDockDropdown';
import ProfileDockDropdown from '@/components/Dock/ProfileDockDropdown';
import SettingsDockDropdown from '@/components/Dock/SettingsDockDropdown';
import TagsDockDropdown from '@/components/Dock/TagsDockDropdown';
export default {
getImageUrl,
@ -327,12 +162,13 @@ export default {
components: {
UploadWallpaperButton,
BoardsDockDropdown,
ProfileDockDropdown,
SettingsDockDropdown,
TagsDockDropdown,
},
data() {
return {
profile: {},
avatarImage: null,
options: [
{ value: null, text: 'Please select an option' },
{ value: 'a', text: 'This is First option' },
@ -345,18 +181,12 @@ export default {
computed: {
...mapState(['board', 'boards', 'settings', 'user', 'games', 'notes', 'tags', 'wallpapers', 'game']),
...mapGetters(['darkTheme', 'navPosition', 'latestRelease', 'isVerticalNav', 'dockDropdownProps']),
...mapGetters(['navPosition', 'latestRelease', 'isVerticalNav', 'dockDropdownProps']),
routeName() {
return this.$route.name;
},
profileTitle() {
return this.profile?.userName
? `@${this.profile.userName}`
: 'Profile';
},
isGamePage() {
return this.$route.name === 'game';
},
@ -367,23 +197,5 @@ export default {
return 'Games';
}
},
mounted() {
if (this.user) this.load();
},
methods: {
async load() {
this.profile = await this.$store.dispatch('LOAD_PROFILE').catch(() => {});
if (this.profile?.avatar) this.loadAvatarImage();
},
async loadAvatarImage() {
const thumbnailRef = getImageThumbnail(this.profile?.avatar);
this.avatarImage = await this.$store.dispatch('LOAD_FIREBASE_IMAGE', thumbnailRef);
},
},
}
</script>

View file

@ -37,15 +37,6 @@ Elevate your gaming experience with PlayStats because your gaming journey is
<div :class="['align-items-center d-flex ml-auto', isVerticalNav ? 'h-100 flex-column' : '']">
<portal-target name="headerActions" multiple />
<b-button
v-if="!user"
class="ml-2 d-none d-sm-inline"
variant="black"
:to="{ name: 'auth' }"
>
Get started
</b-button>
</div>
</header>
</template>

View file

@ -1,7 +1,6 @@
<template lang="html">
<header
class="d-flex justify-content-between align-items-center mb-3"
hasLongText
>
<h2 :class="{ 'text-wrap text-danger': hasLongText }">
{{ title }}
@ -22,7 +21,7 @@ export default {
...mapGetters(['isVerticalNav']),
hasLongText() {
return this.title.length > 30;
return this.title?.length > 30;
},
},
};

View file

@ -9,9 +9,8 @@
<div class="text-center">
<b-avatar
:src="avatarImage"
class="mx-auto rounded my-3"
class="mx-auto my-3"
size="200"
rounded
/>
<br />

View file

@ -81,24 +81,7 @@
<hr />
<b-form-checkbox
switch
@input="toggleTheme"
:checked="darkTheme"
class="mb-3"
>
Dark theme
</b-form-checkbox>
<b-form-checkbox
switch
@input="toggleCoversInMiniBoards"
:checked="coversInMiniBoards"
class="mb-3"
>
Show game covers in mini boards
</b-form-checkbox>
<!-- <h3>Game rating type</h3>
number / stars / minimalist
@ -128,16 +111,6 @@
<delete-account-modal /> -->
<hr />
<b-button
@click="signOut"
>
Log out
</b-button>
<br />
<b-link :to="{ name: 'dev.tools' }">
<i class="fa fa-cog fa-fw" aria-hidden="true" />
<span class="ml-2">Dev tools</span>
@ -149,6 +122,7 @@
<script>
import { mapState, mapGetters } from 'vuex';
import { AGE_RATINGS } from '@/constants';
// TODO: restore and finish delete account modal
// import DeleteAccountModal from '@/components/Settings/DeleteAccountModal';
export default {
@ -160,50 +134,10 @@ export default {
computed: {
...mapState(['user', 'settings']),
...mapGetters(['darkTheme', 'navPosition', 'ageRating', 'coversInMiniBoards']),
...mapGetters(['darkTheme', 'navPosition', 'ageRating']),
},
methods: {
async signOut() {
await this.$store.dispatch('SIGN_OUT');
this.$bvToast.toast('Logged out');
this.$store.commit('CLEAR_SESSION');
this.$router.replace({ name: 'auth' });
},
async toggleTheme() {
const { settings } = this;
const darkTheme = settings?.darkTheme || false;
const payload = {
...settings,
darkTheme: !darkTheme,
};
await this.$store.dispatch('SAVE_SETTINGS', payload)
.catch(() => {
this.$bvToast.toast('There was an error saving your settings', { variant: 'danger' });
this.saving = false;
});
},
async toggleCoversInMiniBoards() {
const { settings } = this;
const coversInMiniBoards = settings?.coversInMiniBoards || false;
const payload = {
...settings,
coversInMiniBoards: !coversInMiniBoards,
};
await this.$store.dispatch('SAVE_SETTINGS', payload)
.catch(() => {
this.$bvToast.toast('There was an error saving your settings', { variant: 'danger' });
this.saving = false;
});
},
async setNavPosition(navPosition) {
try {
await this.$store.dispatch('SAVE_SETTINGS', {

View file

@ -17,9 +17,10 @@ import * as linkify from 'linkifyjs';
import linkifyHtml from 'linkify-html';
export default {
// TODO: use constants for default settings
latestRelease: ({ releases }) => releases?.[0]?.tag_name || 'v1',
darkTheme: ({ settings }) => settings?.darkTheme || false,
coversInMiniBoards: ({ settings }) => settings?.coversInMiniBoards || false,
showGameThumbnails: ({ settings }) => settings?.showGameThumbnails || false,
sortedBoards: ({ boards }) => orderby(boards, 'lastUpdated', 'desc'),
sortedPublicBoards: ({ publicBoards }) => orderby(publicBoards, 'lastUpdated', 'desc'),
isBoardOwner: ({ board, user }) => board?.owner === user?.uid,
@ -31,12 +32,12 @@ export default {
return game?.websites?.map(({ url, category }) => ({ url, ...LINKS_CATEGORIES[category] })) || [];
},
dockDropdownProps: (getters) => {
dockDropdownProps: ({ settings }) => {
return {
variant: getters.darkTheme ? 'black' : 'light',
dropup: getters.navPosition === 'bottom',
dropright: getters.navPosition === 'left',
dropleft: getters.navPosition === 'right',
variant: settings?.darkTheme ? 'dark' : 'light',
dropup: settings?.navPosition === 'bottom',
dropright: settings?.navPosition === 'left',
dropleft: settings?.navPosition === 'right',
noCaret: true,
};
},