diff --git a/aleksis/core/assets/App.vue b/aleksis/core/assets/App.vue index 4ee81730c0772d05f83e62e78e114611e16c50a5..8e525db874be43819793c96d7d16654712e329cc 100644 --- a/aleksis/core/assets/App.vue +++ b/aleksis/core/assets/App.vue @@ -1,6 +1,6 @@ <template> <v-app v-cloak> - <loading v-if="$apollo.loading && !systemProperties"> </loading> + <loading v-if="$apollo.loading && !systemProperties" splash> </loading> <div v-else> <v-navigation-drawer app v-model="drawer"> <v-list nav dense shaped> diff --git a/aleksis/core/assets/components/Loading.vue b/aleksis/core/assets/components/Loading.vue index 379d336756739e3f1700cca8c2a18a54aee7c193..e0493da6c620a602bc3a45530824c51c96f06988 100644 --- a/aleksis/core/assets/components/Loading.vue +++ b/aleksis/core/assets/components/Loading.vue @@ -1,9 +1,29 @@ <template> - <div class="d-flex justify-center align-center progress-container"> + <div + v-if="splash" + id="logo-container" + > + <img + src="/logo" + alt="Logo" + id="logo" + width="600" + > + <div class="lds-ellipsis"> + <div></div> + <div></div> + <div></div> + <div></div> + </div> + </div> + <div + v-else + class="d-flex justify-center align-center progress-container" + > <v-progress-circular - indeterminate - color="primary" - :size="60" + indeterminate + color="primary" + :size="60" ></v-progress-circular> </div> </template> @@ -29,4 +49,81 @@ export default { bottom: 10%; z-index: 1000; } + +#logo { + width: 100%; +} + +#logo-container { + width: min(80vw, 600px); + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + text-align: center; +} + +.lds-ellipsis { + display: inline-block; + position: relative; + width: 80px; + height: 80px; +} + +.lds-ellipsis div { + position: absolute; + top: 33px; + width: 13px; + height: 13px; + border-radius: 50%; + background: v-bind("$vuetify.theme.currentTheme.primary"); + animation-timing-function: cubic-bezier(0, 1, 1, 0); +} + +.lds-ellipsis div:nth-child(1) { + left: 8px; + animation: lds-ellipsis1 0.6s infinite; +} + +.lds-ellipsis div:nth-child(2) { + left: 8px; + animation: lds-ellipsis2 0.6s infinite; +} + +.lds-ellipsis div:nth-child(3) { + left: 32px; + animation: lds-ellipsis2 0.6s infinite; +} + +.lds-ellipsis div:nth-child(4) { + left: 56px; + animation: lds-ellipsis3 0.6s infinite; +} + +@keyframes lds-ellipsis1 { + 0% { + transform: scale(0); + } + 100% { + transform: scale(1); + } +} + +@keyframes lds-ellipsis3 { + 0% { + transform: scale(1); + } + 100% { + transform: scale(0); + } +} + +@keyframes lds-ellipsis2 { + 0% { + transform: translate(0, 0); + } + 100% { + transform: translate(24px, 0); + } +} </style> diff --git a/aleksis/core/assets/components/generic/AvatarClickbox.vue b/aleksis/core/assets/components/generic/AvatarClickbox.vue new file mode 100644 index 0000000000000000000000000000000000000000..faa2ba173e5e9c4dc08d6048c133adf1cad30300 --- /dev/null +++ b/aleksis/core/assets/components/generic/AvatarClickbox.vue @@ -0,0 +1,32 @@ +<template> + <v-dialog v-model="overlay" max-width="fit-content" max-height="fit-content"> + <template #activator="{ on, attrs }"> + <v-card class="rounded-circle"> + <v-responsive :aspect-ratio="1" v-bind="attrs" v-on="on"> + <slot name="activator" /> + </v-responsive> + </v-card> + </template> + <div class="inDialog"> + <slot class="inDialog" /> + </div> + </v-dialog> + </template> + + <script> + export default { + name: "AvatarClickBox", + data: () => ({ + overlay: false, + }), + }; + </script> + + <style scoped> + .inDialog { + /* FIXME: find a way to enlarge image */ + max-height: 80vmin; + width: 80vmin; + } + </style> + \ No newline at end of file diff --git a/aleksis/core/assets/components/generic/DetailView.vue b/aleksis/core/assets/components/generic/DetailView.vue index 843dacefab4c49f2c2f827c42b4b56f7aa49b9b5..dfe6e482bf7694b53ffdf27d9241867e4c543b9f 100644 --- a/aleksis/core/assets/components/generic/DetailView.vue +++ b/aleksis/core/assets/components/generic/DetailView.vue @@ -25,11 +25,10 @@ </v-col> <v-col order="1" order-sm="last" class="ms-5"> - <div - class="d-flex gap justify-md-end flex-column-reverse flex-md-row align-end align-md-center" - > - <slot name="actions" /> - </div> + <slot + name="actions" + v-bind:classes="'d-flex gap justify-md-end flex-column-reverse flex-md-row align-end align-md-center'" + /> </v-col> </v-row> <slot /> diff --git a/aleksis/core/assets/components/person/AvatarClickBox.vue b/aleksis/core/assets/components/person/AvatarClickBox.vue deleted file mode 100644 index fc60bd301db1d8df5b23639cafa2708f46550033..0000000000000000000000000000000000000000 --- a/aleksis/core/assets/components/person/AvatarClickBox.vue +++ /dev/null @@ -1,39 +0,0 @@ -<template> - <v-dialog v-model="overlay" max-width="fit-content" max-height="fit-content"> - <template #activator="{ on, attrs }"> - <v-card class="rounded-circle"> - <v-responsive :aspect-ratio="1" v-bind="attrs" v-on="on"> - <avatar-content :id="id" class="rounded-circle" /> - </v-responsive> - </v-card> - </template> - <avatar-content :id="id" contain class="inDialog" /> - </v-dialog> -</template> - -<script> -import AvatarContent from "./AvatarContent.vue"; - -export default { - name: "AvatarClickBox", - components: { AvatarContent }, - data: () => ({ - overlay: false, - }), - props: { - id: { - type: String, - required: false, - default: "", - }, - }, -}; -</script> - -<style scoped> -.inDialog { - /* FIXME: find a way to enlarge image */ - max-height: 80vmin; - width: 80vmin; -} -</style> diff --git a/aleksis/core/assets/components/person/PersonAvatarClickbox.vue b/aleksis/core/assets/components/person/PersonAvatarClickbox.vue new file mode 100644 index 0000000000000000000000000000000000000000..1a5de2a12879d37716305e85d50304b2fa75f4c1 --- /dev/null +++ b/aleksis/core/assets/components/person/PersonAvatarClickbox.vue @@ -0,0 +1,33 @@ +<template> + <avatar-clickbox> + <template #activator> + <avatar-content :id="id" class="rounded-circle" /> + </template> + <avatar-content :id="id" contain /> + </avatar-clickbox> +</template> + +<script> +import AvatarClickbox from "../generic/AvatarClickbox.vue"; +import AvatarContent from "./AvatarContent.vue"; + +export default { + name: "PersonAvatarClickBox", + components: { + AvatarClickbox, + AvatarContent, + }, + data: () => ({ + overlay: false, + }), + props: { + id: { + type: String, + required: false, + default: "", + }, + }, +}; +</script> + +<style scoped></style> diff --git a/aleksis/core/assets/components/person/PersonOverview.vue b/aleksis/core/assets/components/person/PersonOverview.vue index 43fe4620f2414bbecb6abf7207aed01cf04311a5..62b4962fe9385a8dbea2803ad459a3b786408e17 100644 --- a/aleksis/core/assets/components/person/PersonOverview.vue +++ b/aleksis/core/assets/components/person/PersonOverview.vue @@ -13,7 +13,7 @@ <template v-else-if="data && data.person"> <detail-view> <template #avatarContent> - <avatar-click-box :id="id" /> + <person-avatar-clickbox :id="id" /> </template> <template #title> @@ -24,8 +24,8 @@ {{ data.person.username }} </template> - <template #actions> - <person-actions :id="data.person.id" /> + <template #actions="{ classes }"> + <person-actions :class="classes" :id="data.person.id" /> </template> <div class="text-center my-5" v-text="data.person.description"></div> @@ -204,20 +204,20 @@ <script> import AdditionalImage from "./AdditionalImage.vue"; -import AvatarClickBox from "./AvatarClickBox.vue"; import DetailView from "../generic/DetailView.vue"; import GroupCollection from "../group/GroupCollection.vue"; import PersonActions from "./PersonActions.vue"; +import PersonAvatarClickbox from "./PersonAvatarClickbox.vue"; import PersonCollection from "./PersonCollection.vue"; export default { name: "PersonOverview", components: { AdditionalImage, - AvatarClickBox, DetailView, GroupCollection, PersonActions, + PersonAvatarClickbox, PersonCollection, }, props: { diff --git a/aleksis/core/templates/core/partials/splash_screen.html b/aleksis/core/templates/core/partials/splash_screen.html index 00e01649894a2a83c7d0129580034350fed77c3a..243468a47b5634b9797512ff7aa1887378f9f588 100644 --- a/aleksis/core/templates/core/partials/splash_screen.html +++ b/aleksis/core/templates/core/partials/splash_screen.html @@ -1,5 +1,5 @@ {% load static any_js i18n %} -{% include_css "Roboto300" %} +{% include_css "Roboto400" %} {% static "img/aleksis-banner.svg" as aleksis_banner %} <div id="logo-container"> <img @@ -8,14 +8,11 @@ id="logo" width="600" > - <div id="text"> - <h1>{{ request.site.preferences.general__title }}</h1> - <div class="lds-ellipsis"> - <div></div> - <div></div> - <div></div> - <div></div> - </div> + <div class="lds-ellipsis"> + <div></div> + <div></div> + <div></div> + <div></div> </div> <noscript> {% blocktrans %} @@ -35,17 +32,12 @@ top: 50%; left: 50%; transform: translate(-50%, -50%); + text-align: center; } - #text { - display: flex; - justify-content: space-around; - flex-wrap: wrap; - } - - h1 { + noscript { font-family: Roboto, sans-serif; - font-weight: 300; + font-weight: 400; } .lds-ellipsis { diff --git a/aleksis/core/urls.py b/aleksis/core/urls.py index a8cee0d037d7b06540a224a7a69c93f781fd74d2..c25861a504419b364b6f96d72769d658ed1e01c2 100644 --- a/aleksis/core/urls.py +++ b/aleksis/core/urls.py @@ -24,6 +24,7 @@ urlpatterns = [ path(settings.MEDIA_URL.removeprefix("/"), include("titofisto.urls")), path("__icons__/", include("dj_iconify.urls")), path("graphql/", csrf_exempt(GraphQLView.as_view(graphiql=True)), name="graphql"), + path("logo", views.LogoView.as_view(), name="logo"), path( "django/", include( diff --git a/aleksis/core/views.py b/aleksis/core/views.py index 29d5ca002594e9ea9a989330c66d239e3eb856b3..ede1cd0e41bbc0d84104eaefdbf3e7dbd13f1b1a 100644 --- a/aleksis/core/views.py +++ b/aleksis/core/views.py @@ -23,6 +23,7 @@ from django.http import ( ) from django.shortcuts import get_object_or_404, redirect, render from django.template import loader +from django.templatetags.static import static from django.urls import reverse, reverse_lazy from django.utils import timezone from django.utils.decorators import method_decorator @@ -137,6 +138,16 @@ from .util.forms import PreferenceLayout from .util.pdf import render_pdf +class LogoView(View): + def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse: + image = request.site.preferences["theme__logo"] + if image: + image = image.url + else: + image = static("img/aleksis-banner.svg") + return redirect(image) + + class RenderPDFView(TemplateView): """View to render a PDF file from a template.