diff --git a/base/templates/base/_header.html b/base/templates/base/_header.html index a9b46bc6a..e21af2de6 100644 --- a/base/templates/base/_header.html +++ b/base/templates/base/_header.html @@ -76,7 +76,9 @@ } /> + + + + + ); +}; diff --git a/ontology/frontend/src/features/terminology/components/TssEntityInfo.jsx b/ontology/frontend/src/features/terminology/components/TssEntityInfo.jsx new file mode 100644 index 000000000..a612fbc61 --- /dev/null +++ b/ontology/frontend/src/features/terminology/components/TssEntityInfo.jsx @@ -0,0 +1,37 @@ +// SPDX-FileCopyrightText: 2025 Jonas Huber © Reiner Lemoine Institut +// SPDX-License-Identifier: AGPL-3.0-or-later + +// ontology/frontend/src/features/terminology/components/TssEntityInfo.jsx +import React from "react"; +import { EntityInfoWidget } from "@ts4nfdi/terminology-service-suite"; +import { useTssConfig } from "../hooks/useTssConfig"; + +export default function TssEntityInfo({ + iri, + ontologyId, + entityType = "class", // Default, but can be overridden + onNavigateToEntity, + onNavigateToOntology +}) { + const { apiBase, ontology: configOntology } = useTssConfig(); + // const activeOntology = ontologyId || configOntology; + + // Check if it's an external term by looking at the IRI + const isExternal = iri.includes("purl.obolibrary.org"); + + return ( + { }} + onNavigateToEntity={onNavigateToEntity || (() => { })} + onNavigateToOntology={onNavigateToOntology || (() => { })} + /> + ); +} diff --git a/ontology/frontend/src/features/terminology/components/TssEntityNavButtons.jsx b/ontology/frontend/src/features/terminology/components/TssEntityNavButtons.jsx new file mode 100644 index 000000000..8c974ca78 --- /dev/null +++ b/ontology/frontend/src/features/terminology/components/TssEntityNavButtons.jsx @@ -0,0 +1,95 @@ +// SPDX-FileCopyrightText: 2025 Jonas Huber © Reiner Lemoine Institut +// SPDX-License-Identifier: AGPL-3.0-or-later + +import React from "react"; +import { useQuery } from "react-query"; +import { EuiButton, EuiFlexGroup, EuiFlexItem, EuiLoadingSpinner, EuiText } from "@elastic/eui"; +import { useTssConfig } from "../hooks/useTssConfig"; + +export default function TssEntityNavButtons({ iri, ontologyId, onNavigate }) { + const { apiBase } = useTssConfig(); + + // URL-encode the IRI as required by the TIB API path + const encodedIri = encodeURIComponent(encodeURIComponent(iri)); + + // Construct base URL (handle global vs specific ontology routing) + const baseUrl = ontologyId + ? `${apiBase}ontologies/${ontologyId}/terms/${encodedIri}` + : `${apiBase}terms/${encodedIri}`; + + // Fetch Parents + const { data: parents, isLoading: loadingParents } = useQuery( + ["entityParents", ontologyId, iri], + () => fetch(`${baseUrl}/parents`).then((res) => (res.ok ? res.json() : null)), + { enabled: !!iri } + ); + + // Fetch Children + const { data: children, isLoading: loadingChildren } = useQuery( + ["entityChildren", ontologyId, iri], + () => fetch(`${baseUrl}/children`).then((res) => (res.ok ? res.json() : null)), + { enabled: !!iri } + ); + + // Extract the arrays from the HAL JSON response format + const parentTerms = parents?._embedded?.terms || []; + const childTerms = children?._embedded?.terms || []; + + if (loadingParents || loadingChildren) { + return ; + } + + if (parentTerms.length === 0 && childTerms.length === 0) { + return null; // Don't render anything if no hierarchy exists + } + + return ( + + {/* Parents (Up) */} + {parentTerms.length > 0 && ( + + + {parentTerms.map((parent) => ( + + onNavigate(parent)} + > + {parent.label || parent.short_form} + + + ))} + + + )} + + {/* Separator if both exist */} + {parentTerms.length > 0 && childTerms.length > 0 && ( + + | + + )} + + {/* Children (Down) */} + {childTerms.length > 0 && ( + + + {childTerms.map((child) => ( + + onNavigate(child)} + > + {child.label || child.short_form} + + + ))} + + + )} + + ); +} diff --git a/ontology/frontend/src/features/terminology/components/TssEntityRelations.jsx b/ontology/frontend/src/features/terminology/components/TssEntityRelations.jsx new file mode 100644 index 000000000..adda93e78 --- /dev/null +++ b/ontology/frontend/src/features/terminology/components/TssEntityRelations.jsx @@ -0,0 +1,32 @@ +// SPDX-FileCopyrightText: 2025 Jonas Huber © Reiner Lemoine Institut +// SPDX-License-Identifier: AGPL-3.0-or-later + +// ontology/frontend/src/features/terminology/components/TssEntityRelations.jsx +import React from "react"; +import { EntityRelationsWidget } from "@ts4nfdi/terminology-service-suite"; +import { useTssConfig } from "../hooks/useTssConfig"; + +export default function TssEntityRelations({ + iri, + ontologyId, + entityType = "term", // "term" often works best for relations to show instances too + onNavigateToEntity +}) { + const { apiBase, ontology: configOntology } = useTssConfig(); + const activeOntology = ontologyId || configOntology; + + return ( + { }} + onNavigateToEntity={onNavigateToEntity || (() => { })} + onNavigateToOntology={() => { }} + /> + ); +} diff --git a/ontology/frontend/src/features/terminology/components/TssIriWidget.jsx b/ontology/frontend/src/features/terminology/components/TssIriWidget.jsx new file mode 100644 index 000000000..bc7504784 --- /dev/null +++ b/ontology/frontend/src/features/terminology/components/TssIriWidget.jsx @@ -0,0 +1,19 @@ +// SPDX-FileCopyrightText: 2025 Jonas Huber © Reiner Lemoine Institut +// SPDX-License-Identifier: AGPL-3.0-or-later + +import React from "react"; +import { IriWidget } from "@ts4nfdi/terminology-service-suite"; + +export default function TssIriWidget({ iri }) { + return ( + + ); +} diff --git a/ontology/frontend/src/features/terminology/components/TssSearchResultsList.jsx b/ontology/frontend/src/features/terminology/components/TssSearchResultsList.jsx new file mode 100644 index 000000000..383d50d35 --- /dev/null +++ b/ontology/frontend/src/features/terminology/components/TssSearchResultsList.jsx @@ -0,0 +1,39 @@ +// SPDX-FileCopyrightText: 2025 Jonas Huber © Reiner Lemoine Institut +// SPDX-License-Identifier: AGPL-3.0-or-later + +import React, { useMemo } from "react"; +import { SearchResultsListWidget } from "@ts4nfdi/terminology-service-suite"; +import { useTssConfig } from "../hooks/useTssConfig"; + +export default function TssSearchResultsList({ + ontologyId, + query = "d*", + onNavigateToEntity, + onNavigateToOntology, +}) { + const { apiBase, ontology: configOntology } = useTssConfig(); + + const activeOntology = ontologyId || configOntology; + + const parameter = useMemo(() => { + return `collection=nfdi4energy&ontology=${activeOntology}&fieldList=description,label,iri,ontology_name,type,short_form`; + }, [activeOntology]); + + return ( + href behavior in the TSS widget, + // allowing our custom onNavigateToEntity handler to process the click and route locally. + targetLink="" + onNavigateToEntity={onNavigateToEntity} + onNavigateToOntology={onNavigateToOntology || (() => { })} + /> + ); +} diff --git a/ontology/frontend/src/features/terminology/config/TssConfigProvider.jsx b/ontology/frontend/src/features/terminology/config/TssConfigProvider.jsx new file mode 100644 index 000000000..16359e744 --- /dev/null +++ b/ontology/frontend/src/features/terminology/config/TssConfigProvider.jsx @@ -0,0 +1,28 @@ +// SPDX-FileCopyrightText: 2025 Jonas Huber © Reiner Lemoine Institut +// SPDX-License-Identifier: AGPL-3.0-or-later + +import React, { createContext, useContext, useMemo } from "react"; + +const TssConfigCtx = createContext(null); + +export function TssConfigProvider({ + children, + // read from Vite env (fallback to OLS API + OEO) + apiBase = import.meta.env.VITE_TSS_API_BASE ?? "", + ontology = import.meta.env.VITE_TSS_DEFAULT_ONTOLOGY ?? "", + lang = import.meta.env.VITE_TSS_LANG ?? "en", + requestHeaders = {}, +}) { + const value = useMemo(() => ({ apiBase, ontology, lang, requestHeaders }), [ + apiBase, ontology, lang, requestHeaders, + ]); + return {children}; +} + +export function useTssConfigCtx() { + const ctx = useContext(TssConfigCtx); + if (!ctx) throw new Error("useTssConfigCtx must be used within "); + return ctx; +} + +export default TssConfigProvider; diff --git a/ontology/frontend/src/features/terminology/hooks/useTssConfig.js b/ontology/frontend/src/features/terminology/hooks/useTssConfig.js new file mode 100644 index 000000000..31023752e --- /dev/null +++ b/ontology/frontend/src/features/terminology/hooks/useTssConfig.js @@ -0,0 +1,9 @@ +// SPDX-FileCopyrightText: 2025 Jonas Huber © Reiner Lemoine Institut +// SPDX-License-Identifier: AGPL-3.0-or-later + +import { useTssConfigCtx } from "../config/TssConfigProvider"; + +export function useTssConfig() { + // later you can read from env and merge, but for now just return the ctx + return useTssConfigCtx(); +} diff --git a/ontology/frontend/src/features/terminology/services/tssClient.js b/ontology/frontend/src/features/terminology/services/tssClient.js new file mode 100644 index 000000000..0858fa09d --- /dev/null +++ b/ontology/frontend/src/features/terminology/services/tssClient.js @@ -0,0 +1,20 @@ +// SPDX-FileCopyrightText: 2025 Jonas Huber © Reiner Lemoine Institut +// SPDX-License-Identifier: AGPL-3.0-or-later + +import axios from "axios"; + +// Small helper to build a client bound to the current config +export function makeTssClient({ apiBase, requestHeaders = {} }) { + const http = axios.create({ + baseURL: apiBase.replace(/\/+$/, "/"), // ensure trailing slash + headers: { ...requestHeaders }, + timeout: 20_000, + }); + + return { + // Example endpoints – adjust to the suite/gateway you use + search: (params) => http.get("search", { params }), + concept: (iri) => http.get("concept", { params: { iri } }), + hierarchy: (iri) => http.get("hierarchy", { params: { iri } }), + }; +} diff --git a/ontology/frontend/src/index.jsx b/ontology/frontend/src/index.jsx new file mode 100644 index 000000000..69e3ffaa7 --- /dev/null +++ b/ontology/frontend/src/index.jsx @@ -0,0 +1,44 @@ +// SPDX-FileCopyrightText: 2025 Jonas Huber © Reiner Lemoine Institut +// SPDX-License-Identifier: AGPL-3.0-or-later + +import React, { StrictMode } from "react"; +import { createRoot } from "react-dom/client"; +import App from "./App.jsx"; + +import { QueryClient, QueryClientProvider } from "react-query"; +import { EuiProvider } from "@elastic/eui"; + +import { TssConfigProvider } from "./features/terminology/config/TssConfigProvider"; + + + +// one client for the whole app +const queryClient = new QueryClient({ + defaultOptions: { + queries: { + staleTime: 60_000, + refetchOnWindowFocus: false, + retry: 1, + }, + }, +}); + +function mount() { + const el = document.getElementById("main"); + if (!el || el.dataset.mounted) return; + el.dataset.mounted = "1"; + createRoot(el).render( + + + + + + + + + + ); +} + +if (document.readyState === "loading") document.addEventListener("DOMContentLoaded", mount); +else mount(); diff --git a/ontology/frontend/src/pages/EntitySearchPage.jsx b/ontology/frontend/src/pages/EntitySearchPage.jsx new file mode 100644 index 000000000..2ef6ac942 --- /dev/null +++ b/ontology/frontend/src/pages/EntitySearchPage.jsx @@ -0,0 +1,47 @@ +// SPDX-FileCopyrightText: 2025 Jonas Huber © Reiner Lemoine Institut +// SPDX-License-Identifier: AGPL-3.0-or-later + +import React from "react"; +import { useParams, useNavigate } from "react-router-dom"; +import { EuiPageTemplate, EuiPanel, EuiTitle, EuiSpacer } from "@elastic/eui"; +import TssSearchResultsList from "../features/terminology/components/TssSearchResultsList"; + +export default function EntitySearchPage() { + const { ontology } = useParams(); // Gets 'oeo', 'mrel', etc. from URL + const navigate = useNavigate(); + + const handleNavigateToEntity = (event) => { + // 1. Try to get the short_form (e.g., OEO_00000123) + let shortForm = event.short_form; + + // 2. Fallback: Parse IRI if short_form is missing + if (!shortForm && event.iri) { + const parts = event.iri.split("/"); + shortForm = parts[parts.length - 1]; + } + + if (shortForm) { + // Navigate to the Detail Page within this app + navigate(`/${ontology}/${shortForm}`); + } + }; + + return ( + + + +

{ontology ? ontology.toUpperCase() : ""} Entity Search

+
+ + + + {/* The configured wrapper handles the TSS widgets specifics */} + + +
+
+ ); +} diff --git a/ontology/frontend/src/pages/OeoIriPages.jsx b/ontology/frontend/src/pages/OeoIriPages.jsx new file mode 100644 index 000000000..b12d184e1 --- /dev/null +++ b/ontology/frontend/src/pages/OeoIriPages.jsx @@ -0,0 +1,139 @@ +// SPDX-FileCopyrightText: 2025 Jonas Huber © Reiner Lemoine Institut +// SPDX-License-Identifier: AGPL-3.0-or-later + +import React, { useState } from "react"; +import { useNavigate, useParams } from "react-router-dom"; +import { + EuiPageTemplate, + EuiPanel, + EuiSpacer, + EuiTitle, + EuiTabs, + EuiTab, + EuiFlexGroup, + EuiFlexItem, + EuiCallOut, +} from "@elastic/eui"; + +import TssEntityInfo from "../features/terminology/components/TssEntityInfo"; +import TssEntityRelations from "../features/terminology/components/TssEntityRelations"; +import TssIriWidget from "../features/terminology/components/TssIriWidget"; +import TssEntityNavButtons from "../features/terminology/components/TssEntityNavButtons"; + +function resolveIri(ontology, shortForm) { + if (!shortForm) return ""; + + const oboPrefixes = ["UO", "BFO", "RO", "IAO", "PATO", "ENVO", "CHEBI", "NCBITaxon"]; + const prefix = shortForm.split("_")[0]; + + if (oboPrefixes.includes(prefix)) { + return `http://purl.obolibrary.org/obo/${shortForm}`; + } + + return `https://openenergyplatform.org/ontology/${ontology}/${shortForm}`; +} + +export default function OeoIriPages() { + const { ontology, short_form } = useParams(); + const navigate = useNavigate(); + + const fetchIri = resolveIri(ontology, short_form); + const displayIri = `https://openenergyplatform.org/ontology/${ontology}/${short_form}`; + + const [entityType, setEntityType] = useState("class"); + + const tabs = [ + { id: 'class', name: 'Class' }, + { id: 'property', name: 'Property' }, + { id: 'individual', name: 'Individual' }, + ]; + + const handleNavigateToEntity = (event) => { + let nextId = event.short_form; + + if (!nextId && event.iri) { + const parts = event.iri.split("/"); + nextId = parts[parts.length - 1]; + } + + if (nextId) { + navigate(`/${ontology}/${nextId}`); + window.scrollTo(0, 0); + } + }; + + return ( + + + + + {/* Quick Navigation Buttons */} + + + + + + +

+ {ontology ? ontology.toUpperCase() : "Ontology"} Entity: {short_form} +

+
+ + +
+ + + +

Try selecting a different entity type below.

+
+ + + {tabs.map((tab) => ( + setEntityType(tab.id)} + > + {tab.name} + + ))} + +
+
+ + + + +

Entity Information

+ + + + + +

Relations & Hierarchy

+ + +
+
+
+ ); +} diff --git a/ontology/templates/ontology/about.html b/ontology/templates/ontology/about.html index e9c8d07b2..b375e20f3 100644 --- a/ontology/templates/ontology/about.html +++ b/ontology/templates/ontology/about.html @@ -45,8 +45,8 @@

Open Energy Ontology

system modeling, updated regularly following a release cycle.

- Access the latest version {{ version }} - here. + Access the latest version {{ version }} using the + OEO viewer or browse its content in the OEO search. All entities available in the OEO can be accessed in the OEO entity detail pages when navigating to their IRI.

Domain ontology: What is that? diff --git a/ontology/templates/ontology/class.html b/ontology/templates/ontology/class.html index a22af4d1d..655cd49e8 100644 --- a/ontology/templates/ontology/class.html +++ b/ontology/templates/ontology/class.html @@ -13,7 +13,7 @@

Overview / - Open Energy Ontology + Open Energy Ontology / Class - {{ class_name }}
@@ -32,7 +32,7 @@
Back to the super classes:
@@ -108,7 +108,7 @@
Sub classes:
href="#collapse{{ forloop.counter }}">{{ sub_class.name }} diff --git a/ontology/templates/ontology/partial_ontology_sidebar_content.html b/ontology/templates/ontology/partial_ontology_sidebar_content.html index f28dd0a57..0111c7f6b 100644 --- a/ontology/templates/ontology/partial_ontology_sidebar_content.html +++ b/ontology/templates/ontology/partial_ontology_sidebar_content.html @@ -22,7 +22,7 @@

View the oeo version {{ version }}

diff --git a/ontology/templates/react/entities.html b/ontology/templates/react/entities.html new file mode 100644 index 000000000..7b72f6a94 --- /dev/null +++ b/ontology/templates/react/entities.html @@ -0,0 +1,50 @@ + + +{% extends "base/base-wide-react.html" %} +{% load django_bootstrap5 %} +{% load django_vite %} +{% load static %} +{% block title %} + {% if term_id %} + {{ ontology_id|upper }} Entity: {{ term_id }} + {% else %} + {{ ontology_id|upper }} Search + {% endif %} +{% endblock %} +{% block site-header %} +
+

+ OEO + Ontology +

+
+ Overview / + {% if term_id %} + + {{ ontology_id|upper }} Search + + / Entities - {{ term_id }} + {% else %} + {{ ontology_id|upper }} Search + {% endif %} +
+
+{% endblock site-header %} +{% block after-head %} + {% vite_react_refresh nonce="{{ request.csp_nonce }}" %} + {% vite_asset "ontology/frontend/src/index.jsx" %} +{% endblock after-head %} +{% block main-right-sidebar-content %} +{% endblock main-right-sidebar-content %} +{% block main-content %} +
+
+
+ Loading React App... +
+
+
+{% endblock %} diff --git a/ontology/tests/test_views.py b/ontology/tests/test_views.py index 5d155ef73..d005d8034 100644 --- a/ontology/tests/test_views.py +++ b/ontology/tests/test_views.py @@ -21,8 +21,8 @@ def test_views(self): self.get("ontology:index") self.get( - "ontology:oeo-classes", - kwargs={"ontology": "oeo", "module_or_id": "BFO_0000001"}, + "ontology:oeo-class-detail", + kwargs={"ontology": "oeo", "term_id": "BFO_0000001"}, ) self.get("ontology:oeo-s-c") self.get("ontology:oeo-steering-committee") diff --git a/ontology/urls.py b/ontology/urls.py index 3739b7ead..c8ef3c4e1 100644 --- a/ontology/urls.py +++ b/ontology/urls.py @@ -14,9 +14,9 @@ OeoExtendedFileServeView, OntologyAboutView, OntologyStaticsView, - OntologyViewClassesView, PartialOntologyAboutContentView, PartialOntologyAboutSidebarContentView, + ontology_react_view, ) app_name = "ontology" @@ -78,9 +78,25 @@ OntologyStaticsView.as_view(), name="oeo-initializer", ), + # ------------------------------------------------------------------ + # 1. Search Page Listing + # Pattern: /ontology//entities/ + # Matches: /ontology/oeo/entities/ OR /ontology/xyz/entities/ + # ------------------------------------------------------------------ re_path( - r"^(?P[\w_-]+)?/(?P[\w\d_-]+)?/$", - OntologyViewClassesView.as_view(), - name="oeo-classes", + r"^(?P[\w-]+)/entities/$", + ontology_react_view, + name="ontology-entity-search", + ), + # ------------------------------------------------------------------ + # 2. Specific Entity Page (The Catch-All) + # Pattern: /ontology/// + # Matches: /ontology/oeo/OEO_00000040/ + # NOTE: This must come AFTER 'entities' so 'entities' isn't mistaken for an ID. + # ------------------------------------------------------------------ + re_path( + r"^(?P[\w-]+)/(?P[\w\d:_-]+)/$", + ontology_react_view, + name="oeo-class-detail", ), ] diff --git a/ontology/views.py b/ontology/views.py index 91697297c..c61c3440c 100644 --- a/ontology/views.py +++ b/ontology/views.py @@ -20,11 +20,9 @@ from django.shortcuts import HttpResponse, render from django.views import View -from oeplatform.settings import ( - OEO_EXT_NAME, +from oeplatform.settings import ( # OEO_EXT_NAME,; OEO_EXT_PATH, OEO_EXT_OWL_NAME, OEO_EXT_OWL_PATH, - OEO_EXT_PATH, ONTOLOGY_ROOT, OPEN_ENERGY_ONTOLOGY_FULL_OWL_NAME, OPEN_ENERGY_ONTOLOGY_NAME, @@ -144,140 +142,18 @@ def get(self, request): return HttpResponse(partial) -class OntologyViewClassesView(View): - def get( - self, - request, - ontology=OPEN_ENERGY_ONTOLOGY_NAME, - module_or_id=None, - version=None, - imports=False, - ): - if ontology not in [ - OPEN_ENERGY_ONTOLOGY_NAME, - OEO_EXT_NAME, - ]: - raise Http404 - - if ontology in [OPEN_ENERGY_ONTOLOGY_NAME]: - ontology_data = get_OEO_COMMON_DATA() - elif ontology in [OEO_EXT_NAME]: - ontology_data = get_common_data( - OEO_EXT_NAME, file=OEO_EXT_OWL_NAME, path=OEO_EXT_PATH - ) +def ontology_react_view(request, ontology=None, term_id=None): + """ + Serves the React frontend for both the Search listing and the Entity Detail page. - sub_classes = [] - super_classes = [] - if module_or_id: - for row in ontology_data["oeo_context_data"]["q_global"]: - if module_or_id in row.o: - sub_class_ID = row.s.split("/")[-1] - sub_class_name = "" - sub_class_definition = "" - sub_class_note = "" - if ( - sub_class_ID - in ontology_data["oeo_context_data"]["classes_name"].keys() - ): - sub_class_name = ontology_data["oeo_context_data"][ - "classes_name" - ][sub_class_ID] - if ( - sub_class_ID - in ontology_data["oeo_context_data"][ - "classes_definitions" - ].keys() - ): - sub_class_definition = ontology_data["oeo_context_data"][ - "classes_definitions" - ][sub_class_ID] - if ( - sub_class_ID - in ontology_data["oeo_context_data"]["classes_notes"].keys() - ): - sub_class_note = ontology_data["oeo_context_data"][ - "classes_notes" - ][sub_class_ID] - sub_classes.append( - { - "URI": row.s, - "ID": sub_class_ID, - "name": sub_class_name, - "definitions": sub_class_definition, - "notes": sub_class_note, - } - ) - if module_or_id in row.s: - super_class_ID = row.o.split("/")[-1] - super_class_name = "" - super_class_definition = "" - super_class_note = "" - if ( - super_class_ID - in ontology_data["oeo_context_data"]["classes_name"].keys() - ): - super_class_name = ontology_data["oeo_context_data"][ - "classes_name" - ][super_class_ID] - if ( - super_class_ID - in ontology_data["oeo_context_data"][ - "classes_definitions" - ].keys() - ): - super_class_definition = ontology_data["oeo_context_data"][ - "classes_definitions" - ][super_class_ID] - if ( - super_class_ID - in ontology_data["oeo_context_data"]["classes_notes"].keys() - ): - super_class_note = ontology_data["oeo_context_data"][ - "classes_notes" - ][super_class_ID] - super_classes.append( - { - "URI": row.o, - "ID": super_class_ID, - "name": super_class_name, - "definitions": super_class_definition, - "notes": super_class_note, - } - ) - - class_name = "" - if module_or_id in ontology_data["oeo_context_data"]["classes_name"].keys(): - class_name = ontology_data["oeo_context_data"]["classes_name"][module_or_id] - else: - raise Http404 - - class_definitions = "" - if ( - module_or_id - in ontology_data["oeo_context_data"]["classes_definitions"].keys() - ): - class_definitions = ontology_data["oeo_context_data"][ - "classes_definitions" - ][module_or_id] - - class_notes = "" - if module_or_id in ontology_data["oeo_context_data"]["classes_notes"].keys(): - class_notes = ontology_data["oeo_context_data"]["classes_notes"][ - module_or_id - ] - return render( - request, - "ontology/class.html", - dict( - ontology=ontology, - class_id=module_or_id, - class_name=class_name, - sub_classes=sub_classes, - super_classes=super_classes, - class_definitions=class_definitions, - class_notes=class_notes, - ), - ) + The actual routing logic (deciding whether to show search results or + details) is handled client-side by React Router based on the URL. + """ + context = { + "ontology_id": ontology, + "term_id": term_id, + } + return render(request, "react/entities.html", context) class OntologyStaticsView(View): diff --git a/versions/changelogs/current.md b/versions/changelogs/current.md index aa48cd28e..cb72bd9c2 100644 --- a/versions/changelogs/current.md +++ b/versions/changelogs/current.md @@ -8,8 +8,28 @@ SPDX-License-Identifier: CC0-1.0 # Changes to the oeplatform code +## Features + +- Add a new OEO search page for simple search and browsing of OEO entities + [(#2234)](https://github.com/OpenEnergyPlatform/oeplatform/pull/2234) +- Add interactive parent/child hierarchy navigation and IRI copy functionality + to entity detail pages + [(#2234)](https://github.com/OpenEnergyPlatform/oeplatform/pull/2234) + ## Changes +- Reworked the former OEO Class pages into OEO Entity pages, now supporting + details for Classes, Properties, and Individuals + [(#2234)](https://github.com/OpenEnergyPlatform/oeplatform/pull/2234) +- Integrated NFDI4Energy Terminology Service Suite (TSS) React widgets for + standardized search, metadata, and relations displays + [(#2234)](https://github.com/OpenEnergyPlatform/oeplatform/pull/2234) +- Improved IRI resolution to seamlessly handle and route external/imported + ontology terms (e.g., OBO Foundry) + [(#2234)](https://github.com/OpenEnergyPlatform/oeplatform/pull/2234) +- Implemented client-side React routing (SPA) for seamless transitions between + search results and entity details + [(#2234)](https://github.com/OpenEnergyPlatform/oeplatform/pull/2234) - Reworked the OEO Viewer using React and the [TSS Widgets library](https://ts4nfdi.github.io/terminology-service-suite/comp/latest/?path=/docs/overview--docs), providing enhanced usability. @@ -22,12 +42,13 @@ SPDX-License-Identifier: CC0-1.0 ## Removed +- Removed the previous custom backend and frontend implementation for the OEO + Class / IRI pages + [(#2234)](https://github.com/OpenEnergyPlatform/oeplatform/pull/2234) - Removed the legacy custom backend and frontend implementation of the OEO Viewer. [(#2222)](https://github.com/OpenEnergyPlatform/oeplatform/pull/2222) -## Features - -### Bugs +## Bugs - Fixed an issue related to the Bootstrap vs EUI which lead to strange styling when using the autocomplete widget form diff --git a/vite.config.mjs b/vite.config.mjs index dcef0617c..b95de1438 100644 --- a/vite.config.mjs +++ b/vite.config.mjs @@ -66,6 +66,7 @@ export default defineConfig({ factsheet: resolve("./factsheet/frontend/src/index.jsx"), opr_review: resolve("./dataedit/static/peer_review/main.js"), oeo_viewer: resolve("./oeo_viewer/frontend/src/index.jsx"), + ontology: resolve("./ontology/frontend/src/index.jsx"), wizard: resolve("./dataedit/static/wizard/wizard.js"), metaedit: resolve("./dataedit/static/metaedit/metaedit.js"), },