Skip to content

Commit

Permalink
Added: FE Explorer and related visuals
Browse files Browse the repository at this point in the history
  • Loading branch information
john681611 committed Nov 6, 2023
1 parent bd30918 commit b52b2e2
Show file tree
Hide file tree
Showing 16 changed files with 1,398 additions and 25 deletions.
11 changes: 7 additions & 4 deletions application/frontend/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { QueryClient, QueryClientProvider } from 'react-query';
import { BrowserRouter } from 'react-router-dom';

import { GlobalFilterState, filterContext } from './hooks/applyFilters';
import { DataProvider } from './providers/DataProvider';
import { MainContentArea } from './scaffolding';

const queryClient = new QueryClient();
Expand All @@ -14,10 +15,12 @@ const App = () => (
<div className="app">
<filterContext.Provider value={GlobalFilterState}>
<QueryClientProvider client={queryClient}>
<BrowserRouter>
<Toaster />
<MainContentArea />
</BrowserRouter>
<DataProvider>
<BrowserRouter>
<Toaster />
<MainContentArea />
</BrowserRouter>
</DataProvider>
</QueryClientProvider>
</filterContext.Provider>
</div>
Expand Down
3 changes: 3 additions & 0 deletions application/frontend/src/const.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
export const TWO_DAYS_MILLISECONDS = 1.728e8;

export const TYPE_IS_PART_OF = 'Is Part Of';
export const TYPE_LINKED_TO = 'Linked To';
export const TYPE_LINKED_FROM = 'Linked From';
Expand Down Expand Up @@ -37,5 +39,6 @@ export const GRAPH = '/graph';
export const DEEPLINK = '/deeplink';
export const BROWSEROOT = '/root_cres';
export const GAP_ANALYSIS = '/map_analysis';
export const EXPLORER = '/explorer';

export const GA_STRONG_UPPER_LIMIT = 2; // remember to change this in the Python code too
24 changes: 24 additions & 0 deletions application/frontend/src/hooks/useWindowDimensions.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { useEffect, useState } from 'react';

function getWindowDimensions() {
const { innerWidth: width, innerHeight: height } = window;
return {
width,
height,
};
}

export default function useWindowDimensions() {
const [windowDimensions, setWindowDimensions] = useState(getWindowDimensions());

useEffect(() => {
function handleResize() {
setWindowDimensions(getWindowDimensions());
}

window.addEventListener('resize', handleResize);
return () => window.removeEventListener('resize', handleResize);
}, []);

return windowDimensions;
}
109 changes: 109 additions & 0 deletions application/frontend/src/pages/Explorer/explorer.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
#explorer-content {
font-family: Vollkorn, Ubuntu, Optima, Segoe, Segoe UI, Candara, Calibri, Arial, sans-serif;
margin: 40px;
text-align: left;
}

#explorer-content>.group {
border-top: 1px dotted lightgrey;
border-left: 6px solid lightgrey;
margin: 4px;
margin-left: 46px;
padding: 5px;
vertical-align: top;
background-color: rgba(200, 200, 200, 0.2);
}

#explorer-content>.doc-id {
display: inline-block;
border-radius: 6px;
margin: 3px;
padding: 3px;
background-color: #f8f8f8;
vertical-align: top;
font-size: 90%;
}

#explorer-content>.tag {
display: inline-block;
border: 1px solid lightgrey;
border-radius: 6px;
margin: 3px;
padding: 3px;
background-color: #f8f8f8;
vertical-align: top;
font-size: 90%;
max-width: 80px;
white-space: nowrap;
overflow: hidden;
}

#explorer-content>a {
text-decoration: none;
}

#explorer-content>.icon {
width: 140px;
height: 140px;
object-fit: cover;
border-radius: 4px;
margin-top: 26px;
margin-bottom: 20px;
filter: grayscale(100%);
}

#explorer-content>::placeholder {
color: lightgrey;
opacity: 1;
}

#explorer-content>:-ms-input-placeholder {
color: lightgrey;
}

#explorer-content>::-ms-input-placeholder {
color: lightgrey;
}


#explorer-content>div {
vertical-align: top;
}

#explorer-content>content {
margin-left: -20px;
}

#explorer-content>h1 {
font-size: 50px;
margin-bottom: 5px;
margin-left: 16px;
}

#explorer-content>img {
vertical-align: middle;
height: 80px;
}

b {
padding-top: 20px;
padding-left: 12px;
}
p{
color: grey; margin-left: 26px;
}
#explorer-wrapper{
margin-left: 24px; margin-top: 20px; margin-bottom: 20px; color: grey;
}

#filter{
font-size: 16px; height: 32px; width: 320px; margin-bottom: 10px;
}
#search-summary{
display: inline-block; vertical-align: middle;
}
#graphs{font-size: 80%; color: grey;}

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

import React, { useEffect, useState } from 'react';
import { Link } from 'react-router-dom';
import { Label, List } from 'semantic-ui-react';

import { LoadingAndErrorIndicator } from '../../components/LoadingAndErrorIndicator';
import { useDataStore } from '../../providers/DataProvider';
import { LinkedTreeDocument, TreeDocument } from '../../types';

export const Explorer = () => {
const { dataLoading, dataTree } = useDataStore();
const [filter, setFilter] = useState('');
const [filteredTree, setFilteredTree] = useState<TreeDocument[]>();

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 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 (dataTree.length) {
const treeCopy = structuredClone(dataTree);
const filTree: TreeDocument[] = [];
treeCopy
.map((x) => recursiveFilter(x, filter))
.forEach((x) => {
if (x) {
filTree.push(x);
}
});
setFilteredTree(filTree);
}
}, [filter, dataTree, setFilteredTree]);

function processNode(item) {
if (!item) {
return <></>;
}
const contains = item.links.filter((x) => x.ltype === 'Contains');
const linkedTo = item.links.filter((x) => x.ltype === 'Linked To');
return (
<List.Item key={Math.random()}>
<List.Icon name="folder" />
<List.Content>
<List.Header>
<Link to={item.url}>{applyHighlight(item.displayName, filter)}</Link>
</List.Header>
{linkedTo.length > 0 && (
<List.Description>
<Label.Group size="tiny" tag>
{[...new Set(linkedTo.map((x: LinkedTreeDocument) => x.document.name))]
.sort()
.map((x: string) => (
<Link key={Math.random()} to={`/node/standard/${x}`}>
<Label>{x}</Label>
</Link>
))}
</Label.Group>
</List.Description>
)}
{contains.length > 0 && (
<List.List>{contains.map((child) => processNode(child.document))}</List.List>
)}
</List.Content>
</List.Item>
);
}
function update(event) {
setFilter(event.target.value.toLowerCase());
}

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="explorer/force_graph">
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="explorer/circles">
zoomable circles
</a>
</div>
</div>
<LoadingAndErrorIndicator loading={dataLoading} error={null} />
<List>
{filteredTree?.map((item) => {
return processNode(item);
})}
</List>
</div>
</>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@

.node {
cursor: pointer;
}

.node:hover {
stroke: #000;
stroke-width: 1.5px;
}

.node--leaf {
fill: white;
}

.label {
font: 11px "Helvetica Neue", Helvetica, Arial, sans-serif;
text-anchor: middle;
text-shadow: 0 1px 0 #fff, 1px 0 0 #fff, -1px 0 0 #fff, 0 -1px 0 #fff;
}

.label,
.node--root,
.node--leaf {
pointer-events: none;
}

.ui.button.screen-size-button {
position: absolute;
right: 0;
margin: 0;
background-color: transparent;
}
Loading

0 comments on commit b52b2e2

Please sign in to comment.