Progress styling (#164)

* style progress bar/range

* add progress percentage next to bar

* Linting

* Separate GameProgress and GameProgressModal markup

* Display process percentage within/on top of range

* refactor progress element

* add missing gameprogress

it got lost in the rebase

* add margin and center progress on mobile
This commit is contained in:
Patrick Kontschak 2020-01-06 18:05:49 +01:00 committed by Roman Cervantes
parent 01be6fc7e5
commit 52af31e488
9 changed files with 339 additions and 105 deletions

View file

@ -22,11 +22,11 @@
@click.native="openDetails" @click.native="openDetails"
/> />
<progress <game-progress
v-if="gameProgress" v-if="gameProgress"
max="100" small
:value="gameProgress" :progress="gameProgress"
@click="openDetails" @click.native="openDetails"
/> />
<i <i
@ -59,12 +59,14 @@
<script> <script>
import GameRating from '@/components/GameDetail/GameRating'; import GameRating from '@/components/GameDetail/GameRating';
import GameProgress from '@/components/GameDetail/GameProgress';
import GameCardUtils from '@/components/GameCards/GameCard'; import GameCardUtils from '@/components/GameCards/GameCard';
import Tag from '@/components/Tag'; import Tag from '@/components/Tag';
export default { export default {
components: { components: {
GameRating, GameRating,
GameProgress,
Tag, Tag,
}, },

View file

@ -16,11 +16,11 @@
<i class="fas fa-grip-vertical game-drag-handle" /> <i class="fas fa-grip-vertical game-drag-handle" />
<progress <game-progress
v-if="gameProgress" v-if="gameProgress"
max="100" small
:value="gameProgress" :progress="gameProgress"
@click="openDetails" @click.native="openDetails"
/> />
<game-rating <game-rating
@ -60,12 +60,14 @@
<script> <script>
import GameRating from '@/components/GameDetail/GameRating'; import GameRating from '@/components/GameDetail/GameRating';
import GameProgress from '@/components/GameDetail/GameProgress';
import GameCardUtils from '@/components/GameCards/GameCard'; import GameCardUtils from '@/components/GameCards/GameCard';
import Tag from '@/components/Tag'; import Tag from '@/components/Tag';
export default { export default {
components: { components: {
GameRating, GameRating,
GameProgress,
Tag, Tag,
}, },

View file

@ -19,11 +19,11 @@
@click.native="openDetails" @click.native="openDetails"
/> />
<progress <game-progress
v-if="gameProgress" v-if="gameProgress"
max="100" small
:value="gameProgress" :progress="gameProgress"
@click="openDetails" @click.native="openDetails"
/> />
<i <i
@ -53,12 +53,14 @@
<script> <script>
import GameRating from '@/components/GameDetail/GameRating'; import GameRating from '@/components/GameDetail/GameRating';
import GameProgress from '@/components/GameDetail/GameProgress';
import GameCardUtils from '@/components/GameCards/GameCard'; import GameCardUtils from '@/components/GameCards/GameCard';
import Tag from '@/components/Tag'; import Tag from '@/components/Tag';
export default { export default {
components: { components: {
GameRating, GameRating,
GameProgress,
Tag, Tag,
}, },
@ -159,7 +161,7 @@ export default {
grid-row: 2; grid-row: 2;
} }
.game-progresses { .game-progress {
justify-self: end; justify-self: end;
grid-column: 2; grid-column: 2;
grid-row: span 2; grid-row: span 2;

View file

@ -11,11 +11,11 @@
@click.native="openDetails" @click.native="openDetails"
/> />
<progress <game-progress
v-if="gameProgress" v-if="gameProgress"
max="100" small
:value="gameProgress" :progress="gameProgress"
@click="openDetails" @click.native="openDetails"
/> />
<i <i
@ -50,12 +50,14 @@
<script> <script>
import GameRating from '@/components/GameDetail/GameRating'; import GameRating from '@/components/GameDetail/GameRating';
import GameProgress from '@/components/GameDetail/GameProgress';
import GameCardUtils from '@/components/GameCards/GameCard'; import GameCardUtils from '@/components/GameCards/GameCard';
import Tag from '@/components/Tag'; import Tag from '@/components/Tag';
export default { export default {
components: { components: {
GameRating, GameRating,
GameProgress,
Tag, Tag,
}, },

View file

@ -1,6 +1,6 @@
<template lang="html"> <template lang="html">
<div class="game-actions"> <div class="game-actions">
<game-progress /> <game-progress-modal />
<game-notes /> <game-notes />
<div v-if="hasTags" class="tags"> <div v-if="hasTags" class="tags">
@ -36,12 +36,12 @@
<script> <script>
import { mapState, mapGetters } from 'vuex'; import { mapState, mapGetters } from 'vuex';
import GameNotes from '@/components/GameDetail/GameNotes'; import GameNotes from '@/components/GameDetail/GameNotes';
import GameProgress from '@/components/GameDetail/GameProgress'; import GameProgressModal from '@/components/GameDetail/GameProgressModal';
export default { export default {
components: { components: {
GameNotes, GameNotes,
GameProgress, GameProgressModal,
}, },
props: { props: {

View file

@ -1,103 +1,122 @@
<template lang="html"> <template lang="html">
<modal :title="$t('progresses.modalTitle')" ref="progressModal"> <div :class="['game-progress', { small }]">
<button class="primary" :title="buttonLabel"> <div
<i class="fas fa-clock" /> class="progress"
</button> ref="progress"
:style="`--progress: ${progress}%; --progress-width: ${width}px`"
<div slot="content"> >
<h2>{{ localProgress }}%</h2> <div
class="progress-bar-label not-progressed"
<input :data-progress="progress"
v-model="localProgress" ></div>
type="range" <div
max="100" class="progress-bar-label progressed"
step="5" :data-progress="progress"
@change="saveProgress" ></div>
>
<button class="danger" @click="deleteProgress">
{{ $t('progresses.deleteProgress') }}
</button>
</div> </div>
</modal> </div>
</template> </template>
<script> <script>
import Modal from '@/components/Modal';
import { debounce } from 'lodash';
import { mapGetters } from 'vuex';
export default { export default {
components: { props: {
Modal, progress: {
type: String,
default: '',
},
small: {
type: Boolean,
default: false,
},
}, },
data() { data() {
return { return {
localProgress: {}, width: 0,
}; };
}, },
computed: {
...mapGetters(['gameProgress']),
defaultValue() {
return {
value: 0,
};
},
buttonLabel() {
return this.gameProgress
? this.$t('progresses.updateProgress')
: this.$t('progresses.addProgress');
},
},
watch: {
gameProgress() {
this.reset();
},
},
mounted() { mounted() {
this.reset(); this.getProgressBarWidth();
}, },
methods: { methods: {
reset() { getProgressBarWidth() {
this.localProgress = this.gameProgress this.width = this.$refs.progress.clientWidth;
? JSON.parse(JSON.stringify(this.gameProgress))
: 0;
}, },
async deleteProgress() {
this.$store.commit('REMOVE_GAME_PROGRESS');
await this.$store.dispatch('SAVE_PROGRESSES_NO_MERGE')
.catch(() => {
this.$bus.$emit('TOAST', { message: 'There was an error deleting your progress', type: 'error' });
this.$router.push({ name: 'sessionExpired' });
});
this.$bus.$emit('TOAST', { message: 'Progress deleted' });
this.$refs.progressModal.close();
},
saveProgress: debounce(
// eslint-disable-next-line
function () {
this.$store.commit('SET_GAME_PROGRESS', this.localProgress);
this.$store.dispatch('SAVE_PROGRESSES')
.then(() => {
this.$bus.$emit('TOAST', { message: 'Progress updated' });
})
.catch(() => {
this.$bus.$emit('TOAST', { message: 'There was an error saving your progress', type: 'error' });
this.$router.push({ name: 'sessionExpired' });
});
}, 300),
}, },
}; };
</script> </script>
<style lang="scss" rel="stylesheet/scss" scoped>
@import "~styles/styles";
.game-progress {
display: grid;
width: 140px;
align-items: center;
justify-items: center;
margin: $gp / 2 0;
@media($small) {
margin: $gp auto;
}
.progress {
display: grid;
height: 20px;
width: 100%;
margin: $gp / 4 0;
overflow: hidden;
border-radius: $border-radius / 2;
}
.progress-bar-label {
grid-row: 1;
grid-column: 1;
width: 100%;
overflow: hidden;
font-weight: bold;
&::after {
content: attr(data-progress) "%";
line-height: 1.5;
display: block;
width: 140px;
text-align: center;
color: var(--accent-color);
}
&.not-progressed {
background: var(--list-background);
}
&.progressed {
z-index: 1;
width: var(--progress);
background: var(--accent-color);
&::after {
color: var(--game-card-text-color);
}
}
}
&.small {
width: auto;
margin: 0;
.progress {
height: 10px;
}
.progress-bar-label {
font-size: $font-size-xsmall;
&::after {
width: var(--progress-width);
line-height: 1;
}
}
}
}
</style>

View file

@ -0,0 +1,103 @@
<template lang="html">
<modal :title="$t('progresses.modalTitle')" ref="progressModal">
<button class="primary" :title="buttonLabel">
<i class="fas fa-clock" />
</button>
<div slot="content">
<h2>{{ localProgress }}%</h2>
<input
v-model="localProgress"
type="range"
max="100"
step="5"
@change="saveProgress"
>
<button class="danger" @click="deleteProgress">
{{ $t('progresses.deleteProgress') }}
</button>
</div>
</modal>
</template>
<script>
import Modal from '@/components/Modal';
import { debounce } from 'lodash';
import { mapGetters } from 'vuex';
export default {
components: {
Modal,
},
data() {
return {
localProgress: {},
};
},
computed: {
...mapGetters(['gameProgress']),
defaultValue() {
return {
value: 0,
};
},
buttonLabel() {
return this.gameProgress
? this.$t('progresses.updateProgress')
: this.$t('progresses.addProgress');
},
},
watch: {
gameProgress() {
this.reset();
},
},
mounted() {
this.reset();
},
methods: {
reset() {
this.localProgress = this.gameProgress
? JSON.parse(JSON.stringify(this.gameProgress))
: 0;
},
async deleteProgress() {
this.$store.commit('REMOVE_GAME_PROGRESS');
await this.$store.dispatch('SAVE_PROGRESSES_NO_MERGE')
.catch(() => {
this.$bus.$emit('TOAST', { message: 'There was an error deleting your progress', type: 'error' });
this.$router.push({ name: 'sessionExpired' });
});
this.$bus.$emit('TOAST', { message: 'Progress deleted' });
this.$refs.progressModal.close();
},
saveProgress: debounce(
// eslint-disable-next-line
function () {
this.$store.commit('SET_GAME_PROGRESS', this.localProgress);
this.$store.dispatch('SAVE_PROGRESSES')
.then(() => {
this.$bus.$emit('TOAST', { message: 'Progress updated' });
})
.catch(() => {
this.$bus.$emit('TOAST', { message: 'There was an error saving your progress', type: 'error' });
this.$router.push({ name: 'sessionExpired' });
});
}, 300),
},
};
</script>

View file

@ -22,14 +22,14 @@
<h2>{{ games[id].name }}</h2> <h2>{{ games[id].name }}</h2>
<h4>{{ platform.name }}</h4> <h4>{{ platform.name }}</h4>
<progress <game-progress
v-if="gameProgress" v-if="gameProgress"
max="100" :progress="gameProgress"
:value="gameProgress"
/> />
<game-rating v-if="games[id].rating" :rating="games[id].rating" /> <game-rating v-if="games[id].rating" :rating="games[id].rating" />
<game-tags /> <game-tags />
<!-- TODO: set list id to store instead of passing it around --> <!-- TODO: set list id to store instead of passing it around -->
<game-actions :list-id="listId" /> <game-actions :list-id="listId" />
</div> </div>
@ -68,6 +68,7 @@
import { mapState, mapGetters } from 'vuex'; import { mapState, mapGetters } from 'vuex';
import GameScreenshots from '@/components/GameDetail/GameScreenshots'; import GameScreenshots from '@/components/GameDetail/GameScreenshots';
import VueMarkdown from 'vue-markdown'; import VueMarkdown from 'vue-markdown';
import GameProgress from '@/components/GameDetail/GameProgress';
import GameActions from '@/components/GameDetail/GameActions'; import GameActions from '@/components/GameDetail/GameActions';
import GameTags from '@/components/GameDetail/GameTags'; import GameTags from '@/components/GameDetail/GameTags';
import GameRating from '@/components/GameDetail/GameRating'; import GameRating from '@/components/GameDetail/GameRating';
@ -89,6 +90,7 @@ export default {
GameScreenshots, GameScreenshots,
GameActions, GameActions,
VueMarkdown, VueMarkdown,
GameProgress,
GameTags, GameTags,
GameVideos, GameVideos,
GameDetails, GameDetails,

View file

@ -76,3 +76,105 @@ textarea {
} }
} }
} }
input[type=range] {
-webkit-appearance: none;
appearance: none;
width: 100%;
height: 36px;
border: 0;
border-radius: $border-radius;
padding: 0;
background: transparent;
overflow: hidden;
order: 1;
margin: $gp 0;
&:focus {
outline: none;
&::-webkit-slider-runnable-track,
&::-ms-fill-lower,
&::-ms-fill-upper {
background: var(--accent-color);
}
}
&::-webkit-slider-runnable-track {
width: 100%;
height: 36px;
cursor: pointer;
animate: 0.2s;
background: var(--accent-color);
border-radius: $border-radius;
overflow: hidden;
}
&::-webkit-slider-thumb {
height: 36px;
width: 16px;
background: var(--primary-background);
cursor: pointer;
border-radius: 0;
-webkit-appearance: none;
box-shadow: 500px 0 0 500px var(--list-background);
}
&::-moz-range-track {
width: 100%;
height: 36px;
cursor: pointer;
animate: 0.2s;
overflow: hidden;
background: var(--list-background);
border-radius: $border-radius;
}
&::-moz-range-thumb {
height: 36px;
width: 16px;
border: none;
border-radius: 0;
background: var(--primary-background);
cursor: pointer;
}
&::-moz-range-progress {
height: 36px;
width: 16px;
border: none;
border-radius: 0;
background: var(--accent-color);
}
&::-ms-track {
width: 100%;
height: 36px;
cursor: pointer;
animate: 0.2s;
overflow: hidden;
background: transparent;
border-color: transparent;
border-width: 16px 0;
color: transparent;
}
&::-ms-fill-lower {
background: var(--accent-color);
border-radius: $border-radius;
}
&::-ms-fill-upper {
background: var(--accent-color);
border-radius: $border-radius;
}
&::-ms-thumb {
height: 36px;
width: 16px;
border-radius: 0;
background: var(--primary-background);
box-shadow: 500px 0 0 500px var(--list-background);
cursor: pointer;
}
}