diff --git a/components/custom-primary-details-actor.vue b/components/custom-primary-details-actor.vue new file mode 100644 index 00000000..2ac82585 --- /dev/null +++ b/components/custom-primary-details-actor.vue @@ -0,0 +1,88 @@ + + + diff --git a/components/custom-primary-details-feature.vue b/components/custom-primary-details-feature.vue new file mode 100644 index 00000000..059d20f2 --- /dev/null +++ b/components/custom-primary-details-feature.vue @@ -0,0 +1,119 @@ + + + diff --git a/components/custom-primary-details-place.vue b/components/custom-primary-details-place.vue new file mode 100644 index 00000000..afc7be7c --- /dev/null +++ b/components/custom-primary-details-place.vue @@ -0,0 +1,55 @@ + + + diff --git a/components/data-map-view.vue b/components/data-map-view.vue index 02f6e1eb..2cab6c7e 100644 --- a/components/data-map-view.vue +++ b/components/data-map-view.vue @@ -15,6 +15,8 @@ const router = useRouter(); const route = useRoute(); const t = useTranslations(); +const currentView = useGetCurrentView(); + const searchFiltersSchema = v.object({ category: v.fallback(v.picklist(categories), "entityName"), search: v.fallback(v.string(), ""), @@ -46,7 +48,7 @@ const { data, isPending, isPlaceholderData } = useGetSearchResults( : [], show: ["geometry", "when"], centroid: true, - system_classes: ["place"], + system_classes: ["place", "object_location"], limit: 0, }; }), @@ -188,12 +190,13 @@ watch(data, () => { + { {{ entity.properties.title }} diff --git a/components/data-network-view.vue b/components/data-network-view.vue index 2e936169..27a3194c 100644 --- a/components/data-network-view.vue +++ b/components/data-network-view.vue @@ -81,15 +81,17 @@ const systemClasses = computed(() => { diff --git a/components/geo-map.client.vue b/components/geo-map.client.vue index 02b31206..ce9217b1 100644 --- a/components/geo-map.client.vue +++ b/components/geo-map.client.vue @@ -59,7 +59,7 @@ async function create() { const map = new GeoMap({ center: [initialViewState.longitude, initialViewState.latitude], container: elementRef.value, - maxZoom: 16, + maxZoom: 24, minZoom: 1, pitch: initialViewState.pitch, style: mapStyle.value, @@ -170,6 +170,7 @@ function init() { // updateScope(); + updatePolygons(); } function dispose() { @@ -200,7 +201,7 @@ function updateScope() { }); const polygons = props.features.filter((polygon) => { - return polygon.geometry.type === "GeometryCollection"; + return polygon.geometry.type === "GeometryCollection" || "Polygon"; }); const centerpoints = props.features.filter((centerpoint) => { @@ -217,6 +218,9 @@ function updateScope() { if (geojsonPoints.features.length > 0) { const bounds = turf.bbox(geojsonPoints) as [number, number, number, number]; + map.fitBounds(bounds, { padding: 50, maxZoom: 16 }); + } else if (geojsonCenterPoints.features.length > 0) { + const bounds = turf.bbox(geojsonCenterPoints); map.fitBounds(bounds, { padding: 50 }); } } diff --git a/components/grouped-relation-collapsible.vue b/components/grouped-relation-collapsible.vue new file mode 100644 index 00000000..5b784fe3 --- /dev/null +++ b/components/grouped-relation-collapsible.vue @@ -0,0 +1,83 @@ + + + diff --git a/components/info-card.vue b/components/info-card.vue new file mode 100644 index 00000000..0142c316 --- /dev/null +++ b/components/info-card.vue @@ -0,0 +1,23 @@ + + + diff --git a/components/mirador-viewer.client.vue b/components/mirador-viewer.client.vue index 90b44e2f..2cdd1276 100644 --- a/components/mirador-viewer.client.vue +++ b/components/mirador-viewer.client.vue @@ -1,45 +1,28 @@ diff --git a/components/network.client.vue b/components/network.client.vue index a5c14eaa..93542741 100644 --- a/components/network.client.vue +++ b/components/network.client.vue @@ -91,6 +91,8 @@ watch( { immediate: true }, ); +const currentView = useGetCurrentView(); + onMounted(async () => { layout.start(); @@ -110,7 +112,7 @@ onMounted(async () => { context.camera = context.renderer.getCamera(); context.renderer.on("clickNode", ({ node }) => { - void router.push(`/${locale.value}/entities/` + node); + void router.push(`/${locale.value}/entities/${node}/${currentView.value}`); }); context.renderer.on("enterNode", ({ node }) => { diff --git a/components/relation-collapsible.vue b/components/relation-collapsible.vue new file mode 100644 index 00000000..f5e50a67 --- /dev/null +++ b/components/relation-collapsible.vue @@ -0,0 +1,38 @@ + + + diff --git a/components/relation-list-entry.vue b/components/relation-list-entry.vue new file mode 100644 index 00000000..ca7430b8 --- /dev/null +++ b/components/relation-list-entry.vue @@ -0,0 +1,24 @@ + + + diff --git a/components/search-results-table.vue b/components/search-results-table.vue index b4d80736..d58522ed 100644 --- a/components/search-results-table.vue +++ b/components/search-results-table.vue @@ -12,11 +12,11 @@ import { } from "@tanstack/vue-table"; import { ArrowUpDown } from "lucide-vue-next"; -import NavLink from "@/components/nav-link.vue"; import { Button } from "@/components/ui/button"; import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip"; import type { EntityFeature } from "@/composables/use-create-entity"; import { isColumn } from "@/composables/use-get-search-results"; +import NavLink from "@/components/nav-link.vue"; const emit = defineEmits({ "update:sorting"(sorting: SortingState) { diff --git a/components/simple-timespan.vue b/components/simple-timespan.vue new file mode 100644 index 00000000..1a0eb5d5 --- /dev/null +++ b/components/simple-timespan.vue @@ -0,0 +1,60 @@ + + + diff --git a/components/types-popover.vue b/components/types-popover.vue new file mode 100644 index 00000000..4f7adae8 --- /dev/null +++ b/components/types-popover.vue @@ -0,0 +1,104 @@ + + + diff --git a/components/ui/breadcrumb/Breadcrumb.vue b/components/ui/breadcrumb/Breadcrumb.vue new file mode 100644 index 00000000..72ca1437 --- /dev/null +++ b/components/ui/breadcrumb/Breadcrumb.vue @@ -0,0 +1,13 @@ + + + diff --git a/components/ui/breadcrumb/BreadcrumbEllipsis.vue b/components/ui/breadcrumb/BreadcrumbEllipsis.vue new file mode 100644 index 00000000..c0453db1 --- /dev/null +++ b/components/ui/breadcrumb/BreadcrumbEllipsis.vue @@ -0,0 +1,23 @@ + + + diff --git a/components/ui/breadcrumb/BreadcrumbItem.vue b/components/ui/breadcrumb/BreadcrumbItem.vue new file mode 100644 index 00000000..af29e218 --- /dev/null +++ b/components/ui/breadcrumb/BreadcrumbItem.vue @@ -0,0 +1,16 @@ + + + diff --git a/components/ui/breadcrumb/BreadcrumbLink.vue b/components/ui/breadcrumb/BreadcrumbLink.vue new file mode 100644 index 00000000..b6c85671 --- /dev/null +++ b/components/ui/breadcrumb/BreadcrumbLink.vue @@ -0,0 +1,19 @@ + + + diff --git a/components/ui/breadcrumb/BreadcrumbList.vue b/components/ui/breadcrumb/BreadcrumbList.vue new file mode 100644 index 00000000..4bea0438 --- /dev/null +++ b/components/ui/breadcrumb/BreadcrumbList.vue @@ -0,0 +1,16 @@ + + + diff --git a/components/ui/breadcrumb/BreadcrumbPage.vue b/components/ui/breadcrumb/BreadcrumbPage.vue new file mode 100644 index 00000000..a48323d6 --- /dev/null +++ b/components/ui/breadcrumb/BreadcrumbPage.vue @@ -0,0 +1,19 @@ + + + diff --git a/components/ui/breadcrumb/BreadcrumbSeparator.vue b/components/ui/breadcrumb/BreadcrumbSeparator.vue new file mode 100644 index 00000000..6c18e3e9 --- /dev/null +++ b/components/ui/breadcrumb/BreadcrumbSeparator.vue @@ -0,0 +1,22 @@ + + + diff --git a/components/ui/breadcrumb/index.ts b/components/ui/breadcrumb/index.ts new file mode 100644 index 00000000..05909832 --- /dev/null +++ b/components/ui/breadcrumb/index.ts @@ -0,0 +1,7 @@ +export { default as Breadcrumb } from './Breadcrumb.vue' +export { default as BreadcrumbEllipsis } from './BreadcrumbEllipsis.vue' +export { default as BreadcrumbItem } from './BreadcrumbItem.vue' +export { default as BreadcrumbLink } from './BreadcrumbLink.vue' +export { default as BreadcrumbList } from './BreadcrumbList.vue' +export { default as BreadcrumbPage } from './BreadcrumbPage.vue' +export { default as BreadcrumbSeparator } from './BreadcrumbSeparator.vue' diff --git a/components/ui/collapsible/Collapsible.vue b/components/ui/collapsible/Collapsible.vue new file mode 100644 index 00000000..52bff5f2 --- /dev/null +++ b/components/ui/collapsible/Collapsible.vue @@ -0,0 +1,15 @@ + + + diff --git a/components/ui/collapsible/CollapsibleContent.vue b/components/ui/collapsible/CollapsibleContent.vue new file mode 100644 index 00000000..9f30898b --- /dev/null +++ b/components/ui/collapsible/CollapsibleContent.vue @@ -0,0 +1,11 @@ + + + diff --git a/components/ui/collapsible/CollapsibleTrigger.vue b/components/ui/collapsible/CollapsibleTrigger.vue new file mode 100644 index 00000000..4a434639 --- /dev/null +++ b/components/ui/collapsible/CollapsibleTrigger.vue @@ -0,0 +1,11 @@ + + + diff --git a/components/ui/collapsible/index.ts b/components/ui/collapsible/index.ts new file mode 100644 index 00000000..4930f4c6 --- /dev/null +++ b/components/ui/collapsible/index.ts @@ -0,0 +1,3 @@ +export { default as Collapsible } from './Collapsible.vue' +export { default as CollapsibleTrigger } from './CollapsibleTrigger.vue' +export { default as CollapsibleContent } from './CollapsibleContent.vue' diff --git a/composables/use-filter-relations.ts b/composables/use-filter-relations.ts new file mode 100644 index 00000000..dd1696e0 --- /dev/null +++ b/composables/use-filter-relations.ts @@ -0,0 +1,47 @@ +type Relations = Array[0]>; + +export const useFilterRelations = ( + relations: MaybeRef | undefined, + filters: MaybeRef<{ + relationType?: RelationType; + systemClass?: EntityFeature["systemClass"]; + }>, +) => { + if (!relations) return []; + + const { relationType, systemClass } = toValue(filters); + const rels = toValue(relations); + + const test = rels.reduce((acc: Relations, relation): Relations => { + if (!relation.relationType) return acc; + + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-call + const { crmCode, inverse } = extractRelationTypeFromRelationString(relation.relationType) ?? {}; + + if ( + relationType && + !systemClass && + crmCode === relationType.crmCode && + inverse === Boolean(relationType.inverse) + ) { + return [...acc, relation]; + } + + if (systemClass && !relationType && systemClass === relation.relationSystemClass) { + return [...acc, relation]; + } + + if ( + relationType && + systemClass && + crmCode === relationType.crmCode && + inverse === Boolean(relationType.inverse) && + systemClass === relation.relationSystemClass + ) { + return [...acc, relation]; + } + + return acc; + }, []); + return test; +}; diff --git a/composables/use-get-current-view.ts b/composables/use-get-current-view.ts new file mode 100644 index 00000000..0d18b39c --- /dev/null +++ b/composables/use-get-current-view.ts @@ -0,0 +1,12 @@ +import { z } from "zod"; + +const viewSchema = z.enum(["map", "network"]).catch("map"); + +export function useGetCurrentView() { + const route = useRoute(); + + return computed(() => { + const view = viewSchema.parse(route.path.split("/").pop()); + return view; + }); +} diff --git a/composables/use-get-entity-from-route.ts b/composables/use-get-entity-from-route.ts new file mode 100644 index 00000000..6dc0a292 --- /dev/null +++ b/composables/use-get-entity-from-route.ts @@ -0,0 +1,23 @@ +export const useGetEntityFromRoute = () => { + const route = useRoute(); + + const id = computed(() => { + return Number(route.params.id as string); + }); + + const { data, isPending, isPlaceholderData } = useGetEntity( + computed(() => { + return { entityId: id.value }; + }), + ); + + const entity = computed(() => { + return data.value?.features[0]; + }); + + const isLoading = computed(() => { + return isPending.value || isPlaceholderData.value; + }); + + return { entity, isLoading }; +}; diff --git a/composables/use-get-entity.ts b/composables/use-get-entity.ts index 6cd46c9e..783e675c 100644 --- a/composables/use-get-entity.ts +++ b/composables/use-get-entity.ts @@ -22,6 +22,10 @@ export function useGetEntity(params: MaybeRef) { path: { ...params, }, + query: { + format: "lpx", + centroid: true, + }, }, signal, })) as GetEntityResponse; diff --git a/composables/use-get-system-class.ts b/composables/use-get-system-class.ts new file mode 100644 index 00000000..07ef5882 --- /dev/null +++ b/composables/use-get-system-class.ts @@ -0,0 +1,59 @@ +import { useQuery } from "@tanstack/vue-query"; + +import { useCreateEntity } from "@/composables/use-create-entity"; +import type { operations } from "@/lib/api-client/api"; +import type { LinkedPlace } from "@/types/api"; + +export interface GetSystemClassParams + extends NonNullable {} + +export type GetSystemClassQueryParams = operations["GetBySystemClass"]["parameters"]["query"]; + +export type GetBySystemClassResponse = { + pagination: { + entities: number; + entitiesPerPage: number; + index: Array<{ + page: number; + startId: number; + }>; + totalPages: number; + }; + results: Array; +}; + +export function useGetBySystemClass( + pathParams: MaybeRef, + queryParams: MaybeRef, +) { + const api = useApiClient(); + const createEntity = useCreateEntity(); + + return useQuery({ + queryKey: ["system_class", { path: pathParams, query: queryParams }] as const, + + async queryFn({ queryKey, signal }) { + const [, params] = queryKey; + + const response = (await api.GET("/system_class/{system_class}", { + params: { + path: { + ...params.path, + }, + query: { + ...params.query, + format: "lpx", + }, + }, + signal, + })) as GetBySystemClassResponse; + + const enities = []; + for (const feature of response.results) { + enities.push(createEntity(feature)); + } + + return { enities, pagination: response.pagination }; + }, + }); +} diff --git a/composables/use-relation-info.ts b/composables/use-relation-info.ts new file mode 100644 index 00000000..00e113bf --- /dev/null +++ b/composables/use-relation-info.ts @@ -0,0 +1,28 @@ +import type { Locale, Schema } from "@/config/i18n.config"; +import type { RelationType } from "@/utils/extract-crm-code"; + +export const useRelationTitle = (relation: RelationType, systemClass?: string): string => { + const { te } = useI18n(); + const t = useTranslations(); + + const key = `${relation.crmCode}.title${relation.inverse ? "Inverse" : ""}`; + const systemClassKey = `${systemClass ?? ""}.${key}`; + + if (te(systemClassKey)) { + return t(systemClassKey); + } + return t(key); +}; + +export const useRelationGroupTitle = (relation: RelationType, systemClass?: string): string => { + const { te } = useI18n(); + const t = useTranslations(); + + const key = `${relation.crmCode}.groupTitle${relation.inverse ? "Inverse" : ""}`; + const systemClassKey = `${systemClass ?? ""}.${key}`; + + if (te(systemClassKey)) { + return t(systemClassKey); + } + return t(key); +}; diff --git a/config/i18n.config.ts b/config/i18n.config.ts index f95ecc16..32c13f9b 100644 --- a/config/i18n.config.ts +++ b/config/i18n.config.ts @@ -14,8 +14,8 @@ export type Locale = (typeof locales)[number]; export const defaultLocale: Locale = project.defaultLocale; export const localesMap = { - de: { code: "de", iso: "de", files: ["de/common.json", "de/project.json"] }, - en: { code: "en", iso: "en", files: ["en/common.json", "en/project.json"] }, + de: { code: "de", iso: "de", files: ["de/common.json", "de/project.json", "de/crm.json"] }, + en: { code: "en", iso: "en", files: ["en/common.json", "en/project.json", "en/crm.json"] }, } satisfies Record; export type Messages = typeof en & typeof projectEn; diff --git a/config/mirador.config.ts b/config/mirador.config.ts index 2b36214e..3a45f1c7 100644 --- a/config/mirador.config.ts +++ b/config/mirador.config.ts @@ -1,5 +1,5 @@ -// Configuration for Mirador -export const miradorConfig = ref({ +// FIXME: why is this reactive? +export const config = ref({ layout: "1x1", mainMenuSettings: { show: false, diff --git a/layouts/default.vue b/layouts/default.vue index 1e392d66..c1c485f4 100644 --- a/layouts/default.vue +++ b/layouts/default.vue @@ -100,6 +100,7 @@ router.afterEach((to, from) => { const fullscreen = "--container-width: ;"; const container = "--container-width: 1536px;"; +