show an animation to signify that the page is still loading
This commit is contained in:
parent
767d34f8b7
commit
3a8fa8cdcf
4 changed files with 306 additions and 161 deletions
133
web/src/components/AsyncLoader.vue
Normal file
133
web/src/components/AsyncLoader.vue
Normal file
|
@ -0,0 +1,133 @@
|
||||||
|
<template>
|
||||||
|
<div class="async-wrapper" :class="{ 'loaded': loaded }">
|
||||||
|
<div class="deferred">
|
||||||
|
<slot></slot>
|
||||||
|
</div>
|
||||||
|
<div class="loader-wrapper">
|
||||||
|
<div class="loader-ellipsis">
|
||||||
|
<div></div>
|
||||||
|
<div></div>
|
||||||
|
<div></div>
|
||||||
|
<div></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'AsyncLoader',
|
||||||
|
props: {
|
||||||
|
loaded: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.async-wrapper {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.async-wrapper > .deferred {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.async-wrapper.loaded > .deferred {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.async-wrapper > .loader-wrapper {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.async-wrapper > .loader-wrapper > .loader-ellipsis {
|
||||||
|
color: #17a2b8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.async-wrapper.loaded > .loader-wrapper {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.async-wrapper > .loader-wrapper > .loader-ellipsis,
|
||||||
|
.async-wrapper > .loader-wrapper > .loader-ellipsis div {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.async-wrapper > .loader-wrapper > .loader-ellipsis {
|
||||||
|
display: inline-block;
|
||||||
|
position: relative;
|
||||||
|
width: 80px;
|
||||||
|
height: 80px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.async-wrapper > .loader-wrapper > .loader-ellipsis div {
|
||||||
|
position: absolute;
|
||||||
|
top: 33.33333px;
|
||||||
|
width: 13.33333px;
|
||||||
|
height: 13.33333px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: currentColor;
|
||||||
|
animation-timing-function: cubic-bezier(0, 1, 1, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.async-wrapper > .loader-wrapper > .loader-ellipsis div:nth-child(1) {
|
||||||
|
left: 8px;
|
||||||
|
animation: loader-ellipsis1 0.6s infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.async-wrapper > .loader-wrapper > .loader-ellipsis div:nth-child(2) {
|
||||||
|
left: 8px;
|
||||||
|
animation: loader-ellipsis2 0.6s infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.async-wrapper > .loader-wrapper > .loader-ellipsis div:nth-child(3) {
|
||||||
|
left: 32px;
|
||||||
|
animation: loader-ellipsis2 0.6s infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.async-wrapper > .loader-wrapper > .loader-ellipsis div:nth-child(4) {
|
||||||
|
left: 56px;
|
||||||
|
animation: loader-ellipsis3 0.6s infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes loader-ellipsis1 {
|
||||||
|
0% {
|
||||||
|
transform: scale(0);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: scale(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes loader-ellipsis3 {
|
||||||
|
0% {
|
||||||
|
transform: scale(1);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: scale(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes loader-ellipsis2 {
|
||||||
|
0% {
|
||||||
|
transform: translate(0, 0);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: translate(24px, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -1,4 +1,5 @@
|
||||||
<template>
|
<template>
|
||||||
|
<AsyncLoader :loaded="loadedItems.length > 0">
|
||||||
<div class="container-fluid px-xl-5 mt-3">
|
<div class="container-fluid px-xl-5 mt-3">
|
||||||
<Modal title="Edit Item" v-if="editingItem" @close="closeEditingModal()">
|
<Modal title="Edit Item" v-if="editingItem" @close="closeEditingModal()">
|
||||||
<template #body>
|
<template #body>
|
||||||
|
@ -24,7 +25,8 @@
|
||||||
<template #actions="{ item }">
|
<template #actions="{ item }">
|
||||||
<div class="btn-group">
|
<div class="btn-group">
|
||||||
<button class="btn btn-success"
|
<button class="btn btn-success"
|
||||||
@click.stop="confirm('return Item?') && markItemReturned(item)" title="returned">
|
@click.stop="confirm('return Item?') && markItemReturned(item)"
|
||||||
|
title="returned">
|
||||||
<font-awesome-icon icon="check"/>
|
<font-awesome-icon icon="check"/>
|
||||||
</button>
|
</button>
|
||||||
<button class="btn btn-secondary" @click.stop="openEditingModalWith(item)" title="edit">
|
<button class="btn btn-secondary" @click.stop="openEditingModalWith(item)" title="edit">
|
||||||
|
@ -60,10 +62,12 @@
|
||||||
@click.stop="confirm('return Item?') && markItemReturned(item)" title="returned">
|
@click.stop="confirm('return Item?') && markItemReturned(item)" title="returned">
|
||||||
<font-awesome-icon icon="check"/>
|
<font-awesome-icon icon="check"/>
|
||||||
</button>
|
</button>
|
||||||
<button class="btn btn-outline-secondary" @click.stop="openEditingModalWith(item)" title="edit">
|
<button class="btn btn-outline-secondary" @click.stop="openEditingModalWith(item)"
|
||||||
|
title="edit">
|
||||||
<font-awesome-icon icon="edit"/>
|
<font-awesome-icon icon="edit"/>
|
||||||
</button>
|
</button>
|
||||||
<button class="btn btn-outline-danger" @click.stop="confirm('delete Item?') && deleteItem(item)"
|
<button class="btn btn-outline-danger"
|
||||||
|
@click.stop="confirm('delete Item?') && deleteItem(item)"
|
||||||
title="delete">
|
title="delete">
|
||||||
<font-awesome-icon icon="trash"/>
|
<font-awesome-icon icon="trash"/>
|
||||||
</button>
|
</button>
|
||||||
|
@ -72,6 +76,7 @@
|
||||||
</div>
|
</div>
|
||||||
</Cards>
|
</Cards>
|
||||||
</div>
|
</div>
|
||||||
|
</AsyncLoader>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
@ -82,6 +87,7 @@ import EditItem from '@/components/EditItem';
|
||||||
import {mapActions, mapGetters, mapState} from 'vuex';
|
import {mapActions, mapGetters, mapState} from 'vuex';
|
||||||
import Lightbox from '../components/Lightbox';
|
import Lightbox from '../components/Lightbox';
|
||||||
import AuthenticatedImage from "@/components/AuthenticatedImage.vue";
|
import AuthenticatedImage from "@/components/AuthenticatedImage.vue";
|
||||||
|
import AsyncLoader from "@/components/AsyncLoader.vue";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'Items',
|
name: 'Items',
|
||||||
|
@ -89,7 +95,7 @@ export default {
|
||||||
lightboxHash: null,
|
lightboxHash: null,
|
||||||
editingItem: null,
|
editingItem: null,
|
||||||
}),
|
}),
|
||||||
components: {AuthenticatedImage, Lightbox, Table, Cards, Modal, EditItem},
|
components: {AsyncLoader, AuthenticatedImage, Lightbox, Table, Cards, Modal, EditItem},
|
||||||
computed: {
|
computed: {
|
||||||
...mapState(['loadedItems']),
|
...mapState(['loadedItems']),
|
||||||
...mapGetters(['layout']),
|
...mapGetters(['layout']),
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
<template>
|
<template>
|
||||||
|
<AsyncLoader :loaded="ticket.id">
|
||||||
<div class="container-fluid px-xl-5 mt-3">
|
<div class="container-fluid px-xl-5 mt-3">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-xl-8 offset-xl-2">
|
<div class="col-xl-8 offset-xl-2">
|
||||||
|
@ -59,16 +60,18 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</AsyncLoader>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import {mapActions, mapGetters, mapState} from 'vuex';
|
import {mapActions, mapGetters, mapState} from 'vuex';
|
||||||
import Timeline from "@/components/Timeline.vue";
|
import Timeline from "@/components/Timeline.vue";
|
||||||
import ClipboardButton from "@/components/inputs/ClipboardButton.vue";
|
import ClipboardButton from "@/components/inputs/ClipboardButton.vue";
|
||||||
|
import AsyncLoader from "@/components/AsyncLoader.vue";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'Ticket',
|
name: 'Ticket',
|
||||||
components: {ClipboardButton, Timeline},
|
components: {AsyncLoader, ClipboardButton, Timeline},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
selected_state: null,
|
selected_state: null,
|
||||||
|
|
|
@ -1,14 +1,15 @@
|
||||||
<template>
|
<template>
|
||||||
|
<AsyncLoader :loaded="tickets.length > 0">
|
||||||
<div class="container-fluid px-xl-5 mt-3">
|
<div class="container-fluid px-xl-5 mt-3">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-xl-8 offset-xl-2">
|
<div class="col-xl-8 offset-xl-2">
|
||||||
<Table
|
<Table
|
||||||
:columns="['id', 'name', 'state', 'last_activity', 'assigned_to']"
|
:columns="['id', 'name', 'state', 'last_activity', 'assigned_to', 'actions', 'actions2']"
|
||||||
:items="tickets"
|
:items="tickets.map(formatTicket)"
|
||||||
:keyName="'id'"
|
:keyName="'id'"
|
||||||
v-if="layout === 'table'"
|
v-if="layout === 'table'"
|
||||||
>
|
>
|
||||||
<template #actions="{ item }">
|
<template v-slot:actions="{item}">
|
||||||
<div class="btn-group">
|
<div class="btn-group">
|
||||||
<a class="btn btn-primary" :href="'/'+ getEventSlug + '/ticket/' + item.id" title="view"
|
<a class="btn btn-primary" :href="'/'+ getEventSlug + '/ticket/' + item.id" title="view"
|
||||||
@click.prevent="gotoDetail(item)">
|
@click.prevent="gotoDetail(item)">
|
||||||
|
@ -46,6 +47,7 @@
|
||||||
</template>
|
</template>
|
||||||
</CollapsableCards>
|
</CollapsableCards>
|
||||||
</div>
|
</div>
|
||||||
|
</AsyncLoader>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
@ -56,10 +58,11 @@ import {mapActions, mapGetters, mapState} from 'vuex';
|
||||||
import Lightbox from '../components/Lightbox';
|
import Lightbox from '../components/Lightbox';
|
||||||
import Table from '@/components/Table';
|
import Table from '@/components/Table';
|
||||||
import CollapsableCards from "@/components/CollapsableCards.vue";
|
import CollapsableCards from "@/components/CollapsableCards.vue";
|
||||||
|
import AsyncLoader from "@/components/AsyncLoader.vue";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'Tickets',
|
name: 'Tickets',
|
||||||
components: {Lightbox, Table, Cards, Modal, EditItem, CollapsableCards},
|
components: {AsyncLoader, Lightbox, Table, Cards, Modal, EditItem, CollapsableCards},
|
||||||
computed: {
|
computed: {
|
||||||
...mapState(['tickets']),
|
...mapState(['tickets']),
|
||||||
...mapGetters(['stateInfo', 'getEventSlug', 'layout']),
|
...mapGetters(['stateInfo', 'getEventSlug', 'layout']),
|
||||||
|
|
Loading…
Reference in a new issue