diff --git a/aleksis/core/frontend/app/apollo.js b/aleksis/core/frontend/app/apollo.js index c1cf08a8b3510f19dae8dc1e7bc8b49f72fd2fb1..10f63e2d5e48e0383af2d07fdd4f33259ee5a119 100644 --- a/aleksis/core/frontend/app/apollo.js +++ b/aleksis/core/frontend/app/apollo.js @@ -63,7 +63,7 @@ const apolloOpts = { error: ({ graphQLErrors, networkError }, vm) => { if (graphQLErrors) { for (let err of graphQLErrors) { - console.error("GraphQL query error:", err.message); + console.error("GraphQL query error in query", err.path.join(".") , ":", err.message); } // Add a snackbar on all errors returned by the GraphQL endpoint // If App is offline, don't add snackbar since only the ping query is active diff --git a/aleksis/core/frontend/components/generic/ObjectOverview.vue b/aleksis/core/frontend/components/generic/ObjectOverview.vue new file mode 100644 index 0000000000000000000000000000000000000000..7f7498de7e575ebab0ca7fe71efbc2883cb7702c --- /dev/null +++ b/aleksis/core/frontend/components/generic/ObjectOverview.vue @@ -0,0 +1,69 @@ +<template> + <div> + <slot name="loading" v-if="$apollo.queries.object.loading"></slot> + <slot v-else-if="object" v-bind="object"></slot> + <error-page + v-else + :shortErrorMessageKey="shortErrorMessageKey" + :longErrorMessageKey="longErrorMessageKey" + /> + </div> +</template> + +<script> + +export default { + name: "ObjectOverview", + props: { + titleAttr: { + type: String, + required: true, + }, + query: { + type: Object, + required: true, + }, + shortErrorMessageKey: { + type: String, + required: false, + default: "network_errors.error_404", + }, + longErrorMessageKey: { + type: String, + required: false, + default: "network_errors.page_not_found", + }, + }, + methods: { + getTitleAttr(obj) { + let tmpObj = obj; + this.titleAttr.split(".").forEach((attr) => {tmpObj = tmpObj[attr]}) + return tmpObj; + } + }, + apollo: { + object() { + return { + query: this.query, + variables() { + if (this.$route.params.id) { + return { + id: this.$route.params.id, + }; + } + return {}; + }, + result({data}) { + if (data && data.object) { + this.$root.$setPageTitle(this.getTitleAttr(data.object)); + } + }, + }; + }, + }, +} +</script> + +<style scoped> + +</style> diff --git a/aleksis/core/frontend/components/person/PersonOverview.vue b/aleksis/core/frontend/components/person/PersonOverview.vue index 978b6689a2e6b777e1e05fbb1859b8651eb3782e..5171a22f0ed594bb849d82d8bab671db7e1207ec 100644 --- a/aleksis/core/frontend/components/person/PersonOverview.vue +++ b/aleksis/core/frontend/components/person/PersonOverview.vue @@ -1,6 +1,9 @@ <template> - <div> - <template v-if="$apollo.queries.person.loading"> + <object-overview + :query="query" + title-attr="fullName" + > + <template #loading> <v-skeleton-loader type="article" /> <v-row> @@ -9,7 +12,7 @@ </v-col> </v-row> </template> - <template v-else-if="person"> + <template v-slot="person"> <detail-view> <template #avatarContent> <person-avatar-clickbox :id="id" /> @@ -189,12 +192,13 @@ </v-row> </detail-view> </template> - </div> + </object-overview> </template> <script> import AdditionalImage from "./AdditionalImage.vue"; import GroupCollection from "../group/GroupCollection.vue"; +import ObjectOverview from "../generic/ObjectOverview.vue"; import PersonActions from "./PersonActions.vue"; import PersonAvatarClickbox from "./PersonAvatarClickbox.vue"; import PersonCollection from "./PersonCollection.vue"; @@ -206,27 +210,15 @@ export default { components: { AdditionalImage, GroupCollection, + ObjectOverview, PersonActions, PersonAvatarClickbox, PersonCollection, }, - apollo: { - person: { + data() { + return { query: gqlPersonOverview, - variables() { - if (this.$route.params.id) { - return { - id: this.$route.params.id, - }; - } - return {}; - }, - result({ data }) { - if (data && data.person) { - this.$root.$setPageTitle(data.person.fullName); - } - }, - }, + } }, props: { id: { diff --git a/aleksis/core/frontend/components/person/personOverview.graphql b/aleksis/core/frontend/components/person/personOverview.graphql index cbf77ef19e48346007d7ebab43f6325a6ded3d3f..8173f6a7d7d74842a267c4d5c0a47d7d3ed97634 100644 --- a/aleksis/core/frontend/components/person/personOverview.graphql +++ b/aleksis/core/frontend/components/person/personOverview.graphql @@ -1,5 +1,5 @@ query person($id: ID) { - person: personByIdOrMe(id: $id) { + object: personByIdOrMe(id: $id) { id username firstName diff --git a/aleksis/core/vite.config.js b/aleksis/core/vite.config.js index fcaf916e41decd4053c9805b8e310b9baa1a2ad8..b459ae1f3b761875a59de70dfdf39cb9a26e32ca 100644 --- a/aleksis/core/vite.config.js +++ b/aleksis/core/vite.config.js @@ -97,6 +97,22 @@ function generateAppImporter(appDetails) { return code; } +/** + * Generate a mapping of esbuild import aliases for apps. + * + * App code locations are discovered by the `aleksis-admin` vite wrapper and passed + * in the django_values hints. + */ +function generateAppAliases(appDetails) { + let aliases = { "aleksis.core": django_values.coreAssetDir }; + + for (const [appPackage, appMeta] of Object.entries(appDetails)) { + aliases[appPackage] = appMeta.assetDir; + } + + return aliases; +} + export default defineConfig({ // root must always be the base directory of the AlekSIS-Core source tree // Changing this will mangle the manifest key of the entrypoint! @@ -293,6 +309,8 @@ export default defineConfig({ alias: { "@": path.resolve(django_values.node_modules), vue: path.resolve(django_values.node_modules + "/vue/dist/vue.esm.js"), + // Add aliases for every app using their package name + ...generateAppAliases(django_values.appDetails), }, }, });