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

View File

@ -7,8 +7,9 @@
:pinned="pinned"
>
</XNote>
<div v-else>
<i class="ph-circle-notch ph-bold ph-lg fa-pulse ph-fw ph-lg"></i>
<div class="spinner-container" v-else>
<MagSpinner />
Fetching
</div>
</template>
@ -39,6 +40,17 @@ watch(
noteData.value = n;
});
},
{ immediate: true },
{ immediate: true }
);
</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">{{
i18n.ts.reload
}}</a>
<i
v-else
class="ph-circle-notch ph-bold ph-lg fa-pulse ph-fw ph-lg"
></i>
<MagSpinner v-else />
</span>
<span v-if="isVoted"> · {{ i18n.ts._poll.voted }}</span>
<span v-else-if="closed"> · {{ i18n.ts._poll.closed }}</span>
@ -77,31 +74,31 @@ const pollRefreshing = ref(false);
const remaining = ref(-1);
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 isLocal = computed(() => !props.note.uri);
const isVoted = computed(
() =>
!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(() =>
i18n.t(
remaining.value >= 86400
? "_poll.remainingDays"
: remaining.value >= 3600
? "_poll.remainingHours"
: remaining.value >= 60
? "_poll.remainingMinutes"
: "_poll.remainingSeconds",
? "_poll.remainingHours"
: remaining.value >= 60
? "_poll.remainingMinutes"
: "_poll.remainingSeconds",
{
s: Math.floor(remaining.value % 60),
m: Math.floor(remaining.value / 60) % 60,
h: Math.floor(remaining.value / 3600) % 24,
d: Math.floor(remaining.value / 86400),
},
),
}
)
);
const showResult = ref(props.readOnly || isVoted.value);
@ -112,8 +109,8 @@ if (props.note.poll.expires_at) {
remaining.value = Math.floor(
Math.max(
new Date(props.note.poll.expires_at!).getTime() - Date.now(),
0,
) / 1000,
0
) / 1000
);
if (remaining.value === 0) {
showResult.value = true;
@ -137,7 +134,7 @@ async function refresh() {
os.magApi(
endpoints.GetNoteById,
{ attachments: true },
{ id: obj.object.id },
{ id: obj.object.id }
).then((n) => {
props.note.poll = { ...toRaw(props.note.poll), ...n.poll };
});

View File

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

View File

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

View File

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

View File

@ -47,15 +47,17 @@
v-tooltip:dialog="i18n.ts.usernameInfo"
class="_button _help"
>
<i class="ph-question ph-bold"></i></div
></template>
<i class="ph-question ph-bold"></i>
</div>
</template>
<template #prefix>@</template>
<template #suffix>@{{ host }}</template>
<template #caption>
<span v-if="usernameState === 'wait'" style="color: #6e6a86"
><i
class="ph-circle-notch ph-bold ph-lg fa-pulse ph-fw ph-lg"
></i>
<span
v-if="usernameState === 'wait'"
style="color: #6e6a86"
>
<MagSpinner />
{{ i18n.ts.checking }}</span
>
<span
@ -113,16 +115,15 @@
v-tooltip:dialog="i18n.ts._signup.emailAddressInfo"
class="_button _help"
>
<i class="ph-question ph-bold"></i></div
></template>
<i class="ph-question ph-bold"></i>
</div>
</template>
<template #prefix
><i class="ph-envelope-simple-open ph-bold ph-lg"></i
></template>
<template #caption>
<span v-if="emailState === 'wait'" style="color: #6e6a86"
><i
class="ph-circle-notch ph-bold ph-lg fa-pulse ph-fw ph-lg"
></i>
<span v-if="emailState === 'wait'" style="color: #6e6a86">
<MagSpinner />
{{ i18n.ts.checking }}</span
>
<span
@ -219,8 +220,8 @@
@update:modelValue="onChangePasswordRetype"
>
<template #label
>{{ i18n.ts.password }} ({{ i18n.ts.retype }})</template
>
>{{ i18n.ts.password }} ({{ i18n.ts.retype }})
</template>
<template #prefix
><i class="ph-lock ph-bold ph-lg"></i
></template>
@ -277,16 +278,14 @@
:disabled="shouldDisableSubmitting"
gradate
data-cy-signup-submit
>{{ i18n.ts.start }}</MkButton
>
>{{ i18n.ts.start }}
</MkButton>
</div>
</form>
</template>
<script lang="ts" setup>
import { ref, computed } from "vue";
import {} from "vue";
import { computed, ref } from "vue";
import getPasswordStrength from "syuilo-password-strength";
import { toUnicode } from "punycode/";
import MkButton from "./MkButton.vue";
@ -305,7 +304,7 @@ const props = withDefaults(
}>(),
{
autoSet: false,
},
}
);
const emit = defineEmits<{
@ -379,10 +378,10 @@ function onChangeUsername(): void {
const err = !username.value.match(/^[a-zA-Z0-9_]+$/)
? "invalid-format"
: username.value.length < 1
? "min-range"
: username.value.length > 20
? "max-range"
: null;
? "min-range"
: username.value.length > 20
? "max-range"
: null;
if (err) {
usernameState.value = err;
@ -418,16 +417,16 @@ function onChangeEmail(): void {
emailState.value = result.available
? "ok"
: result.reason === "used"
? "unavailable:used"
: result.reason === "format"
? "unavailable:format"
: result.reason === "disposable"
? "unavailable:disposable"
: result.reason === "mx"
? "unavailable:mx"
: result.reason === "smtp"
? "unavailable:smtp"
: "unavailable";
? "unavailable:used"
: result.reason === "format"
? "unavailable:format"
: result.reason === "disposable"
? "unavailable:disposable"
: result.reason === "mx"
? "unavailable:mx"
: result.reason === "smtp"
? "unavailable:smtp"
: "unavailable";
})
.catch(() => {
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>
<div
<svg
aria-hidden="true"
:class="[
$style.root,
$style['mag-spinner-loading'],
{
[$style.inline]: inline,
[$style.colored]: colored,
[$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">
<svg
:class="[$style.spinner]"
viewBox="0 0 50 50"
xmlns="http://www.w3.org/2000/svg"
<g :class="$style['mag-spinner-spin']">
<g 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="$style['mag-butterflies']"
transform="matrix(1.2958 .098299 -.098299 1.2958 .41715 -18.811)"
>
<circle
:class="[$style.path]"
cx="25"
cy="25"
r="20"
fill="none"
stroke-width="6px"
style="fill: none; stroke: currentColor; stroke-width: 6px"
></circle>
</svg>
</div>
</div>
<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="$style['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>
</g>
</svg>
</template>
<script lang="ts" setup>
import {} from "vue";
const props = withDefaults(
defineProps<{
inline?: boolean;
@ -44,14 +57,12 @@ const props = withDefaults(
colored: true,
mini: false,
em: false,
},
}
);
</script>
<style lang="scss" module>
/* Credit to https://codepen.io/supah/pen/BjYLdW */
@keyframes spin {
@keyframes mag-loading-spin {
0% {
transform: rotate(0deg);
}
@ -60,25 +71,33 @@ const props = withDefaults(
}
}
@keyframes dash {
@keyframes mag-loading-butterfly-appear {
0% {
stroke-dasharray: 1, 150;
stroke-dashoffset: 0;
}
50% {
stroke-dasharray: 90, 150;
stroke-dashoffset: -35;
opacity: 0;
}
100% {
stroke-dasharray: 90, 150;
stroke-dashoffset: -124;
opacity: 1;
}
}
.root {
padding: 32px;
text-align: center;
.mag-butterflies {
opacity: 0;
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;
width: var(--size);
height: var(--size);
--size: 40px;
@ -88,42 +107,22 @@ const props = withDefaults(
&.inline {
display: inline;
padding: 0;
--size: 32px;
}
&.mini {
padding: 16px;
--size: 32px;
margin: 16px;
--size: 16px;
}
&:not(.inline) {
margin-inline: auto;
}
&.em {
display: inline-block;
vertical-align: middle;
padding: 0;
--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>

View File

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

View File

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

View File

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