From 3634c00ee61db4db3672e943e61ddc784f4d9a46 Mon Sep 17 00:00:00 2001 From: Eliot Berriot <contact@eliotberriot.com> Date: Thu, 7 Jun 2018 12:59:22 +0200 Subject: [PATCH] See #262: light messaging area and helpers for the front-end --- front/src/App.vue | 17 ++++- front/src/components/ServiceMessages.vue | 83 ++++++++++++++++++++++++ front/src/components/common/Message.vue | 36 ++++++++++ front/src/components/globals.js | 4 ++ front/src/store/ui.js | 11 +++- front/test/unit/specs/store/ui.spec.js | 18 +++++ 6 files changed, 167 insertions(+), 2 deletions(-) create mode 100644 front/src/components/ServiceMessages.vue create mode 100644 front/src/components/common/Message.vue create mode 100644 front/test/unit/specs/store/ui.spec.js diff --git a/front/src/App.vue b/front/src/App.vue index 673f83864..91cf29843 100644 --- a/front/src/App.vue +++ b/front/src/App.vue @@ -1,6 +1,7 @@ <template> <div id="app"> <sidebar></sidebar> + <service-messages v-if="messages.length > 0" /> <router-view :key="$route.fullPath"></router-view> <div class="ui fitted divider"></div> <div id="footer" class="ui vertical footer segment"> @@ -44,9 +45,11 @@ <script> import axios from 'axios' import _ from 'lodash' +import {mapState} from 'vuex' import Sidebar from '@/components/Sidebar' import Raven from '@/components/Raven' +import ServiceMessages from '@/components/ServiceMessages' import PlaylistModal from '@/components/playlists/PlaylistModal' @@ -55,7 +58,8 @@ export default { components: { Sidebar, Raven, - PlaylistModal + PlaylistModal, + ServiceMessages }, data () { return { @@ -80,6 +84,9 @@ export default { } }, computed: { + ...mapState({ + messages: state => state.ui.messages + }), version () { if (!this.nodeinfo) { return null @@ -115,6 +122,14 @@ html, body { } transform: none !important; } +.service-messages { + position: fixed; + bottom: 1em; + left: 1em; + @include media(">desktop") { + left: 350px; + } +} .main-pusher { padding: 1.5rem 0; } diff --git a/front/src/components/ServiceMessages.vue b/front/src/components/ServiceMessages.vue new file mode 100644 index 000000000..90f6f0679 --- /dev/null +++ b/front/src/components/ServiceMessages.vue @@ -0,0 +1,83 @@ +<template> + <div class="service-messages"> + <message v-for="message in displayedMessages" :key="String(message.date)" :class="['large', getLevel(message)]"> + <p>{{ message.content }}</p> + </message> + </div> +</template> + +<script> +import _ from 'lodash' +import {mapState} from 'vuex' + +export default { + data () { + return { + date: new Date(), + interval: null + } + }, + created () { + this.setupInterval() + }, + destroyed () { + if (this.interval) { + clearInterval(this.interval) + } + }, + computed: { + ...mapState({ + messages: state => state.ui.messages, + displayDuration: state => state.ui.messageDisplayDuration + }), + displayedMessages () { + let now = this.date + let interval = this.displayDuration + let toDisplay = this.messages.filter(m => { + return now - m.date <= interval + }) + return _.reverse(toDisplay).slice(0, 5) + } + }, + methods: { + setupInterval () { + if (this.interval) { + return + } + let self = this + this.interval = setInterval(() => { + if (self.displayedMessages.length === 0) { + clearInterval(self.interval) + this.interval = null + } + self.date = new Date() + }, 1000) + }, + getLevel (message) { + return message.level || 'blue' + } + }, + watch: { + messages: { + handler (v) { + if (v.length > 0 && !this.interval) { + this.setupInterval() + } + }, + deep: true + } + } +} +</script> + +<style> +.service-messages { + z-index: 9999; + margin-left: 1em; + min-width: 20em; + max-width: 40em; +} +.service-messages .message:last-child { + margin-bottom: 0; +} +</style> diff --git a/front/src/components/common/Message.vue b/front/src/components/common/Message.vue new file mode 100644 index 000000000..772071db7 --- /dev/null +++ b/front/src/components/common/Message.vue @@ -0,0 +1,36 @@ +<template> + <div class="ui message"> + <div class="content"> + <slot></slot> + </div> + <i class="close icon"></i> + </div> +</template> +<script> +import $ from 'jquery' + +export default { + mounted () { + let self = this + $(this.$el).find('.close.icon').on('click', function () { + $(self.$el).transition('fade', 125) + }) + $(this.$el).on('click', function () { + $(self.$el).transition('fade', 125) + }) + } +} +</script> +<style scoped> +.ui.message .content { + padding-right: 0.5em; + cursor: pointer; +} +.ui.message .content :first-child { + margin-top: 0; +} + +.ui.message .content :last-child { + margin-bottom: 0; +} +</style> diff --git a/front/src/components/globals.js b/front/src/components/globals.js index 79bbcf1b9..4ad09f704 100644 --- a/front/src/components/globals.js +++ b/front/src/components/globals.js @@ -12,4 +12,8 @@ import DangerousButton from '@/components/common/DangerousButton' Vue.component('dangerous-button', DangerousButton) +import Message from '@/components/common/Message' + +Vue.component('message', Message) + export default {} diff --git a/front/src/store/ui.js b/front/src/store/ui.js index f0935e491..be744afe5 100644 --- a/front/src/store/ui.js +++ b/front/src/store/ui.js @@ -2,11 +2,20 @@ export default { namespaced: true, state: { - lastDate: new Date() + lastDate: new Date(), + maxMessages: 100, + messageDisplayDuration: 10000, + messages: [] }, mutations: { computeLastDate: (state) => { state.lastDate = new Date() + }, + addMessage (state, message) { + state.messages.push(message) + if (state.messages.length > state.maxMessages) { + state.messages.shift() + } } } } diff --git a/front/test/unit/specs/store/ui.spec.js b/front/test/unit/specs/store/ui.spec.js new file mode 100644 index 000000000..adcfa87d8 --- /dev/null +++ b/front/test/unit/specs/store/ui.spec.js @@ -0,0 +1,18 @@ +import store from '@/store/ui' + +import { testAction } from '../../utils' + +describe('store/ui', () => { + describe('mutations', () => { + it('addMessage', () => { + const state = {maxMessages: 100, messages: []} + store.mutations.addMessage(state, 'hello') + expect(state.messages).to.deep.equal(['hello']) + }) + it('addMessage', () => { + const state = {maxMessages: 1, messages: ['hello']} + store.mutations.addMessage(state, 'world') + expect(state.messages).to.deep.equal(['world']) + }) + }) +}) -- GitLab