Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Expand tree on concept page #16

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
94 changes: 75 additions & 19 deletions packages/cli/src/commands/make-site.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { RenderContext } from "@rdf-toolkit/explorer-views/context";
import renderHTML, { HtmlContent } from "@rdf-toolkit/explorer-views/jsx/html";
import renderHTML, { HtmlContent, HtmlElement } from "@rdf-toolkit/explorer-views/jsx/html";
import renderMain from "@rdf-toolkit/explorer-views/pages/main";
import renderNavigation from "@rdf-toolkit/explorer-views/pages/navigation";
import renderFooter from "@rdf-toolkit/explorer-views/sections/footer";
Expand Down Expand Up @@ -124,13 +124,61 @@ class Website implements RenderContext {
}
}

function renderIndex(context: Website, links: HtmlContent, scripts: HtmlContent, navigation: HtmlContent): HtmlContent {
function create_open_tree_script(context: Website, classes: (IRIOrBlankNode | string)[]): HtmlElement {
// generate the source for a javascript function which sets the open attribute
// on all <details> elements in the document that are parents of the classes in the 'classes' array

// first, get all of the reachable classes from the 'classes' array
const reachable_classes: Class[] = [];
for (const class_ of classes) {
const iri = typeof class_ === "string" ? IRI.create(class_) : class_;
const defn = context.schema.classes.get(iri);
if (defn) {
get_reachable_classes(context, defn, reachable_classes);
}
}

// generate the source for a javascript function which sets the open attribute
// on all <details> elements in the document if the class is in the 'reachable_classes' array
const script = `
function set_open() {
const details = document.querySelectorAll("details");
const reachable_classes = [${reachable_classes.map(c => `"${c.id.value}"`).join(", ")}];
for (const detail of details) {
const iri = detail.getAttribute("iri");
if (reachable_classes.includes(iri)) {
detail.open = true;
}
}
}
document.addEventListener("DOMContentLoaded", set_open);
`;

return <script>{script}</script>;
}

function get_reachable_classes(context: Website, class_: Class, reachable_classes: Class[]) {
reachable_classes.push(class_);
for (const parent_class of class_.subClassOf as IRIOrBlankNode[]) {
// get the class object from the IRI
const defn = context.schema.classes.get(parent_class);
// if it's not null, then call the function recursively
if (defn) {
get_reachable_classes(context, defn, reachable_classes);
}
}
}

function renderIndex(context: Website, links: HtmlContent, scripts: HtmlContent, navigation: HtmlContent, expand?: Array<string>): HtmlContent {
// make a script to expand the classes in the 'expand' array automatically
const expand_class_js = create_open_tree_script(context, expand || []);
return <html lang="en-US">
<head>
<meta charset="utf-8" />
<title>{context.title}</title>
{links}
{scripts}
{expand_class_js}
</head>
<body>
<nav>
Expand Down Expand Up @@ -185,22 +233,29 @@ function renderPage(iri: string, context: Website, links: HtmlContent, scripts:

const main = renderMain(subject, iri in context.documents ? context.documents[iri] : null, context);

return <html lang="en-US">
<head>
<meta charset="utf-8" />
<title>{title} &ndash; {context.title}</title>
{links}
{scripts}
</head>
<body>
<nav>
{navigation}
</nav>
<main>
{main}
</main>
</body>
</html>;
// from the context, get the IRI
const open_class_js = create_open_tree_script(context, [subject]);

// Render the HTML
return (
<html lang="en-US">
<head>
<meta charset="utf-8" />
<title>{title} &ndash; {context.title}</title>
{links}
{scripts}
{open_class_js}
</head>
<body>
<nav>
{navigation}
</nav>
<main>
{main}
</main>
</body>
</html>
);
}

function resolveHref(url: string, base: string): string {
Expand Down Expand Up @@ -265,7 +320,8 @@ export default function main(options: Options): void {
site.write(assets[assetPath], project.package.read(assetPath));
}

site.write(INDEX_FILE_NAME, Buffer.from("<!DOCTYPE html>\n" + renderHTML(renderIndex(context, links, scripts, navigation))));
// when rendering the Index, expand all of the siteOptions.expand IRIs
site.write(INDEX_FILE_NAME, Buffer.from("<!DOCTYPE html>\n" + renderHTML(renderIndex(context, links, scripts, navigation, project.json.siteOptions?.expand))));
site.write(ERROR_FILE_NAME, Buffer.from("<!DOCTYPE html>\n" + renderHTML(render404(context, links, scripts, navigation))));

for (const iri in context.outputs) {
Expand Down
4 changes: 3 additions & 1 deletion packages/cli/src/model/project.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ export interface SiteConfig {
outDir?: string;
roots?: Array<string>;
cleanUrls?: boolean;
expand?: Array<string>; // terms to expand automatically in the tree
}

export namespace SiteConfig {
Expand All @@ -45,7 +46,8 @@ export namespace SiteConfig {
&& (Is.undefined(candidate.baseURL) || Is.string(candidate.baseURL))
&& (Is.undefined(candidate.outDir) || Is.string(candidate.outDir))
&& (Is.undefined(candidate.roots) || Is.typedArray(candidate.roots, Is.string))
&& (Is.undefined(candidate.cleanUrls) || Is.boolean(candidate.cleanUrls));
&& (Is.undefined(candidate.cleanUrls) || Is.boolean(candidate.cleanUrls))
&& (Is.undefined(candidate.expand) || Is.typedArray(candidate.expand, Is.string));
}
}

Expand Down
4 changes: 3 additions & 1 deletion packages/explorer-views/src/components/treeview.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { Ix } from "@rdf-toolkit/iterable";
import { IRI, IRIOrBlankNode } from "@rdf-toolkit/rdf/terms";
import { HtmlContent } from "../jsx/html.js";
import "./treeview.css";

export interface TreeNode {
readonly id: IRIOrBlankNode;
readonly label: HtmlContent;
readonly children?: Iterable<TreeNode>;
readonly open?: boolean;
Expand All @@ -13,7 +15,7 @@ function renderNode(node: TreeNode, depth: number): HtmlContent {
.map(child => renderNode(child, node.open ? 1 : depth + 1))
.wrap(children =>
<li>
<details open={node.open || depth > 3}>
<details open={node.open || depth > 3} iri={node.id.value}>
<summary>{node.label}</summary>
{depth >= 9 ? "\u2026" : <ul>{children}</ul>}
</details>
Expand Down
4 changes: 4 additions & 0 deletions packages/explorer-views/src/jsx/html.ts
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,10 @@ export default function render(content: HtmlContent): string {
}
return result;
}
// if content is a script tag, and no 'src' attribute is provided, render the content as a script tag
else if (content && content.type === "script" && !content.props.src) {
return "<script>" + content.props.children + "</script>";
}
else if (content) {
let result = "<" + content.type + renderAttributes(content.props) + ">";
if (!(content.type in noEndTag)) {
Expand Down
1 change: 1 addition & 0 deletions packages/explorer-views/src/jsx/jsx-runtime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -371,6 +371,7 @@ declare global {

details: GlobalAttributes & {
open?: boolean;
iri?: string;
};

summary: GlobalAttributes;
Expand Down
8 changes: 4 additions & 4 deletions packages/explorer-views/src/pages/navigation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,14 @@ import "./navigation.css";

function createTree<T extends Class | Property | Ontology>(items: Iterable<T>, parents: (item: T) => Iterable<IRIOrBlankNode>, context: RenderContext, rootIRIs: Iterable<string> | null, options?: RenderOptions): TreeNode[] {
const roots: TreeNode[] = [];
const tree: { [P in string]: { readonly label: HtmlContent; readonly children: TreeNode[], readonly open: boolean } } = {};
const tree: { [P in string]: { readonly id: IRIOrBlankNode, readonly label: HtmlContent; readonly children: TreeNode[], readonly open: boolean } } = {};

for (const item of items) {
if (IRI.is(item.id)) {
const node = tree[item.id.value] || (tree[item.id.value] = { label: renderRdfTerm(item.id, context, options), children: [], open: item.id === Rdfs.Resource || item.id === Owl.Thing });
const node = tree[item.id.value] || (tree[item.id.value] = { id: item.id, label: renderRdfTerm(item.id, context, options), children: [], open: item.id === Rdfs.Resource || item.id === Owl.Thing });
let hasParents = false;
for (const parent of parents(item)) {
(tree[parent.value] || (tree[parent.value] = { label: renderRdfTerm(parent, context, options), children: [], open: parent === Rdfs.Resource || parent === Owl.Thing })).children.push(node);
(tree[parent.value] || (tree[parent.value] = { id: parent, label: renderRdfTerm(parent, context, options), children: [], open: parent === Rdfs.Resource || parent === Owl.Thing })).children.push(node);
hasParents = true;
}
if (!hasParents) {
Expand Down Expand Up @@ -63,7 +63,7 @@ export default function render(title: string | undefined, context: RenderContext
{
id: "navigation-files",
label: "Files",
content: renderTreeView(Object.keys(context.documents).sort().map<TreeNode>(x => ({ label: renderRdfTerm(IRI.create(x), context, { rawIRIs: true, hideError: true }) })))
content: renderTreeView(Object.keys(context.documents).sort().map<TreeNode>(x => ({ id: IRI.create(x), label: renderRdfTerm(IRI.create(x), context, { rawIRIs: true, hideError: true }) })))
},
])
}
Expand Down
Loading