✨ Replace circular with linear loading indicator
This commit is contained in:
@ -4,7 +4,7 @@ import TitleBar from "./components/titlebar-element.vue";
|
|||||||
import BottomNavbar from "./components/bottom-navbar.vue";
|
import BottomNavbar from "./components/bottom-navbar.vue";
|
||||||
import DateSelector from "./components/date-selector.vue";
|
import DateSelector from "./components/date-selector.vue";
|
||||||
import LoadingElement from "./components/loading-element.vue";
|
import LoadingElement from "./components/loading-element.vue";
|
||||||
import { loading, theme } from "./store";
|
import { loading, loadingProgress, theme } from "./store";
|
||||||
import { computed, ref } from "vue";
|
import { computed, ref } from "vue";
|
||||||
|
|
||||||
const autoThemes = { true: "dark", false: "light" };
|
const autoThemes = { true: "dark", false: "light" };
|
||||||
@ -31,10 +31,10 @@ const isDataView = computed(() => {
|
|||||||
:class="theme == 'auto' ? `theme-${autoTheme}` : `theme-${theme}`"
|
:class="theme == 'auto' ? `theme-${autoTheme}` : `theme-${theme}`"
|
||||||
>
|
>
|
||||||
<TitleBar />
|
<TitleBar />
|
||||||
<DateSelector v-show="isDataView" />
|
<LoadingElement :active="loading" :progress="loadingProgress" />
|
||||||
<div class="center">
|
<div class="center">
|
||||||
<main>
|
<main>
|
||||||
<LoadingElement :active="loading" v-show="isDataView" />
|
<DateSelector v-show="isDataView" />
|
||||||
<RouterView />
|
<RouterView />
|
||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
|
@ -16,6 +16,7 @@
|
|||||||
--bottomnav-active-color: #7ca74b;
|
--bottomnav-active-color: #7ca74b;
|
||||||
--bottomnav-shadow: 5px 7px 19px 0px rgba(0, 0, 0, 0.25);
|
--bottomnav-shadow: 5px 7px 19px 0px rgba(0, 0, 0, 0.25);
|
||||||
--loader-color: #7ca74b;
|
--loader-color: #7ca74b;
|
||||||
|
--loader-error-color: #a54a4a;
|
||||||
--substitution-background-change: #095079;
|
--substitution-background-change: #095079;
|
||||||
--substitution-background-cancellation: #7d2b2d;
|
--substitution-background-cancellation: #7d2b2d;
|
||||||
--substitution-background-addition: #375c1d;
|
--substitution-background-addition: #375c1d;
|
||||||
@ -42,7 +43,8 @@
|
|||||||
--bottomnav-icon-active-color: #456c47;
|
--bottomnav-icon-active-color: #456c47;
|
||||||
--bottomnav-active-color: #99b677;
|
--bottomnav-active-color: #99b677;
|
||||||
--bottomnav-shadow: 5px 7px 19px 0px rgba(0, 0, 0, 0.25);
|
--bottomnav-shadow: 5px 7px 19px 0px rgba(0, 0, 0, 0.25);
|
||||||
--loader-color: #7ca74b;
|
--loader-color: #8bab7c;
|
||||||
|
--loader-error-color: #db5a5a;
|
||||||
--substitution-background-change: #9bb7c7;
|
--substitution-background-change: #9bb7c7;
|
||||||
--substitution-background-cancellation: #e4a7a9;
|
--substitution-background-cancellation: #e4a7a9;
|
||||||
--substitution-background-addition: #b0d396;
|
--substitution-background-addition: #b0d396;
|
||||||
|
@ -26,7 +26,11 @@ const loadedDates = ref([
|
|||||||
|
|
||||||
// Load left or right date on slide change
|
// Load left or right date on slide change
|
||||||
function slideChange(swiper) {
|
function slideChange(swiper) {
|
||||||
selectedDate.value = loadedDates.value[swiper.activeIndex];
|
// Only trigger data refresh if date is different
|
||||||
|
const newSelectedDate = loadedDates.value[swiper.activeIndex];
|
||||||
|
if (selectedDate.value.getTime() != newSelectedDate.getTime())
|
||||||
|
selectedDate.value = newSelectedDate;
|
||||||
|
|
||||||
const activeSlide = swiper.activeIndex;
|
const activeSlide = swiper.activeIndex;
|
||||||
if (activeSlide == loadedDates.value.length - 1) {
|
if (activeSlide == loadedDates.value.length - 1) {
|
||||||
const lastDate = loadedDates.value[loadedDates.value.length - 1];
|
const lastDate = loadedDates.value[loadedDates.value.length - 1];
|
||||||
|
@ -1,54 +1,73 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
defineProps({
|
import { ref, watch } from "vue";
|
||||||
|
const props = defineProps({
|
||||||
active: {
|
active: {
|
||||||
required: true,
|
required: true,
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
},
|
},
|
||||||
|
progress: {
|
||||||
|
default: 0,
|
||||||
|
type: Number,
|
||||||
|
},
|
||||||
|
error: {
|
||||||
|
default: false,
|
||||||
|
type: Boolean,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const computedProgress = ref(0);
|
||||||
|
const visible = ref(true);
|
||||||
|
var hideTimeout;
|
||||||
|
watch(
|
||||||
|
() => props.progress,
|
||||||
|
() => {
|
||||||
|
if (visible.value) computedProgress.value = props.progress;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
watch(
|
||||||
|
() => props.active,
|
||||||
|
() => {
|
||||||
|
if (hideTimeout) {
|
||||||
|
clearTimeout(hideTimeout);
|
||||||
|
hideTimeout = undefined;
|
||||||
|
}
|
||||||
|
if (props.active) {
|
||||||
|
visible.value = true;
|
||||||
|
computedProgress.value = props.progress;
|
||||||
|
} else {
|
||||||
|
hideTimeout = setTimeout(() => {
|
||||||
|
if (!props.active) {
|
||||||
|
visible.value = false;
|
||||||
|
computedProgress.value = 0;
|
||||||
|
}
|
||||||
|
}, 500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="container" :class="active ? 'active' : ''">
|
<div
|
||||||
<span class="loader"></span>
|
class="bar"
|
||||||
</div>
|
:class="(active ? 'active' : '') + (error ? ' error' : '')"
|
||||||
|
:style="`width: ${computedProgress * 100}%;`"
|
||||||
|
></div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.container {
|
.bar {
|
||||||
display: flex;
|
width: 0%;
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
overflow: hidden;
|
|
||||||
background-color: var(--bg-color);
|
|
||||||
height: 0px;
|
|
||||||
margin: 0px;
|
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
transition: all 0.8s;
|
background-color: var(--loader-color);
|
||||||
transition-delay: 0.2s;
|
height: 5px;
|
||||||
|
transition: 0.5s;
|
||||||
|
transition-property: width opacity background-color;
|
||||||
}
|
}
|
||||||
|
|
||||||
.container.active {
|
.bar.active {
|
||||||
height: 100px;
|
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
|
.bar.error {
|
||||||
.loader {
|
background-color: var(--loader-error-color);
|
||||||
width: 48px;
|
|
||||||
height: 48px;
|
|
||||||
border: 5px solid;
|
|
||||||
border-color: var(--loader-color) transparent;
|
|
||||||
border-radius: 50%;
|
|
||||||
display: inline-block;
|
|
||||||
box-sizing: border-box;
|
|
||||||
animation: rotation 1s linear infinite;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes rotation {
|
|
||||||
0% {
|
|
||||||
transform: rotate(0deg);
|
|
||||||
}
|
|
||||||
100% {
|
|
||||||
transform: rotate(360deg);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
10
src/store.js
10
src/store.js
@ -5,6 +5,7 @@ import i18n from "./main";
|
|||||||
|
|
||||||
export const lastRoute = ref();
|
export const lastRoute = ref();
|
||||||
export const loading = ref(false);
|
export const loading = ref(false);
|
||||||
|
export const loadingProgress = ref(0);
|
||||||
|
|
||||||
export const classFilter = ref(localStorage.getItem("classFilter") || "none");
|
export const classFilter = ref(localStorage.getItem("classFilter") || "none");
|
||||||
export const timetableGroups = ref(
|
export const timetableGroups = ref(
|
||||||
@ -45,9 +46,12 @@ if (selectedDay.value == -1)
|
|||||||
|
|
||||||
// Load new data if date changes
|
// Load new data if date changes
|
||||||
watch(selectedDate, async () => {
|
watch(selectedDate, async () => {
|
||||||
|
loadingProgress.value = 0.1;
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
await fetchSubstitutions();
|
await fetchSubstitutions();
|
||||||
|
loadingProgress.value = 1 / 2;
|
||||||
await fetchHistory();
|
await fetchHistory();
|
||||||
|
loadingProgress.value = 1;
|
||||||
loading.value = false;
|
loading.value = false;
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -124,16 +128,22 @@ export const possibleTimetableGroups = computed(() => {
|
|||||||
const baseUrl = import.meta.env.VITE_API_ENDPOINT || "/api";
|
const baseUrl = import.meta.env.VITE_API_ENDPOINT || "/api";
|
||||||
|
|
||||||
export async function fetchData() {
|
export async function fetchData() {
|
||||||
|
loadingProgress.value = 0.1;
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
|
|
||||||
const checkResponse = await fetch(`${baseUrl}/check`);
|
const checkResponse = await fetch(`${baseUrl}/check`);
|
||||||
if (checkResponse.status != 200) router.push("/login");
|
if (checkResponse.status != 200) router.push("/login");
|
||||||
|
loadingProgress.value = 1 / 5;
|
||||||
|
|
||||||
await fetchClassList();
|
await fetchClassList();
|
||||||
|
loadingProgress.value = 2 / 5;
|
||||||
await fetchTimetable();
|
await fetchTimetable();
|
||||||
|
loadingProgress.value = 3 / 5;
|
||||||
await fetchSubstitutions();
|
await fetchSubstitutions();
|
||||||
|
loadingProgress.value = 4 / 5;
|
||||||
await fetchHistory();
|
await fetchHistory();
|
||||||
|
|
||||||
|
loadingProgress.value = 1;
|
||||||
loading.value = false;
|
loading.value = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user