feat: loading spinner on views navigation

pull/1373/head
Ramires Viana 2021-04-16 12:47:50 +00:00
parent b92152693f
commit 976eb5583d
13 changed files with 185 additions and 77 deletions

View File

@ -77,7 +77,7 @@
opacity: 0; opacity: 0;
} }
.spinner { #loading .spinner {
width: 70px; width: 70px;
text-align: center; text-align: center;
position: fixed; position: fixed;
@ -87,7 +87,7 @@
transform: translate(-50%, -50%); transform: translate(-50%, -50%);
} }
.spinner > div { #loading .spinner > div {
width: 18px; width: 18px;
height: 18px; height: 18px;
background-color: #333; background-color: #333;
@ -97,12 +97,12 @@
animation: sk-bouncedelay 1.4s infinite ease-in-out both; animation: sk-bouncedelay 1.4s infinite ease-in-out both;
} }
.spinner .bounce1 { #loading .spinner .bounce1 {
-webkit-animation-delay: -0.32s; -webkit-animation-delay: -0.32s;
animation-delay: -0.32s; animation-delay: -0.32s;
} }
.spinner .bounce2 { #loading .spinner .bounce2 {
-webkit-animation-delay: -0.16s; -webkit-animation-delay: -0.16s;
animation-delay: -0.16s; animation-delay: -0.16s;
} }

View File

@ -16,7 +16,7 @@ body {
#loading { #loading {
background: var(--background); background: var(--background);
} }
#loading .spinner div, #previewer .loading .spinner div { #loading .spinner div, main .spinner div {
background: var(--icon); background: var(--icon);
} }

View File

@ -17,6 +17,48 @@
color: var(--blue); color: var(--blue);
} }
main .spinner {
display: block;
text-align: center;
line-height: 0;
padding: 1em 0;
}
main .spinner > div {
width: .8em;
height: .8em;
margin: 0 .1em;
font-size: 1em;
background-color: rgba(0, 0, 0, 0.3);
border-radius: 100%;
display: inline-block;
animation: sk-bouncedelay 1.4s infinite ease-in-out both;
}
main .spinner .bounce1 {
animation-delay: -0.32s;
}
main .spinner .bounce2 {
animation-delay: -0.16s;
}
.delayed {
animation: delayed linear 100ms;
}
@keyframes delayed {
0% {
opacity: 0;
}
99% {
opacity: 0;
}
100% {
opacity: 1;
}
}
/* * * * * * * * * * * * * * * * /* * * * * * * * * * * * * * * *
* ACTION * * ACTION *
* * * * * * * * * * * * * * * */ * * * * * * * * * * * * * * * */
@ -204,6 +246,20 @@
right: 0.5em; right: 0.5em;
} }
#previewer .spinner {
text-align: center;
position: fixed;
top: calc(50% + 1.85em);
left: 50%;
transform: translate(-50%, -50%);
}
#previewer .spinner > div {
width: 18px;
height: 18px;
background-color: white;
}
/* EDITOR */ /* EDITOR */
#editor-container { #editor-container {
@ -217,11 +273,6 @@
overflow: hidden; overflow: hidden;
} }
#previewer .loading {
height: 100%;
width: 100%;
}
#editor-container #editor { #editor-container #editor {
height: calc(100vh - 8.4em); height: calc(100vh - 8.4em);
} }
@ -283,7 +334,6 @@
@keyframes spin { @keyframes spin {
100% { 100% {
-webkit-transform: rotate(-360deg);
transform: rotate(-360deg); transform: rotate(-360deg);
} }
} }

View File

@ -7,7 +7,12 @@
<errors v-if="error" :errorCode="errorCode" /> <errors v-if="error" :errorCode="errorCode" />
<component v-else-if="currentView" :is="currentView"></component> <component v-else-if="currentView" :is="currentView"></component>
<div v-else> <div v-else>
<h2 class="message"> <h2 class="message delayed">
<div class="spinner">
<div class="bounce1"></div>
<div class="bounce2"></div>
<div class="bounce3"></div>
</div>
<span>{{ $t("files.loading") }}</span> <span>{{ $t("files.loading") }}</span>
</h2> </h2>
</div> </div>

View File

@ -34,6 +34,17 @@
</div> </div>
</div> </div>
<div v-if="loading">
<h2 class="message delayed">
<div class="spinner">
<div class="bounce1"></div>
<div class="bounce2"></div>
<div class="bounce3"></div>
</div>
<span>{{ $t("files.loading") }}</span>
</h2>
</div>
<router-view></router-view> <router-view></router-view>
</div> </div>
</template> </template>
@ -49,7 +60,7 @@ export default {
HeaderBar, HeaderBar,
}, },
computed: { computed: {
...mapState(["user"]), ...mapState(["user", "loading"]),
}, },
}; };
</script> </script>

View File

@ -19,7 +19,50 @@
<breadcrumbs :base="'/share/' + hash" /> <breadcrumbs :base="'/share/' + hash" />
<div v-if="!loading"> <div v-if="loading">
<h2 class="message delayed">
<div class="spinner">
<div class="bounce1"></div>
<div class="bounce2"></div>
<div class="bounce3"></div>
</div>
<span>{{ $t("files.loading") }}</span>
</h2>
</div>
<div v-else-if="error">
<div v-if="error.message === '401'">
<div class="card floating" id="password">
<div v-if="attemptedPasswordLogin" class="share__wrong__password">
{{ $t("login.wrongCredentials") }}
</div>
<div class="card-title">
<h2>{{ $t("login.password") }}</h2>
</div>
<div class="card-content">
<input
v-focus
type="password"
:placeholder="$t('login.password')"
v-model="password"
@keyup.enter="fetchData"
/>
</div>
<div class="card-action">
<button
class="button button--flat"
@click="fetchData"
:aria-label="$t('buttons.submit')"
:title="$t('buttons.submit')"
>
{{ $t("buttons.submit") }}
</button>
</div>
</div>
</div>
<errors v-else :errorCode="errorCode" />
</div>
<div v-else>
<div class="share"> <div class="share">
<div class="share__box share__box__info"> <div class="share__box share__box__info">
<div class="share__box__header"> <div class="share__box__header">
@ -106,39 +149,6 @@
</div> </div>
</div> </div>
</div> </div>
<div v-if="error">
<div v-if="error.message === '401'">
<div class="card floating" id="password">
<div v-if="attemptedPasswordLogin" class="share__wrong__password">
{{ $t("login.wrongCredentials") }}
</div>
<div class="card-title">
<h2>{{ $t("login.password") }}</h2>
</div>
<div class="card-content">
<input
v-focus
type="password"
:placeholder="$t('login.password')"
v-model="password"
@keyup.enter="fetchData"
/>
</div>
<div class="card-action">
<button
class="button button--flat"
@click="fetchData"
:aria-label="$t('buttons.submit')"
:title="$t('buttons.submit')"
>
{{ $t("buttons.submit") }}
</button>
</div>
</div>
</div>
<errors v-else :errorCode="errorCode" />
</div>
</div> </div>
</template> </template>
@ -256,9 +266,10 @@ export default {
this.token = file.token || ""; this.token = file.token || "";
this.updateRequest(file); this.updateRequest(file);
this.setLoading(false);
} catch (e) { } catch (e) {
this.error = e; this.error = e;
} finally {
this.setLoading(false);
} }
}, },
keyEvent(event) { keyEvent(event) {

View File

@ -114,8 +114,13 @@
/> />
</div> </div>
<div v-if="$store.state.loading"> <div v-if="loading">
<h2 class="message"> <h2 class="message delayed">
<div class="spinner">
<div class="bounce1"></div>
<div class="bounce2"></div>
<div class="bounce3"></div>
</div>
<span>{{ $t("files.loading") }}</span> <span>{{ $t("files.loading") }}</span>
</h2> </h2>
</div> </div>
@ -284,7 +289,15 @@ export default {
}; };
}, },
computed: { computed: {
...mapState(["req", "selected", "user", "show", "multiple", "selected"]), ...mapState([
"req",
"selected",
"user",
"show",
"multiple",
"selected",
"loading",
]),
...mapGetters(["selectedCount"]), ...mapGetters(["selectedCount"]),
nameSorted() { nameSorted() {
return this.req.sorting.by === "name"; return this.req.sorting.by === "name";
@ -799,17 +812,13 @@ export default {
viewMode: this.user.viewMode === "mosaic" ? "list" : "mosaic", viewMode: this.user.viewMode === "mosaic" ? "list" : "mosaic",
}; };
try { users.update(data, ["viewMode"]).catch(this.$showError);
await users.update(data, ["viewMode"]);
// Await ensures correct value for setItemWeight() // Await ensures correct value for setItemWeight()
await this.$store.commit("updateUser", data); await this.$store.commit("updateUser", data);
this.setItemWeight(); this.setItemWeight();
this.fillWindow(); this.fillWindow();
} catch (e) {
this.$showError(e);
}
}, },
upload: function () { upload: function () {
if ( if (

View File

@ -46,15 +46,14 @@
</template> </template>
</header-bar> </header-bar>
<div class="loading" v-if="loading"> <div class="loading delayed" v-if="loading">
<div class="spinner"> <div class="spinner">
<div class="bounce1"></div> <div class="bounce1"></div>
<div class="bounce2"></div> <div class="bounce2"></div>
<div class="bounce3"></div> <div class="bounce3"></div>
</div> </div>
</div> </div>
<template v-else>
<template v-if="!loading">
<div class="preview"> <div class="preview">
<ExtendedImage v-if="req.type == 'image'" :src="raw"></ExtendedImage> <ExtendedImage v-if="req.type == 'image'" :src="raw"></ExtendedImage>
<audio <audio

View File

@ -1,5 +1,5 @@
<template> <template>
<div class="row" v-if="settings !== null"> <div class="row" v-if="!loading">
<div class="column"> <div class="column">
<form class="card" @submit.prevent="save"> <form class="card" @submit.prevent="save">
<div class="card-title"> <div class="card-title">
@ -170,7 +170,7 @@
</template> </template>
<script> <script>
import { mapState } from "vuex"; import { mapState, mapMutations } from "vuex";
import { settings as api } from "@/api"; import { settings as api } from "@/api";
import UserForm from "@/components/settings/UserForm"; import UserForm from "@/components/settings/UserForm";
import Rules from "@/components/settings/Rules"; import Rules from "@/components/settings/Rules";
@ -191,11 +191,13 @@ export default {
}; };
}, },
computed: { computed: {
...mapState(["user"]), ...mapState(["user", "loading"]),
isExecEnabled: () => enableExec, isExecEnabled: () => enableExec,
}, },
async created() { async created() {
try { try {
this.setLoading(true);
const original = await api.get(); const original = await api.get();
let settings = { ...original, commands: [] }; let settings = { ...original, commands: [] };
@ -210,11 +212,14 @@ export default {
this.originalSettings = original; this.originalSettings = original;
this.settings = settings; this.settings = settings;
this.setLoading(false);
} catch (e) { } catch (e) {
this.$showError(e); this.$showError(e);
} }
}, },
methods: { methods: {
...mapMutations(["setLoading"]),
capitalize(name, where = "_") { capitalize(name, where = "_") {
if (where === "caps") where = /(?=[A-Z])/; if (where === "caps") where = /(?=[A-Z])/;
let splitted = name.split(where); let splitted = name.split(where);

View File

@ -103,12 +103,13 @@ export default {
}, },
}, },
created() { created() {
this.setLoading(false);
this.locale = this.user.locale; this.locale = this.user.locale;
this.hideDotfiles = this.user.hideDotfiles; this.hideDotfiles = this.user.hideDotfiles;
this.singleClick = this.user.singleClick; this.singleClick = this.user.singleClick;
}, },
methods: { methods: {
...mapMutations(["updateUser"]), ...mapMutations(["updateUser", "setLoading"]),
async updatePassword(event) { async updatePassword(event) {
event.preventDefault(); event.preventDefault();

View File

@ -1,5 +1,5 @@
<template> <template>
<div class="row"> <div class="row" v-if="!loading">
<div class="column"> <div class="column">
<div class="card"> <div class="card">
<div class="card-title"> <div class="card-title">
@ -62,11 +62,11 @@ import { share as api, users } from "@/api";
import moment from "moment"; import moment from "moment";
import { baseURL } from "@/utils/constants"; import { baseURL } from "@/utils/constants";
import Clipboard from "clipboard"; import Clipboard from "clipboard";
import { mapState } from "vuex"; import { mapState, mapMutations } from "vuex";
export default { export default {
name: "shares", name: "shares",
computed: mapState(["user"]), computed: mapState(["user", "loading"]),
data: function () { data: function () {
return { return {
links: [], links: [],
@ -74,6 +74,8 @@ export default {
}; };
}, },
async created() { async created() {
this.setLoading(true);
try { try {
let links = await api.list(); let links = await api.list();
if (this.user.perm.admin) { if (this.user.perm.admin) {
@ -86,6 +88,8 @@ export default {
: ""; : "";
} }
this.links = links; this.links = links;
this.setLoading(false);
} catch (e) { } catch (e) {
this.$showError(e); this.$showError(e);
} }
@ -100,6 +104,7 @@ export default {
this.clip.destroy(); this.clip.destroy();
}, },
methods: { methods: {
...mapMutations(["setLoading"]),
deleteLink: async function (event, link) { deleteLink: async function (event, link) {
event.preventDefault(); event.preventDefault();

View File

@ -1,7 +1,7 @@
<template> <template>
<div class="row"> <div class="row" v-if="!loading">
<div class="column"> <div class="column">
<form v-if="loaded" @submit="save" class="card"> <form @submit="save" class="card">
<div class="card-title"> <div class="card-title">
<h2 v-if="user.id === 0">{{ $t("settings.newUser") }}</h2> <h2 v-if="user.id === 0">{{ $t("settings.newUser") }}</h2>
<h2 v-else>{{ $t("settings.user") }} {{ user.username }}</h2> <h2 v-else>{{ $t("settings.user") }} {{ user.username }}</h2>
@ -55,7 +55,7 @@
</template> </template>
<script> <script>
import { mapMutations } from "vuex"; import { mapState, mapMutations } from "vuex";
import { users as api, settings } from "@/api"; import { users as api, settings } from "@/api";
import UserForm from "@/components/settings/UserForm"; import UserForm from "@/components/settings/UserForm";
import deepClone from "lodash.clonedeep"; import deepClone from "lodash.clonedeep";
@ -69,7 +69,6 @@ export default {
return { return {
originalUser: null, originalUser: null,
user: {}, user: {},
loaded: false,
}; };
}, },
created() { created() {
@ -79,6 +78,7 @@ export default {
isNew() { isNew() {
return this.$route.path === "/settings/users/new"; return this.$route.path === "/settings/users/new";
}, },
...mapState(["loading"]),
}, },
watch: { watch: {
$route: "fetchData", $route: "fetchData",
@ -88,8 +88,10 @@ export default {
}, },
}, },
methods: { methods: {
...mapMutations(["closeHovers", "showHover", "setUser"]), ...mapMutations(["closeHovers", "showHover", "setUser", "setLoading"]),
async fetchData() { async fetchData() {
this.setLoading(true);
try { try {
if (this.isNew) { if (this.isNew) {
let { defaults } = await settings.get(); let { defaults } = await settings.get();
@ -106,7 +108,7 @@ export default {
this.user = { ...(await api.get(id)) }; this.user = { ...(await api.get(id)) };
} }
this.loaded = true; this.setLoading(false);
} catch (e) { } catch (e) {
this.$router.push({ path: "/settings/users/new" }); this.$router.push({ path: "/settings/users/new" });
} }

View File

@ -1,5 +1,5 @@
<template> <template>
<div class="row"> <div class="row" v-if="!loading">
<div class="column"> <div class="column">
<div class="card"> <div class="card">
<div class="card-title"> <div class="card-title">
@ -41,6 +41,7 @@
</template> </template>
<script> <script>
import { mapState, mapMutations } from "vuex";
import { users as api } from "@/api"; import { users as api } from "@/api";
export default { export default {
@ -51,11 +52,20 @@ export default {
}; };
}, },
async created() { async created() {
this.setLoading(true);
try { try {
this.users = await api.getAll(); this.users = await api.getAll();
this.setLoading(false);
} catch (e) { } catch (e) {
this.$showError(e); this.$showError(e);
} }
}, },
computed: {
...mapState(["loading"]),
},
methods: {
...mapMutations(["setLoading"]),
},
}; };
</script> </script>