diff --git a/aleksis/core/assets/components/generic/DetailView.vue b/aleksis/core/assets/components/generic/DetailView.vue new file mode 100644 index 0000000000000000000000000000000000000000..843dacefab4c49f2c2f827c42b4b56f7aa49b9b5 --- /dev/null +++ b/aleksis/core/assets/components/generic/DetailView.vue @@ -0,0 +1,55 @@ +<template> + <div> + <v-row class="align-center"> + <v-col + v-if="!noAvatar" + cols="5" + sm="4" + md="3" + lg="2" + xl="1" + order="first" + max-width="220px" + > + <slot name="avatarContent" /> + </v-col> + + <v-col order="last" order-sm="1" cols="12" sm=""> + <h1> + <slot name="title" /> + </h1> + + <div class="text-h5 grey--text text--darken-2"> + <slot name="subtitle" /> + </div> + </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> + </v-col> + </v-row> + <slot /> + </div> +</template> + +<script> +export default { + name: "DetailView", + props: { + noAvatar: { + type: Boolean, + required: false, + }, + }, +}; +</script> + +<style scoped> +.gap { + gap: 0.5rem; +} +</style> diff --git a/aleksis/core/assets/components/generic/ListView.vue b/aleksis/core/assets/components/generic/ListView.vue new file mode 100644 index 0000000000000000000000000000000000000000..7a78a7643b2f08846f341677eb82074eda31013e --- /dev/null +++ b/aleksis/core/assets/components/generic/ListView.vue @@ -0,0 +1,28 @@ +<template> + <detail-view no-avatar> + <template #title> + <slot name="title" /> + </template> + + <template #actions> + <slot name="actions" /> + </template> + + <slot name="filter" /> + <slot /> + </detail-view> +</template> + +<script> +import DetailView from "./DetailView.vue"; +export default { + name: "ListView", + components: { + DetailView, + }, +} +</script> + +<style scoped> + +</style> diff --git a/aleksis/core/assets/components/group/GroupList.vue b/aleksis/core/assets/components/group/GroupCollection.vue similarity index 100% rename from aleksis/core/assets/components/group/GroupList.vue rename to aleksis/core/assets/components/group/GroupCollection.vue diff --git a/aleksis/core/assets/components/person/PersonActions.vue b/aleksis/core/assets/components/person/PersonActions.vue index d042cf7cd08d80e7f7c7510227583c1316f8a622..2b0bc7bc6e55d985b6c66c3d7b9dbc92b13344b4 100644 --- a/aleksis/core/assets/components/person/PersonActions.vue +++ b/aleksis/core/assets/components/person/PersonActions.vue @@ -1,20 +1,17 @@ <template> <ApolloQuery :query="require('./personActions.graphql')" :variables="{ id }"> <template #default="{ result: { error, data, loading } }"> - <div - class="d-flex gap justify-md-end flex-column-reverse flex-md-row align-end align-md-center" - > - <v-skeleton-loader v-if="loading" type="actions" /> - <template v-else-if="data && data.person && data.person.id"> - <v-btn + <v-skeleton-loader v-if="loading" type="actions"/> + <template v-else-if="data && data.person && data.person.id"> + <v-btn v-if="data.person.canEditPerson" color="primary" :to="{ name: 'core.editPerson', params: { id: data.person.id } }" - > - <v-icon left>$edit</v-icon> - {{ $t("actions.edit") }} - </v-btn> - <v-btn + > + <v-icon left>$edit</v-icon> + {{ $t("actions.edit") }} + </v-btn> + <v-btn v-if="data.person.canChangePersonPreferences" color="secondary" outlined @@ -23,74 +20,82 @@ name: 'core.preferencesPersonByPk', params: { pk: data.person.id }, }" - > - <v-icon left>$preferences</v-icon> - {{ $t("preferences.person.change_preferences") }} - </v-btn> + > + <v-icon left>$preferences</v-icon> + {{ $t("preferences.person.change_preferences") }} + </v-btn> - <v-menu> - <template #activator="{ on, attrs }"> - <v-btn outlined text v-bind="attrs" v-on="on"> - <v-icon center>mdi-dots-horizontal</v-icon> - </v-btn> - </template> - <v-list> - <v-list-item + <v-menu + v-if=" + data.person.canImpersonatePerson || + data.person.canInvitePerson || + data.person.canDeletePerson + " + > + <template #activator="{ on, attrs }"> + <v-btn outlined text v-bind="attrs" v-on="on"> + <v-icon center>mdi-dots-horizontal</v-icon> + </v-btn> + </template> + <v-list> + <v-list-item v-if="data.person.canImpersonatePerson" :to="{ name: 'impersonate.impersonateByUserPk', params: { uid: data.person.userid }, query: { next: $route.path }, }" - > - <v-list-item-icon> - <v-icon>mdi-account-box-outline</v-icon> - </v-list-item-icon> - <v-list-item-content> - <v-list-item-title>{{ + > + <v-list-item-icon> + <v-icon>mdi-account-box-outline</v-icon> + </v-list-item-icon> + <v-list-item-content> + <v-list-item-title>{{ $t("person.impersonation.impersonate") - }}</v-list-item-title> - </v-list-item-content> - </v-list-item> + }} + </v-list-item-title> + </v-list-item-content> + </v-list-item> - <v-list-item + <v-list-item v-if="data.person.canInvitePerson" :to="{ name: 'core.invitePerson', params: { id: data.person.id }, }" - > - <v-list-item-icon> - <v-icon>mdi-account-plus-outline</v-icon> - </v-list-item-icon> - <v-list-item-content> - <v-list-item-title>{{ + > + <v-list-item-icon> + <v-icon>mdi-account-plus-outline</v-icon> + </v-list-item-icon> + <v-list-item-content> + <v-list-item-title>{{ $t("person.invite") - }}</v-list-item-title> - </v-list-item-content> - </v-list-item> + }} + </v-list-item-title> + </v-list-item-content> + </v-list-item> - <v-list-item + <v-list-item v-if="data.person.canDeletePerson" :to="{ name: 'core.deletePerson', params: { id: data.person.id }, }" class="error--text" - > - <v-list-item-icon> - <v-icon color="error">mdi-delete</v-icon> - </v-list-item-icon> - <v-list-item-content> - <v-list-item-title>{{ + > + <v-list-item-icon> + <v-icon color="error">mdi-delete</v-icon> + </v-list-item-icon> + <v-list-item-content> + <v-list-item-title>{{ $t("person.delete") - }}</v-list-item-title> - </v-list-item-content> - </v-list-item> - </v-list> - </v-menu> - </template> - </div> + }} + </v-list-item-title> + </v-list-item-content> + </v-list-item> + </v-list> + </v-menu> + </template> </template> </ApolloQuery> </template> @@ -108,7 +113,5 @@ export default { </script> <style scoped> -.gap { - gap: 0.5rem; -} + </style> diff --git a/aleksis/core/assets/components/person/PersonList.vue b/aleksis/core/assets/components/person/PersonCollection.vue similarity index 100% rename from aleksis/core/assets/components/person/PersonList.vue rename to aleksis/core/assets/components/person/PersonCollection.vue diff --git a/aleksis/core/assets/components/person/PersonOverview.vue b/aleksis/core/assets/components/person/PersonOverview.vue index dd55ed7181d72c10a117dc2c96619852f8325efc..d7990c3ff6f17fb7df7e3f60cb30f0f24c5a4451 100644 --- a/aleksis/core/assets/components/person/PersonOverview.vue +++ b/aleksis/core/assets/components/person/PersonOverview.vue @@ -11,183 +11,176 @@ </v-row> </template> <template v-else-if="data && data.person"> - <v-row class="align-center"> - <v-col - cols="5" - sm="4" - md="3" - lg="2" - xl="1" - order="first" - max-width="220px" - > + <detail-view> + <template #avatarContent> <avatar-click-box :id="id" /> - </v-col> - <v-col order="last" order-sm="1" cols="12" sm=""> - <h1>{{ data.person.firstName }} {{ data.person.lastName }}</h1> - <div - v-if="data.person.username" - class="text-h5 grey--text text--darken-2" - > - {{ data.person.username }} - </div> - </v-col> - - <v-col order="1" order-sm="last" class="ms-5"> - <person-actions :id="data.person.id" /> - </v-col> - </v-row> - - <div class="text-center my-5" v-text="data.person.description"></div> + </template> - <v-row> - <v-col cols="12" lg="4"> - <v-card class="mb-6"> - <v-card-title>{{ $t("person.details") }}</v-card-title> - - <v-list two-line> - <v-list-item> - <v-list-item-icon> - <v-icon> mdi-account-outline </v-icon> - </v-list-item-icon> - - <v-list-item-content> - <v-list-item-title> - {{ data.person.firstName }} - {{ data.person.additionalName }} - {{ data.person.lastName }} - </v-list-item-title> - </v-list-item-content> - </v-list-item> - <v-divider inset /> - - <v-list-item> - <v-list-item-icon> - <v-icon> mdi-human-non-binary </v-icon> - </v-list-item-icon> - - <v-list-item-content> - <v-list-item-title> - {{ data.person.sex || "–" }} - </v-list-item-title> - </v-list-item-content> - </v-list-item> - <v-divider inset /> - - <v-list-item> - <v-list-item-icon> - <v-icon> mdi-map-marker-outline </v-icon> - </v-list-item-icon> - - <v-list-item-content> - <v-list-item-title - >{{ data.person.street || "–" }} - {{ data.person.housenumber }}</v-list-item-title - > - <v-list-item-subtitle - >{{ data.person.postalCode }} - {{ data.person.place }}</v-list-item-subtitle - > - </v-list-item-content> - </v-list-item> - <v-divider inset /> - - <v-list-item :href="'tel:' + data.person.phoneNumber"> - <v-list-item-icon> - <v-icon> mdi-phone-outline </v-icon> - </v-list-item-icon> - - <v-list-item-content> - <v-list-item-title>{{ - data.person.phoneNumber || "–" - }}</v-list-item-title> - <v-list-item-subtitle>{{ - $t("person.home") - }}</v-list-item-subtitle> - </v-list-item-content> - </v-list-item> - - <v-list-item :href="'tel:' + data.person.mobileNumber"> - <v-list-item-action></v-list-item-action> - - <v-list-item-content> - <v-list-item-title>{{ - data.person.mobileNumber || "–" - }}</v-list-item-title> - <v-list-item-subtitle>{{ - $t("person.mobile") - }}</v-list-item-subtitle> - </v-list-item-content> - </v-list-item> - <v-divider inset /> - - <v-list-item :href="'mailto:' + data.person.email"> - <v-list-item-icon> - <v-icon> mdi-email-outline </v-icon> - </v-list-item-icon> - - <v-list-item-content> - <v-list-item-title> - {{ data.person.email || "–" }} - </v-list-item-title> - </v-list-item-content> - </v-list-item> - <v-divider inset /> - - <v-list-item> - <v-list-item-icon> - <v-icon> mdi-cake-variant-outline </v-icon> - </v-list-item-icon> - - <v-list-item-content> - <v-list-item-title>{{ - !!data.person.dateOfBirth - ? $d(new Date(data.person.dateOfBirth), "short") - : "–" - }}</v-list-item-title> - <v-list-item-subtitle>{{ - data.person.placeOfBirth - }}</v-list-item-subtitle> - </v-list-item-content> - </v-list-item> - </v-list> - </v-card> - - <additional-image :src="data.person.secondaryImageUrl" /> - </v-col> + <template #title> + {{ data.person.firstName }} {{ data.person.lastName }} + </template> - <v-col - cols="12" - md="6" - lg="4" - v-if="data.person.children.length || data.person.guardians.length" - > - <v-card v-if="data.person.children.length" class="mb-6"> - <v-card-title>{{ $t("person.children") }}</v-card-title> - <person-list :persons="data.person.children" /> - </v-card> - <v-card v-if="data.person.guardians.length"> - <v-card-title>{{ $t("person.guardians") }}</v-card-title> - <person-list :persons="data.person.guardians" /> - </v-card> - </v-col> + <template #subtitle> + {{ data.person.username }} + </template> - <v-col - cols="12" - md="6" - lg="4" - v-if="data.person.memberOf.length || data.person.ownerOf.length" - > - <v-card v-if="data.person.memberOf.length" class="mb-6"> - <v-card-title>{{ $t("group.title_plural") }}</v-card-title> - <group-list :groups="data.person.memberOf" /> - </v-card> - <v-card v-if="data.person.ownerOf.length"> - <v-card-title>{{ $t("group.ownership") }}</v-card-title> - <group-list :groups="data.person.ownerOf" /> - </v-card> - </v-col> - </v-row> + <template #actions> + <person-actions :id="data.person.id" /> + </template> + + <div class="text-center my-5" v-text="data.person.description"></div> + + <v-row> + <v-col cols="12" lg="4"> + <v-card class="mb-6"> + <v-card-title>{{ $t("person.details") }}</v-card-title> + + <v-list two-line> + <v-list-item> + <v-list-item-icon> + <v-icon> mdi-account-outline</v-icon> + </v-list-item-icon> + + <v-list-item-content> + <v-list-item-title> + {{ data.person.firstName }} + {{ data.person.additionalName }} + {{ data.person.lastName }} + </v-list-item-title> + </v-list-item-content> + </v-list-item> + <v-divider inset /> + + <v-list-item> + <v-list-item-icon> + <v-icon> mdi-human-non-binary</v-icon> + </v-list-item-icon> + + <v-list-item-content> + <v-list-item-title> + {{ data.person.sex || "–" }} + </v-list-item-title> + </v-list-item-content> + </v-list-item> + <v-divider inset /> + + <v-list-item> + <v-list-item-icon> + <v-icon> mdi-map-marker-outline</v-icon> + </v-list-item-icon> + + <v-list-item-content> + <v-list-item-title + >{{ data.person.street || "–" }} + {{ data.person.housenumber }} + </v-list-item-title> + <v-list-item-subtitle + >{{ data.person.postalCode }} + {{ data.person.place }} + </v-list-item-subtitle> + </v-list-item-content> + </v-list-item> + <v-divider inset /> + + <v-list-item :href="'tel:' + data.person.phoneNumber"> + <v-list-item-icon> + <v-icon> mdi-phone-outline</v-icon> + </v-list-item-icon> + + <v-list-item-content> + <v-list-item-title + >{{ data.person.phoneNumber || "–" }} + </v-list-item-title> + <v-list-item-subtitle + >{{ $t("person.home") }} + </v-list-item-subtitle> + </v-list-item-content> + </v-list-item> + + <v-list-item :href="'tel:' + data.person.mobileNumber"> + <v-list-item-action></v-list-item-action> + + <v-list-item-content> + <v-list-item-title + >{{ data.person.mobileNumber || "–" }} + </v-list-item-title> + <v-list-item-subtitle + >{{ $t("person.mobile") }} + </v-list-item-subtitle> + </v-list-item-content> + </v-list-item> + <v-divider inset /> + + <v-list-item :href="'mailto:' + data.person.email"> + <v-list-item-icon> + <v-icon> mdi-email-outline</v-icon> + </v-list-item-icon> + + <v-list-item-content> + <v-list-item-title> + {{ data.person.email || "–" }} + </v-list-item-title> + </v-list-item-content> + </v-list-item> + <v-divider inset /> + + <v-list-item> + <v-list-item-icon> + <v-icon> mdi-cake-variant-outline</v-icon> + </v-list-item-icon> + + <v-list-item-content> + <v-list-item-title + >{{ + !!data.person.dateOfBirth + ? $d(new Date(data.person.dateOfBirth), "short") + : "–" + }} + </v-list-item-title> + <v-list-item-subtitle + >{{ data.person.placeOfBirth }} + </v-list-item-subtitle> + </v-list-item-content> + </v-list-item> + </v-list> + </v-card> + + <additional-image :src="data.person.secondaryImageUrl" /> + </v-col> + + <v-col + cols="12" + md="6" + lg="4" + v-if="data.person.children.length || data.person.guardians.length" + > + <v-card v-if="data.person.children.length" class="mb-6"> + <v-card-title>{{ $t("person.children") }}</v-card-title> + <person-collection :persons="data.person.children" /> + </v-card> + <v-card v-if="data.person.guardians.length"> + <v-card-title>{{ $t("person.guardians") }}</v-card-title> + <person-collection :persons="data.person.guardians" /> + </v-card> + </v-col> + + <v-col + cols="12" + md="6" + lg="4" + v-if="data.person.memberOf.length || data.person.ownerOf.length" + > + <v-card v-if="data.person.memberOf.length" class="mb-6"> + <v-card-title>{{ $t("group.title_plural") }}</v-card-title> + <group-collection :groups="data.person.memberOf" /> + </v-card> + <v-card v-if="data.person.ownerOf.length"> + <v-card-title>{{ $t("group.ownership") }}</v-card-title> + <group-collection :groups="data.person.ownerOf" /> + </v-card> + </v-col> + </v-row> + </detail-view> </template> </template> </ApolloQuery> @@ -196,18 +189,20 @@ <script> import AdditionalImage from "./AdditionalImage.vue"; import AvatarClickBox from "./AvatarClickBox.vue"; -import GroupList from "../group/GroupList.vue"; +import DetailView from "../generic/DetailView.vue"; +import GroupCollection from "../group/GroupCollection.vue"; import PersonActions from "./PersonActions.vue"; -import PersonList from "./PersonList.vue"; +import PersonCollection from "./PersonCollection.vue"; export default { name: "PersonOverview", components: { AdditionalImage, AvatarClickBox, - GroupList, + DetailView, + GroupCollection, PersonActions, - PersonList, + PersonCollection, }, props: { id: { diff --git a/aleksis/core/management/commands/convert_urls_to_routes.py b/aleksis/core/management/commands/convert_urls_to_routes.py new file mode 100644 index 0000000000000000000000000000000000000000..71404415d6c536a8be1e59a80d0be977d3a4be70 --- /dev/null +++ b/aleksis/core/management/commands/convert_urls_to_routes.py @@ -0,0 +1,93 @@ +from re import sub + +from django.apps import apps +from django.core.management.base import BaseCommand, CommandError + +from aleksis.core.util.core_helpers import get_app_module + + +def camelcase(value: str) -> str: + """Convert a string to camelcase.""" + titled = value.replace("_", " ").title().replace(" ", "") + return titled[0].lower() + titled[1:] + + +class Command(BaseCommand): + help = "Convert Django URLs for an app into vue-router routes" # noqa + + def add_arguments(self, parser): + parser.add_argument("app", type=str) + + def handle(self, *args, **options): + app = options["app"] + app_camel_case = camelcase(app) + + app_config = apps.get_app_config(app) + app_config_name = f"{app_config.__module__}.{app_config.__class__.__name__}" + + # Import urls from app + urls = get_app_module(app_config_name, "urls") + if not urls: + raise CommandError(f"No url patterns found in app {app}") + urlpatterns = urls.urlpatterns + + # Import menu from app and structure as dict by url name + menus = get_app_module(app_config_name, "menus") + menu_by_urls = {} + if "NAV_MENU_CORE" in menus.MENUS: + menu = menus.MENUS["NAV_MENU_CORE"] + menu_by_urls = {m["url"]: m for m in menu} + + for menu_item in menu: + if "submenu" in menu_item: + for submenu_item in menu_item["submenu"]: + menu_by_urls[submenu_item["url"]] = submenu_item + + for url in urlpatterns: + # Convert route name and url pattern to vue-router format + menu = menu_by_urls[url.name] if url.name in menu_by_urls else None + route_name = f"{app_camel_case}.{camelcase(url.name)}" + url_pattern = url.pattern._route + url_pattern = sub(r"<(?P<val>\w+)>", r":\g<val>", url_pattern) + + # Start building route + route = "{\n" + route += f' path: "{url_pattern}",\n' + route += ' component: () => import("./components/LegacyBaseTemplate.vue"),\n' + route += f' name: "{route_name}",\n' + + if menu: + # Convert icon to Vuetify format + icon = None + if menu.get("vuetify_icon"): + icon = menu["vuetify_icon"] + elif menu.get("svg_icon"): + icon = menu["svg_icon"].replace(":", "-") + elif menu.get("icon"): + icon = "mdi-" + menu["icon"] + + if icon: + icon = icon.replace("_", "-") + + # Get permission for menu item + permission = None + if menu.get("validators"): + possible_validators = [ + v + for v in menu["validators"] + if v[0] == "aleksis.core.util.predicates.permission_validator" + ] + if possible_validators: + permission = possible_validators[0][1] + + route += " meta: {{\n" + route += " inMenu: true,\n" + route += f' titleKey: "{menu["name"]}", // Needs manual work\n' + if icon: + route += f' icon: "{icon}",\n' + if permission: + route += f' permission: "{permission}",\n' + route += " }},\n" + route += "}," + + print(route) diff --git a/aleksis/core/schema/person.py b/aleksis/core/schema/person.py index b51fa08c2da1032210d5f641a8f309190b1b49ff..821cade46c4ca76539e284bd1311a6992869e782 100644 --- a/aleksis/core/schema/person.py +++ b/aleksis/core/schema/person.py @@ -47,6 +47,71 @@ class PersonType(DjangoObjectType): can_impersonate_person = graphene.Boolean() can_invite_person = graphene.Boolean() + def resolve_street(root, info, **kwargs): # noqa + if info.context.user.has_perm("core.view_address_rule", root): + return root.street + return None + + def resolve_housenumber(root, info, **kwargs): # noqa + if info.context.user.has_perm("core.view_address_rule", root): + return root.housenumber + return None + + def resolve_postal_code(root, info, **kwargs): # noqa + if info.context.user.has_perm("core.view_address_rule", root): + return root.postal_code + return None + + def resolve_place(root, info, **kwargs): # noqa + if info.context.user.has_perm("core.view_address_rule", root): + return root.place + return None + + def resolve_phone_number(root, info, **kwargs): # noqa + if info.context.user.has_perm("core.view_contact_details_rule", root): + return root.phone_number + return None + + def resolve_mobile_number(root, info, **kwargs): # noqa + if info.context.user.has_perm("core.view_contact_details_rule", root): + return root.mobile_number + return None + + def resolve_email(root, info, **kwargs): # noqa + if info.context.user.has_perm("core.view_contact_details_rule", root): + return root.email + return None + + def resolve_date_of_birth(root, info, **kwargs): # noqa + if info.context.user.has_perm("core.view_personal_details_rule", root): + return root.date_of_birth + return None + + def resolve_place_of_birth(root, info, **kwargs): # noqa + if info.context.user.has_perm("core.view_personal_details_rule", root): + return root.place_of_birth + return None + + def resolve_children(root, info, **kwargs): # noqa + if info.context.user.has_perm("core.view_personal_details_rule", root): + return root.children.all() + return [] + + def resolve_guardians(root, info, **kwargs): # noqa + if info.context.user.has_perm("core.view_personal_details_rule", root): + return root.guardians.all() + return [] + + def resolve_member_of(root, info, **kwargs): # noqa + if info.context.user.has_perm("core.view_person_groups_rule", root): + return root.member_of.all() + return [] + + def resolve_owner_of(root, info, **kwargs): # noqa + if info.context.user.has_perm("core.view_person_groups_rule", root): + return root.owner_of.all() + return [] + def resolve_username(root, info, **kwargs): # noqa return root.user.username if root.user else None diff --git a/aleksis/core/templates/core/partials/pure_css_loader.html b/aleksis/core/templates/core/partials/pure_css_loader.html deleted file mode 100644 index 581752415a29448fdff998861530ac51a6877622..0000000000000000000000000000000000000000 --- a/aleksis/core/templates/core/partials/pure_css_loader.html +++ /dev/null @@ -1,60 +0,0 @@ -{# Loader by https://loading.io/css/ under CC0 licence #} - -<style> - .wrapper { - width: 100vw; - height: 100vh; - display: flex; - align-items: center; - justify-content: center; - } - - .lds-ring { - display: inline-block; - position: relative; - width: 80px; - height: 80px; - } - - .lds-ring div { - box-sizing: border-box; - display: block; - position: absolute; - width: 64px; - height: 64px; - margin: 8px; - border: 4px solid{{ request.site.preferences.theme__primary }}; - border-radius: 50%; - animation: lds-ring 1.2s cubic-bezier(0.5, 0, 0.5, 1) infinite; - border-color: {{ request.site.preferences.theme__primary }} transparent transparent transparent; - } - - .lds-ring div:nth-child(1) { - animation-delay: -0.45s; - } - - .lds-ring div:nth-child(2) { - animation-delay: -0.3s; - } - - .lds-ring div:nth-child(3) { - animation-delay: -0.15s; - } - - @keyframes lds-ring { - 0% { - transform: rotate(0deg); - } - 100% { - transform: rotate(360deg); - } - } -</style> -<div class="wrapper"> - <div class="lds-ring"> - <div></div> - <div></div> - <div></div> - <div></div> - </div> -</div> diff --git a/aleksis/core/templates/core/partials/splash_screen.html b/aleksis/core/templates/core/partials/splash_screen.html new file mode 100644 index 0000000000000000000000000000000000000000..00e01649894a2a83c7d0129580034350fed77c3a --- /dev/null +++ b/aleksis/core/templates/core/partials/splash_screen.html @@ -0,0 +1,115 @@ +{% load static any_js i18n %} +{% include_css "Roboto300" %} +{% static "img/aleksis-banner.svg" as aleksis_banner %} +<div id="logo-container"> + <img + src="{% firstof request.site.preferences.theme__logo.url aleksis_banner %}" + alt="{{ request.site.preferences.general__title }} – Logo" + 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> + <noscript> + {% blocktrans %} + This webbrowser doesn't support JavaScript, or it's execution is blocked. Please use another browser to continue. + {% endblocktrans %} + </noscript> +</div> + +<style> + #logo { + width: 100%; + } + + #logo-container { + width: min(80vw, 600px); + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + } + + #text { + display: flex; + justify-content: space-around; + flex-wrap: wrap; + } + + h1 { + font-family: Roboto, sans-serif; + font-weight: 300; + } + + .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: {{ request.site.preferences.theme__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/templates/core/vue_index.html b/aleksis/core/templates/core/vue_index.html index ea4f99b042be25cfd93ee463f5278da80b4042bd..f362963d9d137f3ab3f4886116f0e693018e843f 100644 --- a/aleksis/core/templates/core/vue_index.html +++ b/aleksis/core/templates/core/vue_index.html @@ -22,7 +22,7 @@ <body> <main id="app"> <!-- HTML and CSS in #app will be replaced by vue --> - {% include "core/partials/pure_css_loader.html" %} + {% include "core/partials/splash_screen.html" %} <app ref="aleksisApp"></app> </main>