Frontend: Prettier loading icons
ci/woodpecker/push/ociImagePush Pipeline was successful Details

This commit is contained in:
Natty 2024-04-30 00:17:20 +02:00
parent 6c7e3d79e0
commit 7c44e55597
Signed by: natty
GPG Key ID: BF6CB659ADEE60EC
12 changed files with 221 additions and 128 deletions

View File

@ -1,14 +1,15 @@
use async_stream::stream; use async_stream::stream;
use futures_util::{select, stream::StreamExt, FutureExt, Stream, TryStreamExt}; use futures_util::{FutureExt, select, Stream, stream::StreamExt, TryStreamExt};
use headers::UserAgent; use headers::UserAgent;
use hyper::body::Bytes; use hyper::body::Bytes;
use magnetar_core::web_model::ContentType; use reqwest::{Client, redirect::Policy, RequestBuilder};
use reqwest::{redirect::Policy, Client, RequestBuilder};
use serde_json::Value; use serde_json::Value;
use thiserror::Error; use thiserror::Error;
use tokio::pin; use tokio::pin;
use url::Url; use url::Url;
use magnetar_core::web_model::ContentType;
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct FederationClient { pub struct FederationClient {
pub client: Client, pub client: Client,
@ -118,8 +119,10 @@ impl FederationRequestBuilder<'_> {
async fn send_stream( async fn send_stream(
self, self,
) -> Result<impl Stream<Item = Result<Bytes, FederationClientError>>, FederationClientError> ) -> Result<impl Stream<Item=Result<Bytes, FederationClientError>>, FederationClientError>
{ {
eprintln!("{:?}", self.builder);
let mut body = self let mut body = self
.builder .builder
.send() .send()
@ -146,7 +149,7 @@ impl FederationRequestBuilder<'_> {
let sleep = tokio::time::sleep(tokio::time::Duration::from_secs( let sleep = tokio::time::sleep(tokio::time::Duration::from_secs(
self.client.timeout_seconds, self.client.timeout_seconds,
)) ))
.fuse(); .fuse();
tokio::pin!(sleep); tokio::pin!(sleep);
let body = async move { let body = async move {
@ -158,7 +161,7 @@ impl FederationRequestBuilder<'_> {
}) })
.await .await
} }
.fuse(); .fuse();
pin!(body); pin!(body);

View File

@ -7,8 +7,9 @@
:pinned="pinned" :pinned="pinned"
> >
</XNote> </XNote>
<div v-else> <div class="spinner-container" v-else>
<i class="ph-circle-notch ph-bold ph-lg fa-pulse ph-fw ph-lg"></i> <MagSpinner />
Fetching
</div> </div>
</template> </template>
@ -39,6 +40,17 @@ watch(
noteData.value = n; noteData.value = n;
}); });
}, },
{ immediate: true }, { immediate: true }
); );
</script> </script>
<style scoped lang="scss">
.spinner-container {
margin: 12px 0;
display: flex;
align-items: center;
justify-content: center;
font-size: 1.2em;
gap: 0.4em;
}
</style>

View File

@ -47,10 +47,7 @@
<a v-if="!pollRefreshing" @click.stop="refresh">{{ <a v-if="!pollRefreshing" @click.stop="refresh">{{
i18n.ts.reload i18n.ts.reload
}}</a> }}</a>
<i <MagSpinner v-else />
v-else
class="ph-circle-notch ph-bold ph-lg fa-pulse ph-fw ph-lg"
></i>
</span> </span>
<span v-if="isVoted"> · {{ i18n.ts._poll.voted }}</span> <span v-if="isVoted"> · {{ i18n.ts._poll.voted }}</span>
<span v-else-if="closed"> · {{ i18n.ts._poll.closed }}</span> <span v-else-if="closed"> · {{ i18n.ts._poll.closed }}</span>
@ -77,31 +74,31 @@ const pollRefreshing = ref(false);
const remaining = ref(-1); const remaining = ref(-1);
const total = computed(() => const total = computed(() =>
sum(props.note.poll.options.map((x) => x.votes_count)), sum(props.note.poll.options.map((x) => x.votes_count))
); );
const closed = computed(() => remaining.value === 0); const closed = computed(() => remaining.value === 0);
const isLocal = computed(() => !props.note.uri); const isLocal = computed(() => !props.note.uri);
const isVoted = computed( const isVoted = computed(
() => () =>
!props.note.poll.multiple_choice && !props.note.poll.multiple_choice &&
props.note.poll.options.some((c) => c.voted ?? false), props.note.poll.options.some((c) => c.voted ?? false)
); );
const timer = computed(() => const timer = computed(() =>
i18n.t( i18n.t(
remaining.value >= 86400 remaining.value >= 86400
? "_poll.remainingDays" ? "_poll.remainingDays"
: remaining.value >= 3600 : remaining.value >= 3600
? "_poll.remainingHours" ? "_poll.remainingHours"
: remaining.value >= 60 : remaining.value >= 60
? "_poll.remainingMinutes" ? "_poll.remainingMinutes"
: "_poll.remainingSeconds", : "_poll.remainingSeconds",
{ {
s: Math.floor(remaining.value % 60), s: Math.floor(remaining.value % 60),
m: Math.floor(remaining.value / 60) % 60, m: Math.floor(remaining.value / 60) % 60,
h: Math.floor(remaining.value / 3600) % 24, h: Math.floor(remaining.value / 3600) % 24,
d: Math.floor(remaining.value / 86400), d: Math.floor(remaining.value / 86400),
}, }
), )
); );
const showResult = ref(props.readOnly || isVoted.value); const showResult = ref(props.readOnly || isVoted.value);
@ -112,8 +109,8 @@ if (props.note.poll.expires_at) {
remaining.value = Math.floor( remaining.value = Math.floor(
Math.max( Math.max(
new Date(props.note.poll.expires_at!).getTime() - Date.now(), new Date(props.note.poll.expires_at!).getTime() - Date.now(),
0, 0
) / 1000, ) / 1000
); );
if (remaining.value === 0) { if (remaining.value === 0) {
showResult.value = true; showResult.value = true;
@ -137,7 +134,7 @@ async function refresh() {
os.magApi( os.magApi(
endpoints.GetNoteById, endpoints.GetNoteById,
{ attachments: true }, { attachments: true },
{ id: obj.object.id }, { id: obj.object.id }
).then((n) => { ).then((n) => {
props.note.poll = { ...toRaw(props.note.poll), ...n.poll }; props.note.poll = { ...toRaw(props.note.poll), ...n.poll };
}); });

View File

@ -1,6 +1,6 @@
<template> <template>
<template v-if="waitIncoming"> <template v-if="waitIncoming">
<i class="ph-circle-notch ph-bold ph-lg fa-pulse ph-fw ph-lg"></i> <MagSpinner />
</template> </template>
<template v-else> <template v-else>
<button <button

View File

@ -44,7 +44,7 @@
> >
<!-- つまりリモートフォローの場合 --> <!-- つまりリモートフォローの場合 -->
<span>{{ (state = i18n.ts.processing) }}</span <span>{{ (state = i18n.ts.processing) }}</span
><i class="ph-circle-notch ph-bold ph-lg fa-pulse"></i> ><MagSpinner />
</template> </template>
<template v-else-if="isFollowing"> <template v-else-if="isFollowing">
<span>{{ (state = i18n.ts.unfollow) }}</span <span>{{ (state = i18n.ts.unfollow) }}</span
@ -71,7 +71,7 @@
</template> </template>
<template v-else> <template v-else>
<span>{{ (state = i18n.ts.processing) }}</span <span>{{ (state = i18n.ts.processing) }}</span
><i class="ph-circle-notch ph-bold ph-lg fa-pulse ph-fw ph-lg"></i> ><MagSpinner />
</template> </template>
</button> </button>
</template> </template>

View File

@ -52,7 +52,6 @@ const props = withDefaults(
{} {}
); );
let hide = ref(true); let hide = ref(true);
</script> </script>
@ -101,6 +100,7 @@ let hide = ref(true);
& fieldset.audio-player-frame { & fieldset.audio-player-frame {
border-color: var(--accentedBg); border-color: var(--accentedBg);
border-radius: 10px; border-radius: 10px;
border-style: solid;
& legend { & legend {
padding-inline: 5px; padding-inline: 5px;

View File

@ -47,15 +47,17 @@
v-tooltip:dialog="i18n.ts.usernameInfo" v-tooltip:dialog="i18n.ts.usernameInfo"
class="_button _help" class="_button _help"
> >
<i class="ph-question ph-bold"></i></div <i class="ph-question ph-bold"></i>
></template> </div>
</template>
<template #prefix>@</template> <template #prefix>@</template>
<template #suffix>@{{ host }}</template> <template #suffix>@{{ host }}</template>
<template #caption> <template #caption>
<span v-if="usernameState === 'wait'" style="color: #6e6a86" <span
><i v-if="usernameState === 'wait'"
class="ph-circle-notch ph-bold ph-lg fa-pulse ph-fw ph-lg" style="color: #6e6a86"
></i> >
<MagSpinner />
{{ i18n.ts.checking }}</span {{ i18n.ts.checking }}</span
> >
<span <span
@ -113,16 +115,15 @@
v-tooltip:dialog="i18n.ts._signup.emailAddressInfo" v-tooltip:dialog="i18n.ts._signup.emailAddressInfo"
class="_button _help" class="_button _help"
> >
<i class="ph-question ph-bold"></i></div <i class="ph-question ph-bold"></i>
></template> </div>
</template>
<template #prefix <template #prefix
><i class="ph-envelope-simple-open ph-bold ph-lg"></i ><i class="ph-envelope-simple-open ph-bold ph-lg"></i
></template> ></template>
<template #caption> <template #caption>
<span v-if="emailState === 'wait'" style="color: #6e6a86" <span v-if="emailState === 'wait'" style="color: #6e6a86">
><i <MagSpinner />
class="ph-circle-notch ph-bold ph-lg fa-pulse ph-fw ph-lg"
></i>
{{ i18n.ts.checking }}</span {{ i18n.ts.checking }}</span
> >
<span <span
@ -219,8 +220,8 @@
@update:modelValue="onChangePasswordRetype" @update:modelValue="onChangePasswordRetype"
> >
<template #label <template #label
>{{ i18n.ts.password }} ({{ i18n.ts.retype }})</template >{{ i18n.ts.password }} ({{ i18n.ts.retype }})
> </template>
<template #prefix <template #prefix
><i class="ph-lock ph-bold ph-lg"></i ><i class="ph-lock ph-bold ph-lg"></i
></template> ></template>
@ -277,16 +278,14 @@
:disabled="shouldDisableSubmitting" :disabled="shouldDisableSubmitting"
gradate gradate
data-cy-signup-submit data-cy-signup-submit
>{{ i18n.ts.start }}</MkButton >{{ i18n.ts.start }}
> </MkButton>
</div> </div>
</form> </form>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { ref, computed } from "vue"; import { computed, ref } from "vue";
import {} from "vue";
import getPasswordStrength from "syuilo-password-strength"; import getPasswordStrength from "syuilo-password-strength";
import { toUnicode } from "punycode/"; import { toUnicode } from "punycode/";
import MkButton from "./MkButton.vue"; import MkButton from "./MkButton.vue";
@ -305,7 +304,7 @@ const props = withDefaults(
}>(), }>(),
{ {
autoSet: false, autoSet: false,
}, }
); );
const emit = defineEmits<{ const emit = defineEmits<{
@ -379,10 +378,10 @@ function onChangeUsername(): void {
const err = !username.value.match(/^[a-zA-Z0-9_]+$/) const err = !username.value.match(/^[a-zA-Z0-9_]+$/)
? "invalid-format" ? "invalid-format"
: username.value.length < 1 : username.value.length < 1
? "min-range" ? "min-range"
: username.value.length > 20 : username.value.length > 20
? "max-range" ? "max-range"
: null; : null;
if (err) { if (err) {
usernameState.value = err; usernameState.value = err;
@ -418,16 +417,16 @@ function onChangeEmail(): void {
emailState.value = result.available emailState.value = result.available
? "ok" ? "ok"
: result.reason === "used" : result.reason === "used"
? "unavailable:used" ? "unavailable:used"
: result.reason === "format" : result.reason === "format"
? "unavailable:format" ? "unavailable:format"
: result.reason === "disposable" : result.reason === "disposable"
? "unavailable:disposable" ? "unavailable:disposable"
: result.reason === "mx" : result.reason === "mx"
? "unavailable:mx" ? "unavailable:mx"
: result.reason === "smtp" : result.reason === "smtp"
? "unavailable:smtp" ? "unavailable:smtp"
: "unavailable"; : "unavailable";
}) })
.catch(() => { .catch(() => {
emailState.value = "error"; emailState.value = "error";

View File

@ -0,0 +1,80 @@
<template>
<i class="simple ph-hourglass ph-bold ph-lg" :aria-label="i18n.ts.busy"></i>
<svg
class="animated mag-spinner"
width="32"
height="32"
version="1.1"
viewBox="0 0 67.733 67.733"
xmlns="http://www.w3.org/2000/svg"
>
<g class="mag-ring" transform="matrix(.82287 0 0 .82287 5.9984 5.9989)">
<path
d="m33.959 0.8413c-8.3737-0.008823-16.592 3.1643-22.849 9.1188-8.8995 8.4685-12.376 21.177-9.023 32.996l5.9686-1.681c-2.7249-9.6065 0.095231-19.927 7.329-26.811 7.2337-6.8834 17.845-8.9517 26.103-6.264l1.9737-5.9045c-2.9094-0.98361-4.8438-1.2553-7.8257-1.41-0.55911-0.028992-1.1178-0.044097-1.676-0.04468zm31.826 24.715-6.0201 1.553c2.4674 9.2417-0.15852 19.094-6.8993 25.88-6.7409 6.7861-16.177 9.1968-25.564 6.8428l-1.6444 5.9555c11.39 2.9597 23.3-0.07129 31.594-8.4205 8.2936-8.3492 11.569-20.441 8.5336-31.811z"
fill="currentColor"
/>
</g>
<g
class="mag-butterflies"
transform="matrix(1.2958 .098299 -.098299 1.2958 .41715 -18.811)"
>
<path
d="m19.244 39.965c-0.62132 0.75445-1.2862 1.4845-1.8805 2.2541-0.45826 1.4275-0.95911 2.8484-1.3908 4.28 0.01197 0.7973 0.02382 1.5946 0.03574 2.3919-0.82611-0.0559-1.656-0.15529-2.4798-0.18435-1.4482 0.38557-2.9176 0.72632-4.3525 1.1398-0.76206 0.56257-1.5241 1.1251-2.2862 1.6877 0.00746 0.57752 0.014929 1.155 0.022392 1.7326 0.85814 0.49688 1.6647 1.0953 2.6101 1.4204 0.76646 0.32006 1.5329 0.64012 2.2994 0.96018 0.53911-0.39567 1.0782-0.79134 1.6173-1.187 0.01331 0.4126 0.02652 0.82522 0.03983 1.2378 0.55847 0.37816 1.0935 0.81873 1.6666 1.1579 0.94848 0.09538 1.897 0.19074 2.8454 0.28611 0.79636-0.46986 1.5927-0.93971 2.3891-1.4096-0.14459-0.67269-0.24382-1.3636-0.41647-2.025-0.40398-0.62711-0.79926-1.2606-1.2086-1.8838 0.62691 0.4569 1.2427 0.94576 1.8866 1.3706 0.63581 0.16906 1.2716 0.33814 1.9074 0.5072 0.52106-0.76381 1.0421-1.5276 1.5632-2.2914-0.05014-0.9802-0.03649-1.9797-0.12641-2.9479-0.33964-0.54964-0.67928-1.0993-1.0189-1.6489-0.41083-0.04035-0.82165-0.08069-1.2325-0.12099 0.43013-0.51202 0.86026-1.024 1.2904-1.5361-0.39788-1.1174-0.74239-2.2576-1.1736-3.3607-0.29327-0.56483-0.58654-1.1297-0.8798-1.6945-0.5758-0.0454-1.1516-0.09072-1.7274-0.13608z"
fill="currentColor"
/>
</g>
<g
class="mag-butterflies"
transform="matrix(-1.2957 -.098293 .098293 -1.2957 66.781 86.854)"
>
<path
d="m19.244 39.965c-0.62132 0.75445-1.2862 1.4845-1.8805 2.2541-0.45826 1.4275-0.95911 2.8484-1.3908 4.28 0.01197 0.7973 0.02382 1.5946 0.03574 2.3919-0.82611-0.0559-1.656-0.15529-2.4798-0.18435-1.4482 0.38557-2.9176 0.72632-4.3525 1.1398-0.76206 0.56257-1.5241 1.1251-2.2862 1.6877 0.00746 0.57752 0.014929 1.155 0.022392 1.7326 0.85814 0.49688 1.6647 1.0953 2.6101 1.4204 0.76646 0.32006 1.5329 0.64012 2.2994 0.96018 0.53911-0.39567 1.0782-0.79134 1.6173-1.187 0.01331 0.4126 0.02652 0.82522 0.03983 1.2378 0.55847 0.37816 1.0935 0.81873 1.6666 1.1579 0.94848 0.09538 1.897 0.19074 2.8454 0.28611 0.79636-0.46986 1.5927-0.93971 2.3891-1.4096-0.14459-0.67269-0.24382-1.3636-0.41647-2.025-0.40398-0.62711-0.79926-1.2606-1.2086-1.8838 0.62691 0.4569 1.2427 0.94576 1.8866 1.3706 0.63581 0.16906 1.2716 0.33814 1.9074 0.5072 0.52106-0.76381 1.0421-1.5276 1.5632-2.2914-0.05014-0.9802-0.03649-1.9797-0.12641-2.9479-0.33964-0.54964-0.67928-1.0993-1.0189-1.6489-0.41083-0.04035-0.82165-0.08069-1.2325-0.12099 0.43013-0.51202 0.86026-1.024 1.2904-1.5361-0.39788-1.1174-0.74239-2.2576-1.1736-3.3607-0.29327-0.56483-0.58654-1.1297-0.8798-1.6945-0.5758-0.0454-1.1516-0.09072-1.7274-0.13608z"
fill="currentColor"
/>
</g>
</svg>
</template>
<script setup lang="ts">
import { i18n } from "@/i18n";
</script>
<style scoped lang="scss">
.simple {
display: none;
}
.mag-spinner.animated {
display: inline-block;
height: 100%;
aspect-ratio: 1 / 1;
width: auto;
transform-origin: center;
animation-name: mag-ring-rotate;
animation-timing-function: linear;
animation-direction: normal;
animation-duration: 3s;
animation-iteration-count: infinite;
}
@keyframes mag-ring-rotate {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
@media (prefers-reduced-motion) {
.simple {
display: initial;
}
.animated {
display: none;
}
}
</style>

View File

@ -1,37 +1,50 @@
<template> <template>
<div <svg
aria-hidden="true"
:class="[ :class="[
$style.root, $style['mag-spinner-loading'],
{ {
[$style.inline]: inline, [$style.inline]: inline,
[$style.colored]: colored, [$style.colored]: colored,
[$style.mini]: mini, [$style.mini]: mini,
}, },
]" ]"
width="32"
height="32"
version="1.1"
viewBox="0 0 67.733 67.733"
xmlns="http://www.w3.org/2000/svg"
> >
<div :class="$style.container" aria-hidden="true"> <g :class="$style['mag-spinner-spin']">
<svg <g transform="matrix(.82287 0 0 .82287 5.9984 5.9989)">
:class="[$style.spinner]" <path
viewBox="0 0 50 50" d="m33.959 0.8413c-8.3737-0.008823-16.592 3.1643-22.849 9.1188-8.8995 8.4685-12.376 21.177-9.023 32.996l5.9686-1.681c-2.7249-9.6065 0.095231-19.927 7.329-26.811 7.2337-6.8834 17.845-8.9517 26.103-6.264l1.9737-5.9045c-2.9094-0.98361-4.8438-1.2553-7.8257-1.41-0.55911-0.028992-1.1178-0.044097-1.676-0.04468zm31.826 24.715-6.0201 1.553c2.4674 9.2417-0.15852 19.094-6.8993 25.88-6.7409 6.7861-16.177 9.1968-25.564 6.8428l-1.6444 5.9555c11.39 2.9597 23.3-0.07129 31.594-8.4205 8.2936-8.3492 11.569-20.441 8.5336-31.811z"
xmlns="http://www.w3.org/2000/svg" fill="currentColor"
/>
</g>
<g
:class="$style['mag-butterflies']"
transform="matrix(1.2958 .098299 -.098299 1.2958 .41715 -18.811)"
> >
<circle <path
:class="[$style.path]" d="m19.244 39.965c-0.62132 0.75445-1.2862 1.4845-1.8805 2.2541-0.45826 1.4275-0.95911 2.8484-1.3908 4.28 0.01197 0.7973 0.02382 1.5946 0.03574 2.3919-0.82611-0.0559-1.656-0.15529-2.4798-0.18435-1.4482 0.38557-2.9176 0.72632-4.3525 1.1398-0.76206 0.56257-1.5241 1.1251-2.2862 1.6877 0.00746 0.57752 0.014929 1.155 0.022392 1.7326 0.85814 0.49688 1.6647 1.0953 2.6101 1.4204 0.76646 0.32006 1.5329 0.64012 2.2994 0.96018 0.53911-0.39567 1.0782-0.79134 1.6173-1.187 0.01331 0.4126 0.02652 0.82522 0.03983 1.2378 0.55847 0.37816 1.0935 0.81873 1.6666 1.1579 0.94848 0.09538 1.897 0.19074 2.8454 0.28611 0.79636-0.46986 1.5927-0.93971 2.3891-1.4096-0.14459-0.67269-0.24382-1.3636-0.41647-2.025-0.40398-0.62711-0.79926-1.2606-1.2086-1.8838 0.62691 0.4569 1.2427 0.94576 1.8866 1.3706 0.63581 0.16906 1.2716 0.33814 1.9074 0.5072 0.52106-0.76381 1.0421-1.5276 1.5632-2.2914-0.05014-0.9802-0.03649-1.9797-0.12641-2.9479-0.33964-0.54964-0.67928-1.0993-1.0189-1.6489-0.41083-0.04035-0.82165-0.08069-1.2325-0.12099 0.43013-0.51202 0.86026-1.024 1.2904-1.5361-0.39788-1.1174-0.74239-2.2576-1.1736-3.3607-0.29327-0.56483-0.58654-1.1297-0.8798-1.6945-0.5758-0.0454-1.1516-0.09072-1.7274-0.13608z"
cx="25" fill="currentColor"
cy="25" />
r="20" </g>
fill="none" <g
stroke-width="6px" :class="$style['mag-butterflies']"
style="fill: none; stroke: currentColor; stroke-width: 6px" transform="matrix(-1.2957 -.098293 .098293 -1.2957 66.781 86.854)"
></circle> >
</svg> <path
</div> d="m19.244 39.965c-0.62132 0.75445-1.2862 1.4845-1.8805 2.2541-0.45826 1.4275-0.95911 2.8484-1.3908 4.28 0.01197 0.7973 0.02382 1.5946 0.03574 2.3919-0.82611-0.0559-1.656-0.15529-2.4798-0.18435-1.4482 0.38557-2.9176 0.72632-4.3525 1.1398-0.76206 0.56257-1.5241 1.1251-2.2862 1.6877 0.00746 0.57752 0.014929 1.155 0.022392 1.7326 0.85814 0.49688 1.6647 1.0953 2.6101 1.4204 0.76646 0.32006 1.5329 0.64012 2.2994 0.96018 0.53911-0.39567 1.0782-0.79134 1.6173-1.187 0.01331 0.4126 0.02652 0.82522 0.03983 1.2378 0.55847 0.37816 1.0935 0.81873 1.6666 1.1579 0.94848 0.09538 1.897 0.19074 2.8454 0.28611 0.79636-0.46986 1.5927-0.93971 2.3891-1.4096-0.14459-0.67269-0.24382-1.3636-0.41647-2.025-0.40398-0.62711-0.79926-1.2606-1.2086-1.8838 0.62691 0.4569 1.2427 0.94576 1.8866 1.3706 0.63581 0.16906 1.2716 0.33814 1.9074 0.5072 0.52106-0.76381 1.0421-1.5276 1.5632-2.2914-0.05014-0.9802-0.03649-1.9797-0.12641-2.9479-0.33964-0.54964-0.67928-1.0993-1.0189-1.6489-0.41083-0.04035-0.82165-0.08069-1.2325-0.12099 0.43013-0.51202 0.86026-1.024 1.2904-1.5361-0.39788-1.1174-0.74239-2.2576-1.1736-3.3607-0.29327-0.56483-0.58654-1.1297-0.8798-1.6945-0.5758-0.0454-1.1516-0.09072-1.7274-0.13608z"
</div> fill="currentColor"
/>
</g>
</g>
</svg>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import {} from "vue";
const props = withDefaults( const props = withDefaults(
defineProps<{ defineProps<{
inline?: boolean; inline?: boolean;
@ -44,14 +57,12 @@ const props = withDefaults(
colored: true, colored: true,
mini: false, mini: false,
em: false, em: false,
}, }
); );
</script> </script>
<style lang="scss" module> <style lang="scss" module>
/* Credit to https://codepen.io/supah/pen/BjYLdW */ @keyframes mag-loading-spin {
@keyframes spin {
0% { 0% {
transform: rotate(0deg); transform: rotate(0deg);
} }
@ -60,25 +71,33 @@ const props = withDefaults(
} }
} }
@keyframes dash { @keyframes mag-loading-butterfly-appear {
0% { 0% {
stroke-dasharray: 1, 150; opacity: 0;
stroke-dashoffset: 0;
}
50% {
stroke-dasharray: 90, 150;
stroke-dashoffset: -35;
} }
100% { 100% {
stroke-dasharray: 90, 150; opacity: 1;
stroke-dashoffset: -124;
} }
} }
.root { .mag-butterflies {
padding: 32px; opacity: 0;
text-align: center; animation: 1s mag-loading-butterfly-appear 0s 1 ease-out forwards;
}
.mag-spinner-spin {
animation: 2.5s mag-loading-spin infinite linear;
transform-origin: center;
}
.mag-spinner-loading {
display: block;
position: relative;
margin-block: 32px;
cursor: wait; cursor: wait;
width: var(--size);
height: var(--size);
--size: 40px; --size: 40px;
@ -88,42 +107,22 @@ const props = withDefaults(
&.inline { &.inline {
display: inline; display: inline;
padding: 0;
--size: 32px; --size: 32px;
} }
&.mini { &.mini {
padding: 16px; margin: 16px;
--size: 32px; --size: 16px;
} }
&:not(.inline) {
margin-inline: auto;
}
&.em { &.em {
display: inline-block; display: inline-block;
vertical-align: middle; vertical-align: middle;
padding: 0;
--size: 1em; --size: 1em;
} }
} }
.container {
position: relative;
width: var(--size);
height: var(--size);
margin: 0 auto;
}
.spinner {
position: absolute;
top: 0;
left: 0;
z-index: 999;
width: var(--size);
height: var(--size);
animation: spin 2s linear infinite;
}
.path {
stroke: var(--accent);
stroke-linecap: round;
animation: dash 1.2s ease-in-out infinite;
}
</style> </style>

View File

@ -60,12 +60,14 @@ import MkError from "./components/global/MkError.vue";
import MkPageHeader from "./components/global/MkPageHeader.vue"; import MkPageHeader from "./components/global/MkPageHeader.vue";
import MkSpacer from "./components/global/MkSpacer.vue"; import MkSpacer from "./components/global/MkSpacer.vue";
import MkStickyContainer from "./components/global/MkStickyContainer.vue"; import MkStickyContainer from "./components/global/MkStickyContainer.vue";
import MagSpinner from "@/components/global/MagSpinner.vue";
function globalComponents(app: App) { function globalComponents(app: App) {
app.component("I18n", I18n); app.component("I18n", I18n);
app.component("RouterView", RouterView); app.component("RouterView", RouterView);
app.component("Mfm", Mfm); app.component("Mfm", Mfm);
app.component("MkA", MkA); app.component("MkA", MkA);
app.component("MagSpinner", MagSpinner);
app.component("MkAcct", MkAcct); app.component("MkAcct", MkAcct);
app.component("MagAvatar", MagAvatar); app.component("MagAvatar", MagAvatar);
app.component("MagEmoji", MagEmoji); app.component("MagEmoji", MagEmoji);
@ -87,6 +89,7 @@ declare module "@vue/runtime-core" {
RouterView: typeof RouterView; RouterView: typeof RouterView;
Mfm: typeof Mfm; Mfm: typeof Mfm;
MkA: typeof MkA; MkA: typeof MkA;
MagSpinner: typeof MagSpinner;
MkAcct: typeof MkAcct; MkAcct: typeof MkAcct;
MagAvatar: typeof MagAvatar; MagAvatar: typeof MagAvatar;
MagEmoji: typeof MagEmoji; MagEmoji: typeof MagEmoji;

View File

@ -8,8 +8,7 @@
></div> ></div>
<div class="top"> <div class="top">
<p class="name"> <p class="name">
<i class="ph-circle-notch ph-bold ph-lg fa-pulse"></i <MagSpinner />{{ ctx.name }}
>{{ ctx.name }}
</p> </p>
<p class="status"> <p class="status">
<span <span

View File

@ -34,6 +34,7 @@ loggingIn: "Signing In"
logout: "Sign Out" logout: "Sign Out"
signup: "Sign Up" signup: "Sign Up"
uploading: "Uploading..." uploading: "Uploading..."
busy: "Busy..."
save: "Save" save: "Save"
users: "Users" users: "Users"
addUser: "Add a user" addUser: "Add a user"