Refactor tags

This commit is contained in:
Gamebrary 2022-08-11 23:34:40 -07:00
parent 97d7513c96
commit d619ac9796
17 changed files with 181 additions and 274 deletions

View file

@ -7,7 +7,7 @@
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png"> <link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png">
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png"> <link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png"> <link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png">
<link rel="manifest" href="/site.webmanifest"> <!-- <link rel="manifest" href="/site.webmanifest"> -->
<link rel="mask-icon" href="/safari-pinned-tab.svg" color="#5bbad5"> <link rel="mask-icon" href="/safari-pinned-tab.svg" color="#5bbad5">
<meta name="msapplication-TileColor" content="#da532c"> <meta name="msapplication-TileColor" content="#da532c">
<meta name="theme-color" content="#ffffff"> <meta name="theme-color" content="#ffffff">

View file

@ -100,10 +100,6 @@ export default {
isGamePage() { isGamePage() {
return this.$route.name === 'game'; return this.$route.name === 'game';
}, },
isBoard() {
return ['public.board', 'board'].includes(this.$route.name);
},
}, },
watch: { watch: {

View file

@ -37,19 +37,19 @@
height="6px" height="6px"
/> />
<template v-if="showGameTags"> <!-- TODO: use correct tags -->
<b-badge <!-- <pre>{{ gameTags }}</pre> -->
v-for="({ hex, tagTextColor }, name) in gameTags" <!-- <b-badge
:key="name" v-for="({ hex, tagTextColor }, name) in gameTags"
pill :key="name"
class="mr-1" pill
variant="primary" class="mr-1"
tag="small" variant="primary"
:style="`background-color: ${hex}; color: ${tagTextColor}`" tag="small"
> :style="`background-color: ${hex}; color: ${tagTextColor}`"
<small>{{ name }}</small> >
</b-badge> <small>{{ name }}</small>
</template> </b-badge> -->
</b-card-body> </b-card-body>
</b-col> </b-col>
</b-row> </b-row>

View file

@ -42,14 +42,14 @@
<small v-else>{{ gameProgress }}%</small> <small v-else>{{ gameProgress }}%</small>
</b-badge> </b-badge>
<template v-if="showGameTags"> <template v-if="tagsApplied.length">
<b-badge <b-badge
v-for="({ hex, tagTextColor }, name) in gameTags" v-for="({ bgColor, textColor, name }) in tagsApplied"
:key="name" :key="name"
pill pill
class="mr-1" class="mr-1"
variant="primary" variant="primary"
:style="`background-color: ${hex}; color: ${tagTextColor}`" :style="`background-color: ${bgColor}; color: ${textColor}`"
> >
<small>{{ name }}</small> <small>{{ name }}</small>
</b-badge> </b-badge>

View file

@ -21,7 +21,8 @@
height="6px" height="6px"
/> />
<template v-if="showGameTags"> <!-- TODO: use correct tags -->
<!-- <template v-if="showGameTags">
<b-badge <b-badge
v-for="({ hex, tagTextColor }, name) in gameTags" v-for="({ hex, tagTextColor }, name) in gameTags"
:key="name" :key="name"
@ -32,7 +33,7 @@
> >
<small>{{ name }}</small> <small>{{ name }}</small>
</b-badge> </b-badge>
</template> </template> -->
<b-badge variant="warning" v-if="gameNotes"> <b-badge variant="warning" v-if="gameNotes">
<i class="far fa-sticky-note fa-fw" /> <i class="far fa-sticky-note fa-fw" />

View file

@ -23,7 +23,8 @@
height="6px" height="6px"
/> />
<template v-if="showGameTags"> <!-- TODO: use correct tags -->
<!-- <template v-if="showGameTags">
<b-badge <b-badge
v-for="({ hex, tagTextColor }, name) in gameTags" v-for="({ hex, tagTextColor }, name) in gameTags"
:key="name" :key="name"
@ -34,7 +35,7 @@
> >
<small class="font-weight-bold">{{ name }}</small> <small class="font-weight-bold">{{ name }}</small>
</b-badge> </b-badge>
</template> </template> -->
</b-card-body> </b-card-body>
</b-row> </b-row>
</b-card> </b-card>

View file

@ -1,22 +1,16 @@
<template lang="html"> <template lang="html">
<b-row> <b-row class="p-3">
<b-col <b-button
v-for="({ hex, tagTextColor, name }, index) in tags" v-for="({ textColor, bgColor, name }, index) in tags"
@click="$router.push({ name: 'tag.edit', params: { id: index } })" @click="$router.push({ name: 'tag.edit', params: { id: index } })"
cols="6" rounded
xl="4" block
class="mb-3" variant="outline-light"
:style="`background-color: ${bgColor}; color: ${textColor}`"
:key="name" :key="name"
> >
<b-button {{ name }}
rounded </b-button>
block
variant="outline-light"
:style="`background-color: ${hex}; color: ${tagTextColor}`"
>
{{ name }}
</b-button>
</b-col>
</b-row> </b-row>
</template> </template>
@ -43,13 +37,5 @@ export default {
this.loading = false; this.loading = false;
}, },
methods: {
openGame(gameId) {
const { id, slug } = this.games[gameId];
this.$router.push({ name: 'game.tags', params: { id, slug } })
},
},
}; };
</script> </script>

View file

@ -74,7 +74,7 @@ export const GENRE_ICONS = {
export const KEYBOARD_SHORTCUTS = { export const KEYBOARD_SHORTCUTS = {
'MODAL_keyboard-shortcuts': ['shift', '?'], 'MODAL_keyboard-shortcuts': ['shift', '?'],
'MODAL_create-board': ['shift', 'c'], 'MODAL_create-board': ['shift', 'c'],
MODAL_devTools: ['shift', 'd'], // MODAL_devTools: ['shift', 'd'],
ROUTE_boards: ['shift', 'b'], ROUTE_boards: ['shift', 'b'],
ROUTE_tags: ['shift', 't'], ROUTE_tags: ['shift', 't'],
ROUTE_notes: ['shift', 'n'], ROUTE_notes: ['shift', 'n'],

View file

@ -17,25 +17,10 @@ export default {
return this.gameProgress && Number(this.gameProgress) === 100; return this.gameProgress && Number(this.gameProgress) === 100;
}, },
gameTags() {
const tagsArray = Object.entries(this.tags);
const filteredTags = tagsArray.filter(([key, value]) => {
return value.games.includes(this.gameId);
});
const filteredTagsObject = Object.fromEntries(filteredTags);
return filteredTagsObject;
},
showGameProgress() { showGameProgress() {
return this.gameProgress > 0; return this.gameProgress > 0;
}, },
showGameTags() {
return this.list?.settings?.showGameTags && this.gameTags;
},
showReleaseDates() { showReleaseDates() {
return this.list?.settings?.showReleaseDates; return this.list?.settings?.showReleaseDates;
}, },
@ -48,6 +33,10 @@ export default {
: 0; : 0;
}, },
tagsApplied() {
return this.tags?.filter((tag) => tag?.games?.includes(this.game?.id))
},
gameNotes() { gameNotes() {
const { settings } = this.list; const { settings } = this.list;

View file

@ -4,112 +4,85 @@
Create tag Create tag
</portal> </portal>
<form <form @submit.prevent="submit">
ref="newTagForm" <label for="tagName">Tag name:</label>
@submit.stop.prevent="submit" <b-form-input
> id="tagName"
<b-form-row class="mb-3"> v-model.trim="tag.name"
<b-col cols="8" md="9"> class="mb-3 field"
<b-form-input maxlength="20"
maxlength="20" :placeholder="$t('tags.form.inputPlaceholder')"
:placeholder="$t('tags.form.inputPlaceholder')" required
required trim
v-model.trim="tagName" />
/>
<b-form-text v-if="tagName" tag="span"> <p>Background color</p>
{{ $t('tags.form.preview') }} <v-swatches v-model="tag.bgColor" show-fallback />
<b-badge :style="`background-color: ${hex}; color: ${tagTextColor}`"> <p>Text color</p>
{{ tagName }} <v-swatches v-model="tag.textColor" show-fallback />
</b-badge>
</b-form-text>
</b-col>
<b-col cols="4" md="3"> <p>Preview</p>
<b-input-group>
<b-form-input
v-model="hex"
type="color"
required
/>
<b-form-input <b-button
v-model="tagTextColor" rounded
type="color" block
required size="sm"
/> class="mr-2 mb-2 field"
</b-input-group> variant="outline-light"
</b-col> :style="`background-color: ${tag.bgColor}; color: ${tag.textColor}`"
</b-form-row> >
{{ tag.name || 'Tag preview' }}
</b-button>
<b-button <b-button
variant="primary" variant="primary"
class="d-flex ml-auto" :disabled="saving"
:disabled="isDuplicate || saving || !Boolean(tagName)" type="submit"
@click="submit"
> >
<b-spinner small v-if="saving" /> <b-spinner small v-if="saving" />
<span v-else>{{ $t('tags.form.addTag')}}</span> <span v-else>Create</span>
</b-button> </b-button>
<b-alert
class="mt-3 mb-0"
:show="isDuplicate"
variant="warning"
>
{{ $t('tags.form.duplicateMessage', { tagName }) }}
<strong>{{ tagName }}</strong>
</b-alert>
</form> </form>
</b-container> </b-container>
</template> </template>
<script> <script>
import { mapState } from 'vuex'; import { mapState } from 'vuex';
import VSwatches from 'vue-swatches'
export default { export default {
data() { data() {
return { return {
colorCombinations: [ tag: {
['#0d1137', '#e52165'], name: '',
['#ffffff', '#000000'], textColor: '#DDE6E8',
['#101820', '#FEE715'], bgColor: '#1FBC9C',
['#F2AA4C', '#101820'], games: [],
['#F93822', '#FDD20E'], },
], saving: false,
tagTextColor: '#F4B41A',
tagName: '',
} }
}, },
mounted() { components: {
this.setRandomColors(); VSwatches,
}, },
computed: { computed: {
...mapState(['tags', 'platform', 'games']), ...mapState(['tags']),
// isDuplicate() {
// const { tagName, localTags } = this;
//
// const tagNames = Object.keys(localTags)
// .filter(name => name !== tagName)
// .map(name => name.toLowerCase());
//
// return tagNames.includes(tagName.toLowerCase());
// },
}, },
methods: { methods: {
setRandomColors() { async submit() {
const { colorCombinations } = this; this.$store.commit('CREATE_TAG', this.tag);
this.saving = true;
const randomNumber = Math.floor(Math.random() * colorCombinations.length); await this.$store.dispatch('SAVE_GAME_TAGS')
.catch(() => {});
this.tagTextColor = colorCombinations[randomNumber][0]; this.saving = true;
this.hex = colorCombinations[randomNumber][1]; this.$router.push({ name: 'tags' })
}, },
}, },
}; };
</script> </script>

View file

@ -34,14 +34,6 @@
ref="form" ref="form"
@submit="saveTag" @submit="saveTag"
> >
<b-alert
class="mt-3 mb-0"
:show="isEditedNameDuplicate && !saving"
variant="warning"
>
You already have a tag named <strong>{{ tag.name }}</strong>
</b-alert>
<label for="tagName">Tag name:</label> <label for="tagName">Tag name:</label>
<b-form-input <b-form-input
@ -57,15 +49,14 @@
<p>Background color</p> <p>Background color</p>
<v-swatches <v-swatches
v-model="tag.hex" v-model="tag.bgColor"
show-fallback show-fallback
popover-x="left" popover-x="left"
/> />
<p>Text color</p> <p>Text color</p>
<v-swatches <v-swatches
v-model="tag.tagTextColor" v-model="tag.textColor"
show-fallback show-fallback
popover-x="left" popover-x="left"
/> />
@ -79,7 +70,7 @@
size="sm" size="sm"
class="mr-2 mb-2 field" class="mr-2 mb-2 field"
variant="outline-light" variant="outline-light"
:style="`background-color: ${tag.hex}; color: ${tag.tagTextColor}`" :style="`background-color: ${tag.bgColor}; color: ${tag.textColor}`"
> >
{{ tag.name }} {{ tag.name }}
</b-button> </b-button>
@ -88,6 +79,11 @@
<p>Games tagged</p> <p>Games tagged</p>
<b-alert :show="tag.games.length === 0" variant="light" class="field">
No games tagged
</b-alert>
<!-- TODO: add quick game picker -->
<div class="tagged-games"> <div class="tagged-games">
<b-img <b-img
v-for="game in tag.games" v-for="game in tag.games"
@ -103,7 +99,7 @@
<b-button <b-button
variant="primary" variant="primary"
:disabled="isEditedNameDuplicate || saving" :disabled="saving"
type="submit" type="submit"
> >
<b-spinner small v-if="saving" /> <b-spinner small v-if="saving" />
@ -122,7 +118,6 @@ export default {
return { return {
tag: {}, tag: {},
loading: true, loading: true,
originalTagName: '',
localTags: {}, localTags: {},
saving: false, saving: false,
} }
@ -135,18 +130,6 @@ export default {
computed: { computed: {
...mapState(['tags', 'games']), ...mapState(['tags', 'games']),
tagNames() {
const sanitizedNames = this.tags?.map(({ name }) => name.toLowerCase());
return sanitizedNames.length > 0
? sanitizedNames.filter(name => name?.toLowerCase() !== this.originalTagName?.toLowerCase())
: [];
},
isEditedNameDuplicate() {
return this.tagNames?.includes(this.tag?.name?.toLowerCase());
},
tagIndex() { tagIndex() {
return this.$route?.params?.id; return this.$route?.params?.id;
}, },
@ -172,7 +155,6 @@ export default {
const { tags, tagIndex } = this; const { tags, tagIndex } = this;
this.tag = JSON.parse(JSON.stringify(tags[tagIndex])); this.tag = JSON.parse(JSON.stringify(tags[tagIndex]));
this.originalTagName = JSON.parse(JSON.stringify(this.tag.name));
this.loading = false; this.loading = false;
}, },
@ -194,20 +176,15 @@ export default {
}, },
deleteTag(tagName) { deleteTag(tagName) {
this.$delete(this.localTags, tagName); // TODO: call mutation to remove tag and save tags
this.saveTags(true); // this.saveTags(true);
},
removeTag(tagName) {
this.$store.commit('REMOVE_GAME_TAG', { tagName, gameId: this.gameId });
this.saveTags();
}, },
async saveTag(e) { async saveTag(e) {
e.preventDefault(); e.preventDefault();
if (this.$refs.form.checkValidity()) { if (this.$refs.form.checkValidity()) {
const { tag, tags, originalTagName } = this; const { tag, tags } = this;
tags[this.tagIndex] = tag; tags[this.tagIndex] = tag;

View file

@ -96,14 +96,12 @@
</header> </header>
<b-button <b-button
v-for="({ hex, tagTextColor, name }) in tags" v-for="({ bgColor, textColor, name }) in tagsApplied"
:key="name" :key="name"
rounded rounded
size="sm" size="sm"
variant="outline-light"
class="mr-1 my-2" class="mr-1 my-2"
:disabled="saving" :style="`background-color: ${bgColor}; color: ${textColor}`"
:style="`background-color: ${hex}; color: ${tagTextColor}`"
@click="$router.push({ name: 'game.tags', params: { id: game.id, slug: game.slug } })" @click="$router.push({ name: 'game.tags', params: { id: game.id, slug: game.slug } })"
> >
{{ name }} {{ name }}
@ -335,6 +333,10 @@ export default {
}, },
tagsApplied() {
return this.tags?.filter((tag) => tag?.games?.includes(this.game?.id))
},
legalNotice() { legalNotice() {
return this.game?.steam?.legal_notice; return this.game?.steam?.legal_notice;
}, },

View file

@ -51,22 +51,20 @@
<section> <section>
<h3 class="mb-3">Tags applied to {{ game.name }}</h3> <h3 class="mb-3">Tags applied to {{ game.name }}</h3>
<b-alert <b-alert :show="noneSelected" variant="light">
v-if="tagsSelected.length === 0"
show
variant="light"
>
No tags applied No tags applied
</b-alert> </b-alert>
<!-- TODO: use correct tags -->
<b-button <b-button
v-for="{ name, hex, tagTextColor } in tags" v-for="({ selected, name, bgColor, textColor }, index) in formattedTags"
:key="name" :key="name"
rounded rounded
block block
variant="outline-light" variant="outline-light"
:style="`background-color: ${hex}; color: ${tagTextColor}`" :class="{ 'd-none': !selected }"
@click="removeTag" :disabled="saving"
:style="`background-color: ${bgColor}; color: ${textColor}`"
@click="removeTag(index)"
> >
{{ name }} {{ name }}
</b-button> </b-button>
@ -76,19 +74,19 @@
<h3 class="my-3">Tags available</h3> <h3 class="my-3">Tags available</h3>
<pre>{{ tags }}</pre> <b-button
<!-- <b-button v-for="({ selected, name, bgColor, textColor }, index) in formattedTags"
v-for="({ name, hex, tagTextColor }, index) in tags"
:key="name" :key="name"
rounded rounded
block block
variant="outline-light" variant="outline-light"
:class="{ 'd-none': selected }"
:disabled="saving" :disabled="saving"
:style="`background-color: ${hex}; color: ${tagTextColor}`" :style="`background-color: ${bgColor}; color: ${textColor}`"
@click="addTag(index)" @click="addTag(index)"
> >
{{ name }} {{ name }}
</b-button> --> </b-button>
</section> </section>
</b-col> </b-col>
</b-row> </b-row>
@ -131,36 +129,25 @@ export default {
return Object.keys(this.tags).length === 0; return Object.keys(this.tags).length === 0;
}, },
tagsSelected() { formattedTags() {
return this.tags?.filter(({ games }) => { return this.tags.map((tag) => ({
return games?.includes(this.game?.id); ...tag,
}) selected: tag.games.includes(Number(this.$route.params.id)),
}));
}, },
tagsAvailable() { noneSelected() {
return Object.entries(this.tags).map((t) => { return !this.formattedTags.some(({ selected }) => Boolean(selected));
const [name, tag] = t;
return { name: name, ...tag };
})
.filter(({ games }) => {
return !games.includes(this.game.id);
})
}, },
}, },
mounted() { mounted() {
if (this.game?.id !== this.$route.params.id) { this.load();
this.loadGame();
} else {
this.loading = false;
}
}, },
methods: { methods: {
async loadGame() { async load() {
this.loading = true; this.loading = true;
this.$store.commit('CLEAR_GAME');
await this.$store.dispatch('LOAD_GAME', this.$route.params.id); await this.$store.dispatch('LOAD_GAME', this.$route.params.id);
await this.$store.dispatch('LOAD_TAGS') await this.$store.dispatch('LOAD_TAGS')
@ -172,36 +159,28 @@ export default {
}, },
async addTag(index) { async addTag(index) {
console.log('add this tag'); this.$store.commit('APPLY_TAG_TO_GAME', index);
// TODO: use commit instead? this.saving = true;
// const gameId = this.game.id;
//
// if (!gameId) return;
//
// const tags = JSON.parse(JSON.stringify(this.tags)) ;
//
// tags[index].games.push(gameId)
//
// console.log(`game id ${gameId} should be included`, tags[index].games);
// this.saving = true; await this.$store.dispatch('SAVE_GAME_TAGS')
.catch(() => {
this.saving = false;
});
// await this.$store.dispatch('SAVE_TAGS', tags) this.saving = false;
// .catch((e) => {
// console.log(e);
// });
// this.saving = false;
// this.$store.commit('ADD_GAME_TAG', { tagName, gameId });
// await this.saveTags();
}, },
async removeTag(tagName) { async removeTag(index) {
const gameId = this.game.id; this.$store.commit('REMOVE_GAME_TAG', index);
// this.$store.commit('REMOVE_GAME_TAG', { tagName, gameId }); this.saving = true;
// await this.saveTags();
await this.$store.dispatch('SAVE_GAME_TAGS')
.catch(() => {
this.saving = false;
});
this.saving = false;
}, },
manageTags() { manageTags() {

View file

@ -7,6 +7,7 @@
<portal to="headerActions"> <portal to="headerActions">
<b-button <b-button
v-if="!showEmptyState"
class="mr-2" class="mr-2"
variant="light" variant="light"
:to="{ name: 'tag.create' }" :to="{ name: 'tag.create' }"
@ -27,9 +28,7 @@
</b-button> </b-button>
</empty-state> </empty-state>
<b-col v-else> <tags-list v-else />
<tags-list />
</b-col>
</b-row> </b-row>
</b-container> </b-container>
</template> </template>

View file

@ -581,6 +581,18 @@ export default {
}); });
}, },
SAVE_GAME_TAGS({ state }) {
const db = firestore();
return new Promise((resolve, reject) => {
db.collection('tags')
.doc(state.user.uid)
.set({ tags: state.tags }, { merge: false })
.then(() => resolve())
.catch(reject);
});
},
GET_TWITCH_TOKEN({ commit }) { GET_TWITCH_TOKEN({ commit }) {
const db = firestore(); const db = firestore();
@ -726,14 +738,13 @@ export default {
const { tags } = doc.data(); const { tags } = doc.data();
if (typeof tags === 'object') { if (typeof tags === 'object') {
console.warn('Legacy tag detected'); // console.warn('Legacy tag detected');
const formattedTags = Object.entries(tags).map(([ ,tag]) => ({ ...tag })); const formattedTags = Object.entries(tags).map(([ ,tag]) => ({ ...tag }));
commit('SET_TAGS', formattedTags); commit('SET_TAGS', formattedTags);
resolve(formattedTags); resolve(formattedTags);
} else { } else {
console.log('is type', typeof tags); // console.log('is type', typeof tags);
} }
}); });
}); });

View file

@ -9,19 +9,6 @@ export default {
return board?.owner === user?.uid; return board?.owner === user?.uid;
}, },
gameTags: ({ tags, game }) => {
if (!game?.id) return [];
// TODO: refactor architecture, don't use tag name as key
const tagsArray = Object.entries(tags);
const filteredTags = tagsArray.filter(([key, value]) => {
return value.games.includes(game.id);
});
const filteredTagsObject = Object.fromEntries(filteredTags);
return filteredTagsObject;
},
// Arabic is the only ltr language supported at the moment // Arabic is the only ltr language supported at the moment
isRTL: ({ settings }) => settings && settings.language !== 'ar', isRTL: ({ settings }) => settings && settings.language !== 'ar',

View file

@ -175,21 +175,21 @@ export default {
state.settings = settings; state.settings = settings;
}, },
UPDATE_TAG(state, { tagName, tagHex, tempTag }) { // UPDATE_TAG(state, { tagName, tagHex, tempTag }) {
const updatedTag = { // const updatedTag = {
...state.tags[tempTag.tagName], // ...state.tags[tempTag.tagName],
hex: tagHex, // hex: tagHex,
}; // };
//
const renaming = tagName !== tempTag.tagName; // const renaming = tagName !== tempTag.tagName;
//
if (renaming) { // if (renaming) {
Vue.set(state.tags, tagName, updatedTag); // Vue.set(state.tags, tagName, updatedTag);
Vue.delete(state.tags, tempTag.tagName); // Vue.delete(state.tags, tempTag.tagName);
} else { // } else {
state.tags[tempTag.tagName] = updatedTag; // state.tags[tempTag.tagName] = updatedTag;
} // }
}, // },
SET_TAGS(state, tags) { SET_TAGS(state, tags) {
state.tags = tags; state.tags = tags;
@ -247,12 +247,18 @@ export default {
} }
}, },
ADD_GAME_TAG(state, { tagName, gameId }) { APPLY_TAG_TO_GAME(state, tagIndex) {
state.tags[tagName].games.push(gameId); state.tags[tagIndex].games.push(state.game.id);
}, },
REMOVE_GAME_TAG(state, { tagName, gameId }) { CREATE_TAG(state, tag) {
state.tags[tagName].games.splice(state.tags[tagName].games.indexOf(gameId), 1); state.tags.push(tag);
},
REMOVE_GAME_TAG(state, tagIndex) {
const gameIndex = state.tags[tagIndex].games.indexOf(state.game.id);
state.tags[tagIndex].games.splice(gameIndex, 1);
}, },
SET_SEARCH_RESULTS(state, results) { SET_SEARCH_RESULTS(state, results) {