file management and board settings

This commit is contained in:
Gamebrary 2020-08-24 21:23:22 -07:00
parent a84be79a9a
commit 3a43d2eafb
13 changed files with 470 additions and 268 deletions

View file

@ -22,6 +22,7 @@
<b-form-input
id="name"
v-model="board.name"
autofocus
required
/>
</b-form-group>
@ -74,6 +75,7 @@
<b-button
variant="primary"
:disabled="saving"
@click="submit"
>
<b-spinner small v-if="saving" />
@ -131,7 +133,7 @@ export default {
name: null,
description: null,
theme: null,
backgroundColor: '#ccc',
wallpaper: null,
platforms: [],
lists: [],
};

View file

@ -6,8 +6,6 @@
<create-board />
</div>
<!-- <pre>{{ platforms }}</pre> -->
<b-overlay :show="loading && !platforms.length" rounded="sm" variant="transparent">
<b-row cols="3" no-gutters>
<b-col v-for="board in boards" :key="board.id">
@ -20,12 +18,6 @@
{{ board.description }}
</b-card-text>
<div v-for="platform in board.platforms" :key="platform">
{{ platforms[platform].name }}
</div>
{{ board.lists.length }} lists
<b-button
variant="danger"
@click="confirmDelete(board.id)"
@ -39,6 +31,10 @@
>
Open board
</b-button>
<!-- <b-button v-b-modal:board-settings>
<b-icon-gear-fill />
</b-button> -->
</b-card>
</b-col>
</b-row>

View file

@ -12,7 +12,7 @@
<b-list-group flush>
<b-list-group-item>
<wallpaper-upload />
<legacy-wallpaper-upload />
</b-list-group-item>
<b-list-group-item>
@ -52,11 +52,11 @@
<script>
import { mapState } from 'vuex';
import themes from '@/themes';
import WallpaperUpload from '@/components/WallpaperUpload';
import LegacyWallpaperUpload from '@/components/LegacyBoard/LegacyWallpaperUpload';
export default {
components: {
WallpaperUpload,
LegacyWallpaperUpload,
},
data() {

View file

@ -14,10 +14,8 @@
<h5>Platforms (Deprecated)</h5>
<platforms-header />
<platforms-list
:platforms="sortedPlatforms"
:platforms="ownedPlatforms"
/>
<platforms-footer />
@ -26,15 +24,12 @@
<script>
import platforms from '@/platforms';
import PlatformsFooter from '@/components/LegacyBoard/LegacyPlatformsFooter';
import PlatformsHeader from '@/components/LegacyBoard/LegacyPlatformsHeader';
import PlatformsList from '@/components/LegacyBoard/LegacyPlatformsList';
import sortby from 'lodash.sortby';
import { mapState } from 'vuex';
export default {
components: {
PlatformsFooter,
PlatformsHeader,
PlatformsList,
},
@ -47,48 +42,13 @@ export default {
computed: {
...mapState(['gameLists', 'settings']),
// TODO: move to getter and replace other instances
hasLists() {
return Object.keys(this.gameLists).length > 0;
},
listView() {
return this.settings && this.settings.platformsView
? this.settings.platformsView
: 'grid';
},
platformsFilterField() {
return this.settings && this.settings.platformsFilterField
? this.settings.platformsFilterField
: null;
},
ownedPlatforms() {
return this.platforms.filter(({ code }) => this.gameLists[code]);
},
platformsSortField() {
return this.settings && this.settings.platformsSortField
? this.settings.platformsSortField
: 'releaseYear';
},
filteredPlatforms() {
return this.platformsFilterField
? this.ownedPlatforms.filter(({ type }) => type === this.platformsFilterField)
: this.ownedPlatforms;
},
sortedPlatforms() {
const sortedPlatforms = this.platformsSortField
? sortby(this.filteredPlatforms, this.platformsSortField)
: this.filteredPlatforms;
return this.platformsSortField === 'releaseYear'
? sortedPlatforms.reverse()
: sortedPlatforms;
},
},
};
</script>

View file

@ -1,116 +0,0 @@
<template lang="html">
<header>
<b-button-toolbar aria-label="Filter and sort platforms">
<strong class="py-2 pr-1">Filter:</strong>
<b-button-group class="mx-1">
<b-button
v-for="filter in filters"
:key="filter"
:variant="filterField === filter ? 'primary' : null"
size="sm"
@click="setFilter(filter)"
>
{{ filter ? $t(`platforms.${filter}`) : $t('platforms.all') }}
</b-button>
</b-button-group>
<strong class="py-2 pr-1 mx-1">Sort:</strong>
<b-button-group>
<b-button
v-for="field in availableSortFields"
:key="field"
:variant="sortField === field ? 'primary' : null"
size="sm"
@click="setSortField(field)"
>
{{ $t(`platforms.${field}`) }}
</b-button>
</b-button-group>
</b-button-toolbar>
</header>
</template>
<script>
import { mapState } from 'vuex';
export default {
data() {
return {
filters: [
null,
'consoles',
'handheld',
'computer',
],
availableSortFields: [
'releaseYear',
'name',
'type',
],
};
},
computed: {
...mapState(['settings']),
filterField() {
return this.settings && this.settings.platformsFilterField
? this.settings.platformsFilterField
: null;
},
sortField() {
return this.settings && this.settings.platformsSortField
? this.settings.platformsSortField
: 'releaseYear';
},
},
methods: {
saveSettings() {
this.$store.dispatch('SAVE_SETTINGS_LEGACY', this.settings)
.then(() => {
this.$bvToast.toast('Settings saved', { title: 'Success', variant: 'success' });
})
.catch(() => {
this.$bvToast.toast('There was an error saving your settings', { title: 'Error', variant: 'danger' });
});
},
setFilter(value) {
this.$store.commit('UPDATE_SETTING', { key: 'platformsFilterField', value });
this.saveSettings();
},
setSortField(value) {
this.$store.commit('UPDATE_SETTING', { key: 'platformsSortField', value });
this.saveSettings();
},
},
};
</script>
<style lang="scss" rel="stylesheet/scss" scoped>
// @import "~styles/styles";
header {
// display: inline-grid;
// align-items: center;
display: flex;
align-items: center;
margin-bottom: 1rem;
grid-gap: 1rem;
}
.filter-active {
color: var(--success-background);
}
section {
padding-top: 1rem;
}
h3 {
margin-bottom: .5rem;
}
</style>

View file

@ -81,7 +81,7 @@ export default {
wallpapers: this.wallpapers,
};
this.$store.dispatch('SAVE_SETTINGS_LEGACY', settings)
this.$store.dispatch('SAVE_SETTINGS', settings)
.then(() => {
this.$bvToast.toast('Settings saved', { title: 'Success', variant: 'success' });
this.loading = false;

View file

@ -14,13 +14,9 @@
/>
</template>
<template v-if="isBoard">
<board-settings />
<b-dropdown-divider />
</template>
<tags-settings />
<account-settings />
<file-settings />
<releases />
<about />
<b-dropdown-divider />
@ -34,8 +30,8 @@
<script>
import TagsSettings from '@/components/Settings/TagsSettings';
import AccountSettings from '@/components/Settings/AccountSettings';
import FileSettings from '@/components/Settings/FileSettings';
import Releases from '@/components/Settings/Releases';
import BoardSettings from '@/components/Settings/BoardSettings';
import SignOut from '@/components/Settings/SignOut';
import About from '@/components/Settings/About';
import { mapState } from 'vuex';
@ -44,18 +40,14 @@ export default {
components: {
TagsSettings,
AccountSettings,
FileSettings,
Releases,
BoardSettings,
SignOut,
About,
},
computed: {
...mapState(['user']),
isBoard() {
return this.$route.name === 'board';
},
},
};
</script>

View file

@ -1,25 +1,48 @@
<template lang="html">
<b-dropdown-item v-b-modal:board-settings>
<b-icon-kanban class="mr-1" />
Board settings
<b-button
v-b-modal:board-settings
class="mt-3"
ref="addList"
>
<b-icon-gear-fill />
<b-modal
id="board-settings"
title="Board settings"
body-class="p-0"
@show="getSettings"
scrollable
@show="init"
>
<b-list-group flush>
<form ref="boardSettingsForm" @submit.stop.prevent="submit">
<b-form-group
label="Board name"
label-for="name"
>
<b-form-input
id="name"
v-model="name"
required
/>
</b-form-group>
<b-list-group-item>
<wallpaper-upload />
</b-list-group-item>
<b-form-group
label="Board descriptiopn"
label-for="description"
>
<b-form-textarea
id="description"
v-model="description"
maxlength="280"
rows="3"
/>
</b-form-group>
<b-list-group-item>
<label for="theme">Theme</label>
<b-form-group
label="Board theme"
label-for="theme"
>
<b-form-select
id="theme"
disabled
v-model="theme"
>
<b-form-select-option
v-for="{ id, name } in themes"
@ -29,60 +52,155 @@
{{ name }}
</b-form-select-option>
</b-form-select>
</b-form-group>
<b-alert show variant="warning" class="mt-3">
Themes are temporarily disabled
</b-alert>
</b-list-group-item>
</b-list-group>
<b-form-group
label="Board wallpaper"
label-for="wallpaper"
>
<b-dropdown
v-if="wallpapers.length"
id="wallpaper"
text="Select wallpaper"
boundary="viewport"
class="m-md-2"
>
<b-dropdown-item variant="danger" v-if="wallpaper" @click="removeWallpaper">
Remove wallpaper
</b-dropdown-item>
<b-dropdown-item
v-for="file in wallpapers"
:key="file.name"
@click="setWallpaper(file)"
>
<b-img
thumbnail
width="200px"
:src="file.url"
:alt="file.name"
fluid
/>
</b-dropdown-item>
</b-dropdown>
</b-form-group>
<b-img
v-if="wallpaper"
thumbnail
width="200px"
:src="wallpaperUrl"
fluid
/>
<platform-picker
v-model="board.platforms"
/>
</form>
<template v-slot:modal-footer>
<b-button
:title="$t('list.delete')"
variant="danger"
@click="promptDeleteBoard"
@click="confirmDelete"
>
Delete board
</b-button>
<b-button
variant="primary"
:disabled="saving"
@click="saveSettings"
>
<b-spinner small v-if="saving" />
<span v-else>Save</span>
</b-button>
</template>
</b-modal>
</b-dropdown-item>
</b-button>
</template>
<script>
import { mapState } from 'vuex';
import themes from '@/themes';
import WallpaperUpload from '@/components/WallpaperUpload';
import PlatformPicker from '@/components/Board/PlatformPicker';
export default {
components: {
WallpaperUpload,
PlatformPicker,
},
data() {
return {
themes,
saving: false,
description: null,
name: null,
platforms: null,
theme: null,
wallpaper: null,
wallpaperUrl: null,
wallpapers: [],
};
},
computed: {
...mapState(['user', 'platform', 'gameLists']),
...mapState(['board', 'user']),
},
methods: {
getSettings() {
// console.log('get settings bitdch!');
// const { sortOrder } = this.gameLists[this.platform.code][this.listIndex];
//
// this.sortOrder = sortOrder || 'sortByCustom';
async loadWallpapers() {
this.wallpapers = [];
const files = await this.$store.dispatch('LOAD_WALLPAPERS');
// TODO: use promise all instead
files.forEach(async (path) => {
const url = await this.$store.dispatch('LOAD_FIRESTORE_FILE', path);
const name = path.split(`${this.user.uid}/wallpapers/`)[1];
this.wallpapers.push({ name, url, path });
});
},
promptDeleteBoard() {
this.$bvModal.msgBoxConfirm('All your data will be removed', {
title: 'Are you sure you want to delete this board?',
removeWallpaper() {
this.wallpaper = null;
this.wallpaperUrl = null;
},
async setWallpaper(file) {
this.wallpaper = file.path;
this.wallpaperUrl = await this.$store.dispatch('LOAD_FIRESTORE_FILE', file.path);
},
submit(e) {
e.preventDefault();
if (this.$refs.createBoardForm.checkValidity()) {
this.createBoard();
}
},
async init() {
const { board } = this;
this.description = board.description;
this.name = board.name;
this.platforms = board.platforms;
this.theme = board.theme || 'default';
this.wallpaper = board.wallpaper;
this.wallpaperUrl = board.wallpaper
? await this.$store.dispatch('LOAD_FIRESTORE_FILE', board.wallpaper)
: null;
this.loadWallpapers();
},
confirmDelete() {
this.$bvModal.msgBoxConfirm('Are you sure you want to delete this board?', {
title: 'Delete board',
okVariant: 'danger',
okTitle: 'Yes, delete board! Hahaha!',
okTitle: 'Yes, delete board',
})
.then((value) => {
if (value) {
@ -91,47 +209,60 @@ export default {
});
},
deleteBoard() {
this.$store.commit('REMOVE_PLATFORM_LEGACY');
async deleteBoard() {
this.loading = true;
this.$store.dispatch('SAVE_LIST_NO_MERGE_LEGACY', this.gameLists)
.then(() => {
this.$router.push({ name: 'platforms' });
})
await this.$store.dispatch('DELETE_BOARD', this.board.id)
.catch(() => {
this.$bvToast.toast('Authentication error', { title: 'Error', variant: 'danger' });
this.loading = false;
this.$bvToast.toast('There was an error deleting board', { title: 'Error', variant: 'error' });
});
this.loading = false;
this.$bvToast.toast('Board removed', { title: 'Success', variant: 'success' });
this.$router.push({ name: 'home' });
},
// async save() {
// this.saving = true;
//
// const gameLists = JSON.parse(JSON.stringify(this.gameLists));
//
// gameLists[this.platform.code][this.listIndex].sortOrder = this.sortOrder;
//
// await this.$store.dispatch('SAVE_LIST_LEGACY', gameLists)
// .catch(() => {
// this.saving = false;
//
// this.$bvToast.toast('There was an error renaming list', {
// title: 'Error',
// variant: 'danger',
// });
// });
//
// this.saving = false;
//
// this.$bvToast.toast('List renamed', {
// title: 'Saved',
// variant: 'success',
// });
//
// this.$bvModal.hide('board-settings');
// },
async saveSettings() {
this.saving = true;
const wallpaperChanged = this.board.wallpaper !== this.wallpaper;
const { board } = this;
const payload = {
...board,
description: this.description,
name: this.name,
platforms: this.platforms,
theme: this.theme,
wallpaper: this.wallpaper,
};
this.$store.commit('SET_BOARD', payload);
await this.$store.dispatch('SAVE_BOARD')
.catch(() => {
this.saving = false;
this.$bvToast.toast('There was an error renaming list', { title: 'Error', variant: 'danger' });
});
this.saving = false;
this.$bvToast.toast('Board settings saved', { title: 'Saved', variant: 'success' });
this.$bvModal.hide('board-settings');
if (wallpaperChanged) {
this.$bus.$emit('RELOAD_WALLPAPER');
}
},
},
};
</script>
<style lang="scss" rel="stylesheet/scss" scoped>
<style lang="scss" rel="stylesheet/scss">
.dropdown-menu {
max-height: 400px;
max-width: 360px;
overflow: auto;
}
</style>

View file

@ -0,0 +1,153 @@
<template lang="html">
<b-dropdown-item v-b-modal:file-settings>
<b-icon-tags class="mr-1" />
Manage files
<b-modal
id="file-settings"
title="Manage Files"
hide-footer
size="xl"
@show="loadWallpapers"
>
<h5>Wallpapers</h5>
<b-form-file
v-model="file"
accept="image/*"
class="mb-3"
placeholder="Choose a file or drop it here..."
drop-placeholder="Drop file here..."
/>
<b-button @click="uploadFile" :disabled="!Boolean(file)">
Upload
</b-button>
<hr>
Files
<div class="wallpapers">
<b-card
v-for="(file, index) in wallpapers"
:key="file.name"
:header="file.name"
header-class="py-0 px-2"
body-class="d-flex p-0 text-center justify-content-center align-items-center"
header-tag="small"
>
<b-img
:src="file.url"
:alt="file.name"
fluid
/>
<b-button
class="delete-file"
variant="danger"
size="sm"
@click="confirmDeleteFile({ file, index })"
>
<b-icon-trash />
</b-button>
</b-card>
</div>
</b-modal>
</b-dropdown-item>
</template>
<script>
import firebase from 'firebase/app';
import 'firebase/firestore';
import { mapState } from 'vuex';
export default {
data() {
return {
file: null,
saving: false,
loading: false,
wallpapers: [],
};
},
computed: {
...mapState(['user', 'board']),
},
methods: {
async loadWallpapers() {
this.wallpapers = [];
this.loading = true;
const files = await this.$store.dispatch('LOAD_WALLPAPERS');
// TODO: use promise all instead
files.forEach(async (path) => {
const url = await this.$store.dispatch('LOAD_FIRESTORE_FILE', path);
const name = path.split(`${this.user.uid}/wallpapers/`)[1];
this.wallpapers.push({ name, url, path });
});
},
uploadFile() {
const { file, user } = this;
this.saving = true;
firebase.storage().ref(`${user.uid}/wallpapers/${file.name}`).put(file)
.then(({ state }) => {
if (state === 'success') {
this.$bvToast.toast(file.name, { title: 'File uploaded', variant: 'success' });
}
});
},
confirmDeleteFile({ file, index }) {
this.$bvModal.msgBoxConfirm(`${file.name} will be permanently removed`, {
title: 'Are you sure you want to delete this file?',
okVariant: 'danger',
okTitle: 'Yes',
})
.then((value) => {
if (value) {
this.deleteFile({ file, index });
}
});
},
async deleteFile({ file, index }) {
await this.$store.dispatch('DELETE_FIRESTORE_FILE', file.path);
this.$bvToast.toast(file.name, { title: 'File deleted', variant: 'success' });
this.wallpapers.splice(index, 1);
if (this.board && this.board.wallpaper && this.board.wallpaper === file.path) {
this.$bus.$emit('RELOAD_WALLPAPER');
}
},
},
};
</script>
<style lang="scss" rel="stylesheet/scss" scoped>
.wallpapers {
display: grid;
grid-template-columns: repeat(5, 1fr);
grid-gap: 1rem;
img {
max-height: 140px;
}
.delete-file {
position: absolute;
bottom: 10px;
right: 10px;
}
}
</style>

View file

@ -1,3 +1,5 @@
export const POPULAR_PLATFORMS = [169, 167, 130, 48, 49, 41];
export const PLATFORM_CATEGORIES = {
1: 'console',
2: 'arcade',
@ -7,6 +9,22 @@ export const PLATFORM_CATEGORIES = {
6: 'computer',
};
export const PLATFORM_FILTER_FIELDS = [
null,
'popular',
'console',
// 'arcade',
// 'platform',
'operating_system',
'portable_console',
'computer',
];
export const PLATFORM_SORT_FILEDS = [
'generation',
'name',
];
export const PLATFORM_BG_HEX = {
167: '#222',
166: '#000',
@ -56,6 +74,7 @@ export const EXCLUDED_PLATFORMS = [
151,
152,
52, // Arcade
79, // Neogeo
153,
154,
155,

View file

@ -25,11 +25,15 @@
"reportBugs": "reporting bugs",
"submitFeedback": "submitting feedback",
"all": "All",
"consoles": "Consoles",
"handheld": "Handhelds",
"console": "Consoles",
"platform": "Digital",
"operating_system": "PC / Other",
"portable_console": "Handhelds",
"computer": "Home Computers",
"popular": "Popular",
"releaseYear": "Year released",
"name": "Alphabetically",
"generation": "Generation / Date relased",
"type": "Type"
},
"gameBoard": {

View file

@ -1,5 +1,5 @@
<template lang="html">
<div class="board" :class="{ dragging }" >
<div :class="['board', { dragging }]" :style="wallpaper" >
<board-placeholder v-if="loading" />
<template v-else>
@ -11,12 +11,17 @@
/>
</template>
<add-list />
<div class="d-flex flex-column pr-3">
<add-list />
<board-settings />
</div>
<game-modal />
</div>
</template>
<script>
import BoardSettings from '@/components/Settings/BoardSettings';
import BoardPlaceholder from '@/components/Board/BoardPlaceholder';
import AddList from '@/components/Board/AddList';
import GameModal from '@/components/Game/GameModal';
@ -31,24 +36,38 @@ export default {
List,
BoardPlaceholder,
AddList,
BoardSettings,
GameModal,
},
data() {
return {
loading: true,
gameData: null,
gameDetailListIndex: null,
queryLimit: 500,
wallpaperUrl: null,
};
},
computed: {
...mapState(['user', 'dragging', 'board']),
wallpaper() {
const { wallpaperUrl } = this;
return wallpaperUrl
? `background-image: url('${wallpaperUrl}');`
: '';
},
},
mounted() {
this.load();
this.$bus.$on('RELOAD_WALLPAPER', this.loadBoardWallpaper);
},
destroyed() {
this.$store.commit('CLEAR_BOARD');
this.$bus.$off('RELOAD_WALLPAPER', this.loadBoardWallpaper);
},
methods: {
@ -70,6 +89,13 @@ export default {
});
this.loadBoardGames();
this.loadBoardWallpaper();
},
async loadBoardWallpaper() {
this.wallpaperUrl = this.board.wallpaper
? await this.$store.dispatch('LOAD_FIRESTORE_FILE', this.board.wallpaper)
: null;
},
loadBoardGames() {
@ -118,9 +144,10 @@ export default {
.board {
user-select: none;
display: flex;
background-size: cover;
align-items: flex-start;
height: calc(100vh - 58px);
padding: 0 1rem;
height: 100vh;
padding: 58px 1rem 0;
box-sizing: border-box;
overflow-x: auto;
overflow-x: overlay;

View file

@ -94,6 +94,40 @@ export default {
});
},
DELETE_FIRESTORE_FILE(context, path) {
return new Promise((resolve, reject) => {
firebase.storage().ref(path).delete()
.then(() => {
resolve();
})
.catch(reject);
});
},
LOAD_FIRESTORE_FILE(context, path) {
const storage = firebase.storage().ref();
return new Promise((resolve, reject) => {
storage.child(path).getDownloadURL()
.then((url) => {
resolve(url);
})
.catch(reject);
});
},
async LOAD_WALLPAPERS({ state }) {
const storage = firebase.storage().ref(`${state.user.uid}/wallpapers`);
return new Promise((resolve, reject) => {
storage
.listAll()
.then(({ items }) => resolve(items.map(({ fullPath }) => fullPath)))
.catch(reject);
});
},
// set merge to true when deleting lists
SAVE_BOARD({ state }, merge = false) {
const db = firebase.firestore();
@ -197,19 +231,6 @@ export default {
});
},
SAVE_SETTINGS_LEGACY({ commit, state }, settings) {
const db = firebase.firestore();
return new Promise((resolve, reject) => {
db.collection('settings').doc(state.user.uid).set(settings, { merge: true })
.then(() => {
commit('SET_SETTINGS', settings);
resolve();
})
.catch(reject);
});
},
SEARCH_LEGACY({ commit, state }, searchText) {
return new Promise((resolve, reject) => {
axios.get(`${API_BASE}/search?search=${searchText}&platform=${state.platform.id}`)
@ -225,6 +246,19 @@ export default {
// STUFF THAT REMAINS THE SAME
//
SAVE_SETTINGS({ commit, state }, settings) {
const db = firebase.firestore();
return new Promise((resolve, reject) => {
db.collection('settings').doc(state.user.uid).set(settings, { merge: true })
.then(() => {
commit('SET_SETTINGS', settings);
resolve();
})
.catch(reject);
});
},
// TODO: combine into single action
SAVE_NOTES({ state }) {
return new Promise((resolve, reject) => {