Reworked log data format, overhauled snackbar
This commit is contained in:
16
app/imports/ui/components/snackbars/SnackbarQueue.js
Normal file
16
app/imports/ui/components/snackbars/SnackbarQueue.js
Normal file
@@ -0,0 +1,16 @@
|
||||
// Modified from https://gitlab.com/tozd/vue/snackbar-que
|
||||
import Vue from 'vue';
|
||||
|
||||
const globalState = Vue.observable({queue: []});
|
||||
let lastSnackbarId = 0;
|
||||
|
||||
function snackbar(data) {
|
||||
globalState.queue.push({
|
||||
data,
|
||||
id: ++lastSnackbarId,
|
||||
enqueuedAt: new Date(),
|
||||
shown: false,
|
||||
});
|
||||
}
|
||||
|
||||
export {snackbar, globalState}
|
||||
128
app/imports/ui/components/snackbars/SnackbarQueue.vue
Normal file
128
app/imports/ui/components/snackbars/SnackbarQueue.vue
Normal file
@@ -0,0 +1,128 @@
|
||||
<template lang="html">
|
||||
<v-snackbar
|
||||
bottom
|
||||
left
|
||||
v-bind="$attrs"
|
||||
:value="isShown"
|
||||
:timeout="timeout"
|
||||
@input="value => isShown = value"
|
||||
>
|
||||
<div class="layout align-center">
|
||||
<template v-if="snackbar && snackbar.data">
|
||||
<div v-if="snackbar.data.text">
|
||||
{{ snackbar.data.text }}
|
||||
</div>
|
||||
<template v-else-if="snackbar.data.content">
|
||||
<log-content :model="snackbar.data.content" />
|
||||
</template>
|
||||
<v-spacer />
|
||||
<v-btn
|
||||
v-if="snackbar.data.callback"
|
||||
color="primary"
|
||||
text
|
||||
@click="closeSnackbar(); snackbar.data.callback()"
|
||||
>
|
||||
{{ snackbar.data.callbackName }}
|
||||
</v-btn>
|
||||
</template>
|
||||
</div>
|
||||
<template #action="{ attrs }">
|
||||
<v-btn
|
||||
icon
|
||||
v-bind="attrs"
|
||||
@click="closeSnackbar"
|
||||
>
|
||||
<v-icon>close</v-icon>
|
||||
</v-btn>
|
||||
</template>
|
||||
</v-snackbar>
|
||||
</template>
|
||||
|
||||
<script lang="js">
|
||||
// Modified from https://gitlab.com/tozd/vue/snackbar-queue
|
||||
import { globalState } from '/imports/ui/components/snackbars/SnackbarQueue.js';
|
||||
import LogContent from '/imports/ui/log/LogContent.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
LogContent,
|
||||
},
|
||||
props: {
|
||||
timeout: {
|
||||
type: Number,
|
||||
default: 6000,
|
||||
},
|
||||
pause: {
|
||||
type: Number,
|
||||
default: 300,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
isShown: false,
|
||||
snackbar: null,
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
isShown(newValue) {
|
||||
if (newValue === false && this.snackbar) {
|
||||
const snackbarIndex = globalState.queue.findIndex((element) => element.id === this.snackbar.id);
|
||||
if (snackbarIndex > -1) {
|
||||
globalState.queue.splice(snackbarIndex, 1);
|
||||
}
|
||||
this.snackbar = null;
|
||||
}
|
||||
},
|
||||
},
|
||||
created() {
|
||||
this.handle = null;
|
||||
this.unwait = null;
|
||||
this.showNextSnackbar();
|
||||
},
|
||||
methods: {
|
||||
clearSnackbarState() {
|
||||
if (this.handle) {
|
||||
clearTimeout(this.handle);
|
||||
this.handle = null;
|
||||
}
|
||||
|
||||
if (this.unwait) {
|
||||
this.unwait();
|
||||
this.unwait = null;
|
||||
}
|
||||
},
|
||||
showNextSnackbar() {
|
||||
this.clearSnackbarState();
|
||||
|
||||
// Wait for the first next snackbar to be available.
|
||||
this.unwait = this.$wait(function () {
|
||||
// Snackbars are enqueued from oldest to newest and "find" searches array elements in
|
||||
// same order as well, so the first one which matches is also the oldest one.
|
||||
return globalState.queue.find((element) => element.shown === false);
|
||||
}, function (snackbar) {
|
||||
this.unwait = null;
|
||||
|
||||
snackbar.shown = true;
|
||||
|
||||
this.snackbar = snackbar;
|
||||
this.isShown = true;
|
||||
|
||||
this.handle = setTimeout(() => {
|
||||
this.handle = null;
|
||||
|
||||
this.showNextSnackbar();
|
||||
}, this.timeout + this.pause);
|
||||
});
|
||||
},
|
||||
closeSnackbar() {
|
||||
this.clearSnackbarState();
|
||||
|
||||
this.isShown = false;
|
||||
|
||||
setTimeout(() => {
|
||||
this.showNextSnackbar();
|
||||
}, this.pause);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -1,46 +0,0 @@
|
||||
<template lang="html">
|
||||
<v-snackbar
|
||||
v-if="snackbar"
|
||||
:key="snackbar.text"
|
||||
auto-height
|
||||
bottom
|
||||
:value="true"
|
||||
:timeout="0"
|
||||
>
|
||||
{{ snackbar.text }}
|
||||
<v-btn
|
||||
v-if="snackbar.callback"
|
||||
class="primary--text"
|
||||
icon
|
||||
@click="doCallback"
|
||||
>
|
||||
{{ snackbar.callbackName }}
|
||||
</v-btn>
|
||||
<v-btn
|
||||
v-if="snackbar.showCloseButton"
|
||||
icon
|
||||
@click="$store.dispatch('closeSnackbar')"
|
||||
>
|
||||
<v-icon>close</v-icon>
|
||||
</v-btn>
|
||||
</v-snackbar>
|
||||
</template>
|
||||
|
||||
<script lang="js">
|
||||
export default {
|
||||
computed: {
|
||||
snackbar(){
|
||||
return this.$store.state.snackbars.snackbars[0];
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
doCallback(){
|
||||
this.snackbar.callback();
|
||||
this.$store.dispatch('closeSnackbar')
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="css" scoped>
|
||||
</style>
|
||||
@@ -1,47 +0,0 @@
|
||||
const snackbarStore = {
|
||||
state: {
|
||||
snackbars: [],
|
||||
snackbarTimout: undefined,
|
||||
},
|
||||
mutations: {
|
||||
addSnackbar(state, value){
|
||||
state.snackbars.push(value)
|
||||
},
|
||||
closeCurrentSnackbar (state){
|
||||
state.snackbars.shift();
|
||||
},
|
||||
cancelSnackbarTimeout (state){
|
||||
if(state.snackbarTimout){
|
||||
clearTimeout(state.snackbarTimout);
|
||||
}
|
||||
},
|
||||
setSnackbarTimout(state, value){
|
||||
state.snackbarTimout = value;
|
||||
},
|
||||
},
|
||||
actions: {
|
||||
snackbar({dispatch, commit}, value){
|
||||
// value = {
|
||||
// text,
|
||||
// showCloseButton,
|
||||
// callback,
|
||||
// callbackName
|
||||
// }
|
||||
commit('addSnackbar', value);
|
||||
commit('setSnackbarTimout', setTimeout(() => {
|
||||
dispatch('closeSnackbar');
|
||||
}, 5000));
|
||||
},
|
||||
closeSnackbar({dispatch, commit, state}){
|
||||
commit('closeCurrentSnackbar');
|
||||
commit('cancelSnackbarTimeout');
|
||||
if (state.snackbars.length){
|
||||
commit('setSnackbarTimout', setTimeout(() => {
|
||||
dispatch('closeSnackbar');
|
||||
}, 5000));
|
||||
}
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
export default snackbarStore;
|
||||
@@ -78,6 +78,7 @@
|
||||
import TreeTab from '/imports/ui/creature/character/characterSheetTabs/TreeTab.vue';
|
||||
import { assertEditPermission } from '/imports/api/creature/creaturePermissions.js';
|
||||
import CreatureLogs from '/imports/api/creature/log/CreatureLogs.js';
|
||||
import { snackbar } from '/imports/ui/components/snackbars/SnackbarQueue.js';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
@@ -119,13 +120,10 @@
|
||||
this.logObserver = CreatureLogs.find({
|
||||
creatureId: this.creatureId,
|
||||
}).observe({
|
||||
added(doc){
|
||||
added({content}){
|
||||
if (!that.$subReady.singleCharacter) return;
|
||||
if (that.$store.state.rightDrawer) return;
|
||||
that.$store.dispatch('snackbar', {
|
||||
text: doc.name,
|
||||
showCloseButton: true,
|
||||
});
|
||||
snackbar({content});
|
||||
},
|
||||
});
|
||||
},
|
||||
|
||||
@@ -80,6 +80,7 @@ import getPropertyTitle from '/imports/ui/properties/shared/getPropertyTitle.js'
|
||||
import { assertEditPermission } from '/imports/api/creature/creaturePermissions.js';
|
||||
import { get, findLast } from 'lodash';
|
||||
import equipItem from '/imports/api/creature/creatureProperties/methods/equipItem.js';
|
||||
import { snackbar } from '/imports/ui/components/snackbars/SnackbarQueue.js';
|
||||
|
||||
let formIndex = {};
|
||||
for (let key in propertyFormIndex){
|
||||
@@ -194,7 +195,7 @@ export default {
|
||||
} else {
|
||||
this.$store.dispatch('popDialogStack');
|
||||
}
|
||||
this.$store.dispatch('snackbar', {
|
||||
snackbar({
|
||||
text: `Deleted ${getPropertyTitle(this.model)}`,
|
||||
callbackName: 'undo',
|
||||
callback(){
|
||||
|
||||
@@ -57,6 +57,7 @@ import softRemoveProperty from '/imports/api/creature/creatureProperties/methods
|
||||
import restoreProperty from '/imports/api/creature/creatureProperties/methods/restoreProperty.js';
|
||||
import getPropertyTitle from '/imports/ui/properties/shared/getPropertyTitle.js';
|
||||
import insertPropertyFromLibraryNode from '/imports/api/creature/creatureProperties/methods/insertPropertyFromLibraryNode.js';
|
||||
import { snackbar } from '/imports/ui/components/snackbars/SnackbarQueue.js';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
@@ -104,7 +105,7 @@ export default {
|
||||
},
|
||||
remove(model){
|
||||
softRemoveProperty.call({_id: model._id});
|
||||
this.$store.dispatch('snackbar', {
|
||||
snackbar({
|
||||
text: `Deleted ${getPropertyTitle(model)}`,
|
||||
callbackName: 'undo',
|
||||
callback(){
|
||||
|
||||
@@ -65,7 +65,7 @@
|
||||
name="rightDrawer"
|
||||
/>
|
||||
<dialog-stack />
|
||||
<snackbars />
|
||||
<snackbar-queue />
|
||||
</v-app>
|
||||
</template>
|
||||
|
||||
@@ -74,13 +74,13 @@
|
||||
import Sidebar from '/imports/ui/layouts/Sidebar.vue';
|
||||
import DialogStack from '/imports/ui/dialogStack/DialogStack.vue';
|
||||
import { mapMutations } from 'vuex';
|
||||
import Snackbars from '/imports/ui/components/snackbars/Snackbars.vue';
|
||||
import SnackbarQueue from '/imports/ui/components/snackbars/SnackbarQueue.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
Sidebar,
|
||||
DialogStack,
|
||||
Snackbars,
|
||||
SnackbarQueue,
|
||||
},
|
||||
data(){return {
|
||||
name: 'Home',
|
||||
|
||||
51
app/imports/ui/log/LogContent.vue
Normal file
51
app/imports/ui/log/LogContent.vue
Normal file
@@ -0,0 +1,51 @@
|
||||
<template lang="html">
|
||||
<div class="log-content">
|
||||
<div
|
||||
v-for="(content, index) in model"
|
||||
:key="index"
|
||||
class="content-line"
|
||||
>
|
||||
<h4 class="content-name">
|
||||
{{ content.name }}
|
||||
</h4>
|
||||
<markdown-text
|
||||
v-if="content.value"
|
||||
class="content-value"
|
||||
:markdown="content.value"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="js">
|
||||
import MarkdownText from '/imports/ui/components/MarkdownText.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
MarkdownText,
|
||||
},
|
||||
props: {
|
||||
model: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="css" scoped>
|
||||
.content-line {
|
||||
min-height: 24px;
|
||||
margin-top: 2px;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
.content-line .details {
|
||||
display: inline-block;
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="css">
|
||||
.log-content .content-value > p:last-of-type{
|
||||
margin-bottom: 0;
|
||||
}
|
||||
</style>
|
||||
@@ -6,42 +6,17 @@
|
||||
v-if="model.text || (model.content && model.content.length)"
|
||||
class="pa-2"
|
||||
>
|
||||
<div
|
||||
v-for="(content, index) in model.content"
|
||||
:key="index"
|
||||
class="content-line"
|
||||
>
|
||||
<h4>
|
||||
{{ content.name }}
|
||||
</h4>
|
||||
<span
|
||||
v-if="content.error"
|
||||
class="error"
|
||||
>{{ content.error }}</span>
|
||||
{{ content.resultPrefix }}
|
||||
<span
|
||||
v-if="content.result"
|
||||
class="subheading font-weight-bold mx-1"
|
||||
>{{ content.result }}</span>
|
||||
<span
|
||||
v-if="content.details"
|
||||
>{{ content.details }}</span>
|
||||
<markdown-text
|
||||
v-if="content.description"
|
||||
class="description"
|
||||
:markdown="content.description"
|
||||
/>
|
||||
</div>
|
||||
<log-content :model="model.content" />
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</template>
|
||||
|
||||
<script lang="js">
|
||||
import MarkdownText from '/imports/ui/components/MarkdownText.vue';
|
||||
import LogContent from '/imports/ui/log/LogContent.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
MarkdownText,
|
||||
LogContent,
|
||||
},
|
||||
props: {
|
||||
model: {
|
||||
@@ -51,20 +26,3 @@ export default {
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="css" scoped>
|
||||
.content-line {
|
||||
min-height: 24px;
|
||||
margin-top: 2px;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
.content-line .details {
|
||||
display: inline-block;
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="css">
|
||||
.log-entry .description > p:last-of-type{
|
||||
margin-bottom: 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -3,6 +3,7 @@ import store from '/imports/ui/vuexStore.js';
|
||||
import VueMeteorTracker from 'vue-meteor-tracker';
|
||||
import AppLayout from '/imports/ui/layouts/AppLayout.vue';
|
||||
import ReactiveProvide from 'vue-reactive-provide';
|
||||
import VueObserverUtils from '@tozd/vue-observer-utils';
|
||||
import router from '/imports/ui/router.js';
|
||||
import themes from '/imports/ui/themes.js';
|
||||
import '/imports/ui/components/global/globalIndex.js';
|
||||
@@ -35,7 +36,8 @@ Vue.use(Vuetify, {
|
||||
});
|
||||
Vue.use(ReactiveProvide, {
|
||||
name: 'reactiveProvide', // default value
|
||||
})
|
||||
});
|
||||
Vue.use(VueObserverUtils);
|
||||
|
||||
// App start
|
||||
Meteor.startup(() => {
|
||||
|
||||
@@ -1,14 +1,12 @@
|
||||
import Vue from 'vue';
|
||||
import Vuex from 'vuex';
|
||||
import dialogStackStore from '/imports/ui/dialogStack/dialogStackStore.js';
|
||||
import snackbarStore from '/imports/ui/components/snackbars/snackboxStore.js';
|
||||
|
||||
Vue.use(Vuex);
|
||||
const store = new Vuex.Store({
|
||||
strict: process.env.NODE_ENV !== 'production',
|
||||
modules: {
|
||||
dialogStack: dialogStackStore,
|
||||
snackbars: snackbarStore,
|
||||
},
|
||||
state: {
|
||||
drawer: undefined,
|
||||
|
||||
Reference in New Issue
Block a user