add PromiseResolver.vue, TimeFormatter.vue
parent
b91950d9ab
commit
e4eb578d51
@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<svg width='200px' height='200px' xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" preserveAspectRatio="xMidYMid" class="uil-ring-alt">
|
||||
<rect x="0" y="0" width="100" height="100" fill="none" class="bk"></rect>
|
||||
<circle cx="50" cy="50" r="40" stroke="#d9d9d9" fill="none" stroke-width="10" stroke-linecap="round"></circle>
|
||||
<circle cx="50" cy="50" r="40" stroke="#009eff" fill="none" stroke-width="10" stroke-linecap="round">
|
||||
<animate attributeName="stroke-dashoffset" dur="2s" repeatCount="indefinite" from="502" to="0"></animate>
|
||||
<animate attributeName="stroke-dasharray" dur="2s" repeatCount="indefinite" values="125.5 125.5;1 250;125.5 125.5"></animate>
|
||||
</circle>
|
||||
</svg>
|
After Width: | Height: | Size: 751 B |
@ -0,0 +1,114 @@
|
||||
<template>
|
||||
<div
|
||||
class="position-relative PromiseResolver"
|
||||
:class="{ loading: loading && showThrobber }"
|
||||
>
|
||||
<slot
|
||||
v-if="!error && (!loading || (overlay && data !== null))"
|
||||
:data="getter(data)"
|
||||
:update="update"
|
||||
/>
|
||||
<slot
|
||||
v-if="error"
|
||||
name="error"
|
||||
:error="error"
|
||||
:update="update"
|
||||
>
|
||||
<div class="alert alert-danger">
|
||||
{{ error.message || 'Oops! Something went wrong :/' }}
|
||||
</div>
|
||||
</slot>
|
||||
<transition name="fade">
|
||||
<slot v-if="loading && showThrobber" name="loading">
|
||||
<div class="overlay">
|
||||
<div class="wrapper">
|
||||
<div class="background bg-darkmode-dark bg-light" />
|
||||
<ThrobberLoading class="position-relative" />
|
||||
</div>
|
||||
</div>
|
||||
</slot>
|
||||
</transition>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, defineProps, watch } from "vue";
|
||||
import ThrobberLoading from "@/components/ThrobberLoading.vue";
|
||||
|
||||
const props = defineProps({
|
||||
promise: [Promise, Object],
|
||||
getter: {
|
||||
type: Function,
|
||||
default: (data: unknown) => data,
|
||||
},
|
||||
overlay: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
throbber: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
}
|
||||
});
|
||||
|
||||
const loading = ref(true);
|
||||
const showThrobber = ref(true);
|
||||
const error = ref(null);
|
||||
const data = ref(null);
|
||||
|
||||
const update = async (promise: Promise | unknown) => {
|
||||
loading.value = true;
|
||||
if (props.throbber) setTimeout(() => {
|
||||
if (loading.value) showThrobber.value = true;
|
||||
}, 500);
|
||||
try {
|
||||
data.value = await (promise.isPromiseList
|
||||
? Promise.all(promise.promises)
|
||||
: promise);
|
||||
error.value = null;
|
||||
} catch (e) {
|
||||
error.value = e;
|
||||
} finally {
|
||||
loading.value = false;
|
||||
showThrobber.value = false;
|
||||
}
|
||||
};
|
||||
update(props.promise);
|
||||
|
||||
watch(() => props.promise, (to) => {
|
||||
update(to);
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.PromiseResolver {
|
||||
&.loading {
|
||||
min-height: 5rem;
|
||||
}
|
||||
.overlay {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
min-height: 5rem;
|
||||
.wrapper {
|
||||
position: sticky;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
top: 0;
|
||||
height: 100%;
|
||||
max-height: 100vh;
|
||||
.background {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
opacity: 0.9;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
@ -0,0 +1,19 @@
|
||||
<template>
|
||||
<div class="Throbber">
|
||||
<img src="../assets/throbber.svg" alt="throbber" />
|
||||
<div class="info"><slot>loading</slot></div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.Throbber {
|
||||
padding: 0.5rem;
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
align-items: center;
|
||||
img {
|
||||
height: 3rem;
|
||||
width: 3rem;
|
||||
}
|
||||
}
|
||||
</style>
|
@ -0,0 +1,29 @@
|
||||
<script setup lang="ts">
|
||||
import { defineProps, watch, ref } from "vue";
|
||||
|
||||
const props = defineProps({
|
||||
seconds: Number,
|
||||
});
|
||||
|
||||
const getHmsFromSeconds = (d: number) => ({
|
||||
hours: Math.floor(d / 3600),
|
||||
minutes: Math.floor(d % 3600 / 60),
|
||||
seconds: Math.floor(d % 3600 % 60),
|
||||
});
|
||||
|
||||
const getLeadingZero = (a: number, digits: number) =>
|
||||
('0'.repeat(digits) + a.toString())
|
||||
.slice(- digits);
|
||||
|
||||
const hms = ref(getHmsFromSeconds(props.seconds as number));
|
||||
|
||||
watch(() => props.seconds, (to) => {
|
||||
hms.value = getHmsFromSeconds(to as number);
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<span v-if="hms.hours" class="hours">{{ getLeadingZero(hms.hours, 2) }}:</span>
|
||||
<span class="minutes">{{ getLeadingZero(hms.minutes, 2) }}:</span>
|
||||
<span class="seconds">{{ getLeadingZero(hms.seconds, 2) }}</span>
|
||||
</template>
|
Loading…
Reference in New Issue