Skip to content

Commit

Permalink
Get the Tree working
Browse files Browse the repository at this point in the history
  • Loading branch information
john681611 committed Oct 27, 2023
1 parent a924775 commit 97ff328
Show file tree
Hide file tree
Showing 5 changed files with 228 additions and 104 deletions.
6 changes: 5 additions & 1 deletion application/frontend/src/pages/Explorer/explorer.scss
Original file line number Diff line number Diff line change
Expand Up @@ -102,4 +102,8 @@ p{
#search-summary{
display: inline-block; vertical-align: middle;
}
#graphs{font-size: 80%; color: grey;}
#graphs{font-size: 80%; color: grey;}

.highlight{
background-color: yellow;
}
298 changes: 197 additions & 101 deletions application/frontend/src/pages/Explorer/explorer.tsx
Original file line number Diff line number Diff line change
@@ -1,36 +1,77 @@
import './explorer.scss';

import React, { useContext, useEffect, useMemo, useState } from 'react';
import React, { useEffect, useState } from 'react';
import { useQuery } from 'react-query';
import { useParams } from 'react-router-dom';
import { Link } from 'react-router-dom';
import { Label, List } from 'semantic-ui-react';

import { DocumentNode } from '../../components/DocumentNode';
import { ClearFilterButton, FilterButton } from '../../components/FilterButton/FilterButton';
import { LoadingAndErrorIndicator } from '../../components/LoadingAndErrorIndicator';
import { useEnvironment } from '../../hooks';
import { applyFilters, filterContext } from '../../hooks/applyFilters';
import { Document } from '../../types';
import { groupLinksByType } from '../../utils';
import { SearchResults } from '../Search/components/SearchResults';
import { Document, LinkedTreeDocument, TreeDocument } from '../../types';
import { getDocumentDisplayName } from '../../utils';
import { getInternalUrl } from '../../utils/document';

export const Explorer = () => {
const { apiUrl } = useEnvironment();
const [loading, setLoading] = useState<boolean>(false);
const [filter, setFilter] = useState("");
const [searchSummary, setSearchSummary] = useState(0);
const [data, setData] = useState<Document[]>()
const [rootCREs, setRootCREs] = useState<Document[]>()
const [filteredData, setFilteredData] = useState<Document[]>()
const [filter, setFilter] = useState('');
const [store, setStore] = useState<Record<string, TreeDocument>>(
JSON.parse(localStorage.getItem('record-store') || '{}')
);
const [tree, setTree] = useState<TreeDocument[]>(JSON.parse(localStorage.getItem('record-tree') || '[]'));
const [filteredTree, setFilteredTree] = useState<TreeDocument[]>();

useQuery<{ data: Document }, string>(
'root_cres',
() =>
fetch(`${apiUrl}/root_cres`)
.then((res) => res.json())
.then((resjson) => {
setRootCREs(resjson.data);
return resjson;
}),
const applyHighlight = (text, term) => {
if (!term) return text;
var index = text.toLowerCase().indexOf(term);
if (index >= 0) {
return (
<>
{text.substring(0, index)}
<span className="highlight">{text.substring(index, index + term.length)}</span>
{text.substring(index + term.length)}
</>
);
}
return text;
};

const getStoreKey = (doc: Document): string => {
if (doc.doctype === 'CRE') return doc.id;
return `${doc.name}-${doc.sectionID}`;
};

const buildTree = (doc: Document, keyPath: string[] = []): TreeDocument => {
if (!doc) {
return doc;
}
const selfKey = getStoreKey(doc);
keyPath.push(selfKey);
const storedDoc = store[selfKey];
if (storedDoc.links) {
storedDoc.standards = storedDoc.links.filter(
(x) => x.ltype !== 'Contains' && x.document && x.document.doctype && x.document.doctype !== 'CRE'
);
storedDoc.links = storedDoc.links
.filter((x) => x.ltype === 'Contains' && x.document && !keyPath.includes(getStoreKey(x.document)))
.map((x) => ({ ltype: x.ltype, document: buildTree(x.document, keyPath) }));
}
return storedDoc;
};

const getTreeQuery = useQuery('root_cres', () => {
if (!tree.length && Object.keys(store).length) {
setLoading(true);
fetch(`${apiUrl}/root_cres`)
.then((res) => res.json())
.then((resjson) => {
const treeData = resjson.data.map((x) => buildTree(x));
localStorage.setItem('record-tree', JSON.stringify(treeData));
setFilteredTree(treeData);
setTree(treeData);
});
}
},
{
retry: false,
enabled: false,
Expand All @@ -39,110 +80,165 @@ export const Explorer = () => {
},
}
);
const docs = localStorage.getItem("documents")
useEffect(()=>{
if (docs != null) {
setData(JSON.parse(docs).sort((a, b) => (a.id + '').localeCompare(b.id + '')));
setFilteredData(data)
}
},[docs])

const query = useQuery(
'everything',
() => {
if (docs == null) {
fetch(`${apiUrl}/everything`)
.then((res) => { return res.json() })
.then((resjson) => {
return resjson.data
}).then((data) => {
if (data) {
localStorage.setItem("documents", JSON.stringify(data));
setData(data)
}
}),

const getStoreQuery = useQuery('everything', () => {
if (!Object.keys(store).length) {
setLoading(true);
fetch(`${apiUrl}/everything`)
.then((res) => {
return res.json();
})
.then((resjson) => {
return resjson.data;
})
.then((data) => {
if (data) {
let store = {};
data.forEach((x) => {
store[getStoreKey(x)] = {
...x,
displayName: getDocumentDisplayName(x),
url: getInternalUrl(x),
};
});
localStorage.setItem('record-store', JSON.stringify(store));
setStore(store);
getTreeQuery.refetch();
}
}),
{
retry: false,
enabled: false,
onSettled: () => {
setLoading(false);
},
}
}
};
}
});

const filterFunc = (doc: TreeDocument, term: string) =>
doc.displayName && doc.displayName.toLowerCase().includes(term);
const recursiveFilter = (doc: TreeDocument, term: string) => {
if (doc.links) {
const filteredLinks: LinkedTreeDocument[] = [];
doc.links.forEach((x) => {
const docu = recursiveFilter(x.document, term);
if (docu) {
filteredLinks.push({ ltype: x.ltype, document: docu });
}
});
doc.links = filteredLinks;
}
);
if (filterFunc(doc, term) || doc.links?.length) {
return doc;
}
return null;
};

useEffect(() => {
if (tree.length) {
const treeCopy = structuredClone(tree);
const filTree: TreeDocument[] = [];
treeCopy
.map((x) => recursiveFilter(x, filter))
.forEach((x) => {
if (x) {
filTree.push(x);
}
});
setFilteredTree(filTree);
}
}, [filter, tree, setFilteredTree]);

useEffect(() => {
window.scrollTo(0, 0);
setLoading(true);
query.refetch();
getStoreQuery.refetch();
getTreeQuery.refetch();
}, []);


if (!data?.length) {
const docs = localStorage.getItem("documents")
if (docs) {
setData(JSON.parse(docs).sort((a, b) => (a.id + '').localeCompare(b.id + '')));
setFilteredData(data)
}
}

function processNode(item) {
if (!item) {
return (<></>)
return <></>;
}
return (
<div className="group" >
<div className="group-1">
<div className='group-2'>
<a target="_blank" href={"https://opencre.org/cre/" + item?.id}>
{item?.name}
</a>
</div>
<div /*style="font-size: 90%"*/>
{item?.links?.forEach(child => processNode(child))}
</div>
</div>
</div>
)
<List.Item key={Math.random()}>
<List.Icon name="folder" />
<List.Content>
<List.Header>
<Link to={item.url}>{applyHighlight(item.displayName, filter)}</Link>
</List.Header>
{item.standards && item.standards.length > 0 && (
<List.Description>
<Label.Group size="tiny" tag>
{[...new Set(item.standards.map((x) => x.document.name))].sort().map((x) => (
<Link key={Math.random()} to={`/node/standard/${x}`}>
<Label>{x}</Label>
</Link>
))}
</Label.Group>
</List.Description>
)}
{item.links && item.links.length > 0 && (
<List.List>{item.links.map((child) => processNode(child.document))}</List.List>
)}
</List.Content>
</List.Item>
);
}
function update(event) {
setFilter(event.target.value)
setFilteredData(data?.filter(item => item.name.toLowerCase().includes(filter)))
setFilter(event.target.value.toLowerCase());
}

return (<>
{/* <LoadingAndErrorIndicator loading={loading} error={query.error} /> */}
<div id="explorer-content">
<h1>
<img src="assets/logo.png" />
<b>Open CRE Explorer</b>
</h1>
<p>A visual explorer of Open Common Requirement Enumerations (CREs).
Data source: <a target="_blank" href="https://opencre.org/">opencre.org</a>.</p>
return (
<>
<div id="explorer-content">
<h1>
<b>Explorer</b>
</h1>
<p>
A visual explorer of Open Common Requirement Enumerations (CREs). Originally created by:{' '}
<a target="_blank" href="https://zeljkoobrenovic.github.io/opencre-explorer/">
Zeljko Obrenovic
</a>
.
</p>

<div id="explorer-wrapper">
<div>
<input id="filter" type="text" placeholder="search..."
onKeyUp={update} />
<div id="search-summary"></div>
</div>
<div id="graphs">graphs (3D):
<a target="_blank" href="visuals/force-graph-3d-all.html">CRE dependencies</a> -
<a target="_blank" href="visuals/force-graph-3d-contains.html">hierarchy only</a> -
<a target="_blank" href="visuals/force-graph-3d-related.html">related only</a> |
<a target="_blank" href="visuals/force-graph-3d-linked.html">links to external standards</a> |
<a target="_blank" href="visuals/circles.html">zoomable circles</a>
<div id="explorer-wrapper">
<div>
<input id="filter" type="text" placeholder="search..." onKeyUp={update} />
<div id="search-summary"></div>
</div>
<div id="graphs">
graphs (3D):
<a target="_blank" href="visuals/force-graph-3d-all.html">
CRE dependencies
</a>{' '}
-
<a target="_blank" href="visuals/force-graph-3d-contains.html">
hierarchy only
</a>{' '}
-
<a target="_blank" href="visuals/force-graph-3d-related.html">
related only
</a>{' '}
|
<a target="_blank" href="visuals/force-graph-3d-linked.html">
links to external standards
</a>{' '}
|
<a target="_blank" href="visuals/circles.html">
zoomable circles
</a>
</div>
</div>
<LoadingAndErrorIndicator loading={loading} error={null} />
<List>
{filteredTree?.map((item) => {
return processNode(item);
})}
</List>
</div>
<div id="content"></div>
{filteredData?.map(item => {
return processNode(item)
})}
</div>

</>
</>
);
};
15 changes: 13 additions & 2 deletions application/frontend/src/routes.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,25 @@
import { ReactNode } from 'react';

import { BROWSEROOT, CRE, DEEPLINK, GRAPH, INDEX, SEARCH, SECTION, SECTION_ID, STANDARD, EXPLORER } from './const';
import {
BROWSEROOT,
CRE,
DEEPLINK,
EXPLORER,
GRAPH,
INDEX,
SEARCH,
SECTION,
SECTION_ID,
STANDARD,
} from './const';
import { CommonRequirementEnumeration, Graph, Search, Standard } from './pages';
import { BrowseRootCres } from './pages/BrowseRootCres/browseRootCres';
import { Chatbot } from './pages/chatbot/chatbot';
import { Deeplink } from './pages/Deeplink/Deeplink';
import { Explorer } from './pages/Explorer/explorer';
import { MembershipRequired } from './pages/MembershipRequired/MembershipRequired';
import { SearchName } from './pages/Search/SearchName';
import { StandardSection } from './pages/Standard/StandardSection';
import { Explorer } from './pages/Explorer/explorer';

export interface IRoute {
path: string;
Expand Down
Loading

0 comments on commit 97ff328

Please sign in to comment.