Skip to content

Commit

Permalink
chore: add a view entity/id and a button to cite it in the entity-det…
Browse files Browse the repository at this point in the history
…ailviews
  • Loading branch information
oliviareichl committed Aug 12, 2024
1 parent 476a039 commit 3237f0a
Show file tree
Hide file tree
Showing 6 changed files with 228 additions and 6 deletions.
2 changes: 1 addition & 1 deletion components/entity-geo-map.vue
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ const features = computed(() => {
</script>

<template>
<Card class="h-full overflow-hidden">
<Card class="h-full">
<VisualisationContainer v-slot="{ height, width }">
<GeoMap
v-if="height && width"
Expand Down
35 changes: 33 additions & 2 deletions components/entity-primary-details.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<script setup lang="ts">
import { MapPinIcon } from "lucide-vue-next";
import { CheckIcon, CopyIcon, MapPinIcon } from "lucide-vue-next";
import CustomPrimaryDetailsActor from "@/components/custom-primary-details-actor.vue";
import CustomPrimaryDetailsFeature from "@/components/custom-primary-details-feature.vue";
Expand All @@ -10,6 +10,8 @@ const getRelationTitle = (relation: RelationType) => {
};
const { getUnprefixedId } = useIdPrefix();
const route = useRoute();
const t = useTranslations();
const props = defineProps<{
entity: EntityFeature;
Expand Down Expand Up @@ -82,6 +84,8 @@ interface Place {
relationType: RelationType | null;
}
const isCopied = ref(false);
// TODO: For instances where there is no location set (at least for actors), make use of first and last event if no places are available
const places = computed(() => {
return props.entity.relations?.reduce((acc: Array<Place>, relation) => {
Expand All @@ -103,6 +107,9 @@ const places = computed(() => {
});
watchEffect(() => {
if (route.query.selection) {
isCopied.value = false;
}
if (!places.value || places.value.length === 0) return;
const relTypes = places.value.map((place) => {
return place.relationType;
Expand All @@ -112,11 +119,35 @@ watchEffect(() => {
}
emitHandledRelations([]);
});
function copyEntity() {
const fullUrl = window.location.href;
const baseUrl = fullUrl.split(route.path)[0];
if (baseUrl) {
isCopied.value = true;
return navigator.clipboard.writeText(`${baseUrl}/entity/${route.query.selection as string}`);
}
return null;
}
</script>

<template>
<CardHeader>
<EntitySystemClass :system-class="entity.systemClass" />
<div class="grid grid-cols-[auto_auto]">
<EntitySystemClass :system-class="entity.systemClass" />
<div v-if="!isCopied" class="ml-auto">
<Button variant="outline" @click="copyEntity">
<CopyIcon :size="16" />
{{ t("EntitySidebar.copy") }}
</Button>
</div>
<div v-if="isCopied" class="ml-auto">
<Button variant="brand" @click="copyEntity">
<CheckIcon :size="16" />
{{ t("EntitySidebar.copied") }}
</Button>
</div>
</div>
<PageTitle>{{ entity.properties.title }}</PageTitle>
<!-- @ts-expect FIXME: Incorrect information provided by openapi document. -->
<EntityAliases
Expand Down
2 changes: 1 addition & 1 deletion components/search-form.vue
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ const searchLabelId = "search-field";
>
<div class="grid gap-y-1">
<!-- eslint-disable-next-line vuejs-accessibility/form-control-has-label -->
<Select name="category" :default-value="props.filter">
<Select name="category" :default-value="props.category">
<SelectTrigger :aria-label="t('SearchForm.filter')" class="min-w-48">
<SelectValue :placeholder="t('SearchForm.select-filter')" />
</SelectTrigger>
Expand Down
4 changes: 3 additions & 1 deletion messages/de/common.json
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,9 @@
},
"EntitySidebar": {
"NextFeature": "Nächste Funktion",
"PreviousFeature": "Vorheriges Feature"
"PreviousFeature": "Vorheriges Feature",
"copy": "Kopiere Zitation",
"copied": "Kopiert!"
},
"ErrorBoundary": {
"error": "Fehler"
Expand Down
4 changes: 3 additions & 1 deletion messages/en/common.json
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,9 @@
},
"EntitySidebar": {
"NextFeature": "NextFeature",
"PreviousFeature": "Previous Feature"
"PreviousFeature": "Previous Feature",
"copy": "Copy Citation",
"copied": "Copied!"
},
"ErrorBoundary": {
"error": "Error"
Expand Down
187 changes: 187 additions & 0 deletions pages/entity/[id].vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
<script lang="ts" setup>
import { groupByToMap, keyByToMap } from "@acdh-oeaw/lib";
import { z } from "zod";
import { useIdPrefix } from "@/composables/use-id-prefix";
import { hasCoordinates } from "@/utils/has-geojson-coordinates";
// defineRouteRules({
// prerender: true,
// });
definePageMeta({
title: "EntityPage.meta.title",
validate(route) {
const env = useRuntimeConfig();
if (env.public.NUXT_PUBLIC_DATABASE === "disabled") return false;
const paramsSchema = z.object({
id: z.coerce.number().int().positive(),
});
return paramsSchema.safeParse(route.params).success;
},
});
const locale = useLocale();

Check failure on line 25 in pages/entity/[id].vue

View workflow job for this annotation

GitHub Actions / Validate (20.x, ubuntu-latest)

'locale' is assigned a value but never used. Allowed unused vars must match /^_/u
const t = useTranslations();
const { getUnprefixedId } = useIdPrefix();
const route = useRoute();
const id = computed(() => {
return Number(route.params.id as string);
});
const { data, error, isPending, isPlaceholderData, suspense } = useGetEntity(

Check failure on line 34 in pages/entity/[id].vue

View workflow job for this annotation

GitHub Actions / Validate (20.x, ubuntu-latest)

'error' is assigned a value but never used. Allowed unused vars must match /^_/u

Check failure on line 34 in pages/entity/[id].vue

View workflow job for this annotation

GitHub Actions / Validate (20.x, ubuntu-latest)

'suspense' is assigned a value but never used. Allowed unused vars must match /^_/u
computed(() => {
return { entityId: id.value };
}),
);
const isLoading = computed(() => {
return isPending.value || isPlaceholderData.value;
});
const entity = computed(() => {
return data.value?.features[0];
});
const entities = computed(() => {
return data.value?.features ?? [];
});
useHead({
title: computed(() => {
return entity.value?.properties.title ?? t("EntityPage.meta.title");
}),
// TODO: description, other metadata
});
const tabs = computed(() => {
const tabs = [];
if (entity.value?.geometry != null && hasCoordinates(entity.value.geometry)) {
tabs.push({
id: "geo-map",
label: t("EntityPage.map"),
});
}
if (entity.value?.depictions != null) {
tabs.push({
id: "images",
label: t("EntityPage.images", { count: entity.value.depictions.length }),
});
}
return tabs;
});
const relationsByType = computed(() => {
return groupByToMap(entity.value?.relations ?? [], (relation) => {
// FIXME: This used to use `relationType` (without the prefix)
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
return relation.relationSystemClass!;
});
});
const typesById = computed(() => {
return keyByToMap(entity.value?.types ?? [], (type) => {
return type.identifier;
});
});
</script>

<template>
<MainContent class="container relative grid grid-rows-[auto_1fr] gap-4 py-8">
<template v-if="entity != null">
<Card>
<CardHeader>
<EntitySystemClass :system-class="entity.systemClass" />
<PageTitle>{{ entity.properties.title }}</PageTitle>
</CardHeader>
<CardContent>
<div class="grid gap-4">
<EntityTimespans :timespans="entity.when?.timespans" />
<EntityDescriptions :descriptions="entity?.descriptions ?? []" />
</div>
</CardContent>
</Card>

<Tabs v-if="tabs.length > 0" :default-value="tabs[0]?.id">
<TabsList>
<TabsTrigger v-for="tab of tabs" :key="tab.id" :value="tab.id">
{{ tab.label }}
</TabsTrigger>
</TabsList>
<!-- TODO: keep map alive -->
<TabsContent v-for="tab of tabs" :key="tab.id" class="h-full max-h-full" :value="tab.id">
<EntityGeoMap v-if="tab.id === 'geo-map'" :entities="entities" />
<EntityImages v-else-if="tab.id === 'images'" :images="entity.depictions" />
</TabsContent>
</Tabs>

<Card>
<CardHeader>
<CardTitle>{{ t("EntityPage.details") }}</CardTitle>
</CardHeader>
<CardContent>
<dl
class="grid gap-x-8 gap-y-4 sm:grid-cols-[repeat(auto-fill,minmax(20rem,1fr))] sm:justify-start"
>
<div v-for="[relationType, relations] of relationsByType" :key="relationType">
<dt class="text-xs font-medium uppercase tracking-wider text-muted-foreground">
{{ t(`SystemClassNames.${relationType}`) }}
</dt>
<dd>
<ul role="list">
<li v-for="(relation, index) of relations.slice(0, 10)" :key="index">
<NavLink
class="underline decoration-dotted hover:no-underline"
:href="{ path: `/entities/${getUnprefixedId(relation.relationTo)}` }"
>
{{ relation.label }}
</NavLink>
<span
v-if="
relation.relationSystemClass === 'type' &&
typesById.has(relation.relationTo)
"
>
({{ typesById.get(relation.relationTo)?.hierarchy }})
</span>
</li>
</ul>
<details v-if="relations.length > 10">
<summary class="cursor-pointer py-1 text-sm text-muted-foreground">
Show more
</summary>
<ul role="list">
<li v-for="(relation, index) of relations.slice(10)" :key="index">
<NavLink
class="underline decoration-dotted hover:no-underline"
:href="{ path: `/entities/${getUnprefixedId(relation.relationTo)}` }"
>
{{ relation.label }}
</NavLink>
<span
v-if="
relation.relationSystemClass === 'type' &&
typesById.has(relation.relationTo)
"
>
({{ typesById.get(relation.relationTo)?.hierarchy }})
</span>
</li>
</ul>
</details>
</dd>
</div>
</dl>
</CardContent>
</Card>
</template>

<template v-else-if="isLoading">
<Centered class="pointer-events-none opacity-50">
<LoadingIndicator />
</Centered>
</template>
</MainContent>
</template>

0 comments on commit 3237f0a

Please sign in to comment.