diff --git a/src/components/SingleTermView/OverView/Details.jsx b/src/components/SingleTermView/OverView/Details.jsx index 8e61303f..5ea9fad9 100644 --- a/src/components/SingleTermView/OverView/Details.jsx +++ b/src/components/SingleTermView/OverView/Details.jsx @@ -7,11 +7,12 @@ import { } from "@mui/material"; import PropTypes from "prop-types"; import OpenInNewOutlinedIcon from '@mui/icons-material/OpenInNewOutlined'; +import { formatTimestamp } from "../../../utils"; import { vars } from "../../../theme/variables"; const { gray800, gray500 } = vars; -const Details = ({loading, data }) => { +const Details = ({ loading, data, jsonData }) => { const handleChipClick = (url) => { window.open(url, '_blank'); }; @@ -34,6 +35,10 @@ const Details = ({loading, data }) => { if (!data) { return
No data available
; } + const graphArray = jsonData["@graph"]; + const lastGraphItem = graphArray[graphArray.length - 1] + const versionIRI = lastGraphItem?.["owl:versionIRI"]?.["@id"]; + const versionInfo = formatTimestamp(lastGraphItem?.["owl:versionInfo"]); return ( <> @@ -75,7 +80,7 @@ const Details = ({loading, data }) => { Existing IDs - {data?.existingID && ( processExistingIds(data?.existingID).map((id) => + {data?.existingID && (processExistingIds(data?.existingID).map((id) => } onClick={() => handleChipClick(id)} /> ))} @@ -111,7 +116,7 @@ const Details = ({loading, data }) => { Version - {data?.versionInfo} + {versionIRI.split('/version/')[1].split('/')[0]} @@ -151,7 +156,7 @@ const Details = ({loading, data }) => { Last modify timestamp - {data?.lastModifyTimestamp} + {versionInfo} @@ -162,7 +167,8 @@ const Details = ({loading, data }) => { Details.propTypes = { loading: PropTypes.bool.isRequired, - data: PropTypes.object + data: PropTypes.object, + jsonData: PropTypes.object }; export default Details; diff --git a/src/components/SingleTermView/OverView/OverView.jsx b/src/components/SingleTermView/OverView/OverView.jsx index 28c36d11..33441efd 100644 --- a/src/components/SingleTermView/OverView/OverView.jsx +++ b/src/components/SingleTermView/OverView/OverView.jsx @@ -9,18 +9,20 @@ import PropTypes from 'prop-types'; import Hierarchy from "./Hierarchy"; import Predicates from "./Predicates"; import RawDataViewer from "./RawDataViewer"; -import {useCallback, useEffect, useMemo, useState} from "react"; -import { getMatchTerms } from "../../../api/endpoints"; +import { useCallback, useEffect, useMemo, useState } from "react"; +import { getMatchTerms, getRawData } from "../../../api/endpoints"; const OverView = ({ searchTerm, isCodeViewVisible, selectedDataFormat }) => { const [data, setData] = useState(null); const [loading, setLoading] = useState(true); - + const [jsonData, setJsonData] = useState(null); + // eslint-disable-next-line react-hooks/exhaustive-deps const fetchTerms = useCallback( debounce((searchTerm) => { if (searchTerm) { getMatchTerms("base", searchTerm).then(data => { + console.log("data from api call: ", data) setData(data?.results?.[0]); setLoading(false); }); @@ -28,24 +30,31 @@ const OverView = ({ searchTerm, isCodeViewVisible, selectedDataFormat }) => { }, 300), [] ); - + + const fetchJSONFile = useCallback(() => { + getRawData("base", searchTerm, 'jsonld').then(rawResponse => { + setJsonData(rawResponse); + }) + }, [searchTerm]); + useEffect(() => { setLoading(true); fetchTerms(searchTerm); + fetchJSONFile(); return () => { fetchTerms.cancel(); }; - }, [searchTerm, fetchTerms]); + }, [searchTerm, fetchTerms, fetchJSONFile]); const memoData = useMemo(() => data, [data]); - + return ( {isCodeViewVisible ? : <> -
+
diff --git a/src/components/SingleTermView/index.jsx b/src/components/SingleTermView/index.jsx index ecb13764..51cc3423 100644 --- a/src/components/SingleTermView/index.jsx +++ b/src/components/SingleTermView/index.jsx @@ -16,7 +16,6 @@ import ToggleButton from '@mui/material/ToggleButton'; import CustomBreadcrumbs from "../common/CustomBreadcrumbs"; import ForkRightIcon from '@mui/icons-material/ForkRight'; import { vars } from "../../theme/variables"; -import FiberManualRecordIcon from '@mui/icons-material/FiberManualRecord'; import OntologySearch from "./OntologySearch"; import ModeEditOutlineOutlinedIcon from '@mui/icons-material/ModeEditOutlineOutlined'; import RateReviewOutlinedIcon from '@mui/icons-material/RateReviewOutlined'; @@ -45,10 +44,18 @@ import CreateForkDialog from "./CreateForkDialog"; import TermDialog from "../TermEditor/TermDialog"; import { getSelectedTermLabel } from "../../api/endpoints/apiService"; import { GlobalDataContext } from "../../contexts/DataContext"; +import { getRawData } from "../../api/endpoints"; -const { gray200, brand700, gray600 } = vars; +const { gray200, gray600 } = vars; -const dataFormats = ['JSON-LD', 'Turtle', 'N3', 'OWL', 'CSV'] +const dataFormats = ['JSON-LD', 'Turtle', 'N3', 'OWL', 'CSV']; +const formatExtensions = { + 'JSON-LD': 'jsonld', + 'Turtle': 'ttl', + 'N3': 'n3', + 'OWL': 'owl', + 'CSV': 'csv' +}; const SingleTermView = () => { const [open, setOpen] = useState(false); @@ -93,6 +100,8 @@ const SingleTermView = () => { const handleDataFormatMenuItemClick = (value) => { setSelectedDataFormat(value); setDataFormatAnchorEl(null); + + downloadFormattedData(value); }; const handleOpenRequestMergeDialog = () => { @@ -118,15 +127,30 @@ const SingleTermView = () => { setTabValue(newValue); }; + const downloadFormattedData = (dataFormat) => { + getRawData("base", searchTerm, formatExtensions[dataFormat]).then(rawResponse => { + const formattedData = JSON.stringify(rawResponse, null, 2); + const blob = new Blob([formattedData], { type: 'application/json' }); + const url = URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + a.download = `data.${formatExtensions[dataFormat]}`; + a.click(); + URL.revokeObjectURL(url); + }).catch(error => { + console.error('Error downloading data:', error); + }); + } + const CodeOrTreeIcon = () => { return isCodeViewVisible ? : } const breadcrumbItems = [ { label: '', href: '/', icon: HomeOutlinedIcon }, - { label: 'Term search', href: `/search?searchTerm=${searchTerm}` }, - { label: 'My organization 1', href: '#' }, - { label: 'ILX:0101901' }, + { label: 'Term search', href: `/search?searchTerm=${storedSearchTerm}` }, + { label: 'base', href: '#' }, + { label: searchTerm.toUpperCase().replace("_", ":") }, ]; useEffect(() => { @@ -148,19 +172,7 @@ const SingleTermView = () => { - - - - - fork1 - - } - label="Not merged" - variant="outlined" - className="rounded not-merged" - /> - + Active Ontology: diff --git a/src/utils.js b/src/utils.js index f125688f..e30361fc 100644 --- a/src/utils.js +++ b/src/utils.js @@ -45,29 +45,29 @@ export const stableSort = (array, comparator) => { export function compareArrays(originalArray, modifiedArray) { if (!Array.isArray(originalArray) || !Array.isArray(modifiedArray)) { - console.warn("compareArrays received invalid input", { originalArray, modifiedArray }); - return []; + console.warn("compareArrays received invalid input", { originalArray, modifiedArray }); + return []; } - + return originalArray.filter(item => !modifiedArray.includes(item)); } export function compareSentences(originalText, modifiedText) { - // Split texts into sentences - const originalSentences = originalText.match(/[^.!?]+[.!?]+/g) || []; - const modifiedSentences = modifiedText.match(/[^.!?]+[.!?]+/g) || []; - - // Find sentences in original that are not in modified - const uniqueSentences = originalSentences.filter(sentence => - !modifiedSentences.some(modSentence => - sentence.trim().toLowerCase() === modSentence.trim().toLowerCase() - ) - ); - return uniqueSentences; + // Split texts into sentences + const originalSentences = originalText.match(/[^.!?]+[.!?]+/g) || []; + const modifiedSentences = modifiedText.match(/[^.!?]+[.!?]+/g) || []; + + // Find sentences in original that are not in modified + const uniqueSentences = originalSentences.filter(sentence => + !modifiedSentences.some(modSentence => + sentence.trim().toLowerCase() === modSentence.trim().toLowerCase() + ) + ); + return uniqueSentences; } export function compareStrings(originalString, modifiedString) { - return originalString !== modifiedString ? [originalString] : [] + return originalString !== modifiedString ? [originalString] : [] } export function compareObjects(originalObject, modifiedObject) { @@ -75,11 +75,33 @@ export function compareObjects(originalObject, modifiedObject) { } export function getDataType(data) { - if (Array.isArray(data)) return "array" - if (typeof data === "string") return "string" - if (typeof data === "number") return "number" - if (typeof data === "boolean") return "boolean" - if (data === null) return "null" - if (typeof data === "object") return "object" - return "unknown" + if (Array.isArray(data)) return "array" + if (typeof data === "string") return "string" + if (typeof data === "number") return "number" + if (typeof data === "boolean") return "boolean" + if (data === null) return "null" + if (typeof data === "object") return "object" + return "unknown" +} + +export function formatTimestamp(rawDate) { + if (!rawDate || typeof rawDate !== 'string') return 'Invalid date'; + + try { + const cleanedDateStr = rawDate.replace(',', '.'); + const date = new Date(cleanedDateStr); + + if (isNaN(date)) return 'Invalid date'; + + const yyyy = date.getFullYear(); + const mm = String(date.getMonth() + 1).padStart(2, '0'); + const dd = String(date.getDate()).padStart(2, '0'); + const hh = String(date.getHours()).padStart(2, '0'); + const min = String(date.getMinutes()).padStart(2, '0'); + + return `${yyyy}-${mm}-${dd} ${hh}:${min}`; + } catch (e) { + return 'Invalid date'; + } } +