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