organic_ast_explorer/src/OrgAst.tsx

438 lines
11 KiB
TypeScript
Raw Normal View History

2024-01-24 02:27:12 +00:00
import React, { ReactNode, useState } from "react";
import styles from "./OrgAst.module.css";
2024-01-27 22:01:09 +00:00
import { Fragment } from "react";
2024-01-24 02:27:12 +00:00
2024-01-27 19:30:15 +00:00
const OrgAst = (props: {
setHighlight: Function;
clearHighlights: Function;
2024-01-27 22:01:09 +00:00
astTree: any;
2024-01-27 19:30:15 +00:00
value: string;
}) => {
const [selectedNode, setSelectedNode] = useState<OrgNodeReference | null>(
null,
);
const [hoveredNode, setHoveredNode] = useState<OrgNodeReference | null>(null);
function selectNode(uid: string, start: number, end: number) {
const new_node: OrgNodeReference = { uid: uid, start: start, end: end };
props.setHighlight([new_node, hoveredNode].filter((node) => node !== null));
setSelectedNode({ uid: uid, start: start, end: end });
}
function startHoverNode(uid: string, start: number, end: number) {
const new_node: OrgNodeReference = { uid: uid, start: start, end: end };
props.setHighlight(
[selectedNode, new_node].filter((node) => node !== null),
);
setHoveredNode({ uid: uid, start: start, end: end });
}
function endHoverNode(uid: string) {
props.setHighlight([selectedNode].filter((node) => node !== null));
setHoveredNode(null);
2024-01-27 19:04:44 +00:00
}
2024-01-27 19:56:12 +00:00
if (props.astTree.status !== "success") {
return <div className={styles.OrgAst}>Error! {props.astTree.content}</div>;
2024-01-24 02:27:12 +00:00
} else {
return (
2024-01-24 03:06:47 +00:00
<div className={styles.OrgAst}>
2024-01-27 19:30:15 +00:00
<OrgAstNode
key="^"
uid="^"
2024-01-27 19:30:15 +00:00
selectNode={selectNode}
startHoverNode={startHoverNode}
endHoverNode={endHoverNode}
2024-01-27 19:56:12 +00:00
node={props.astTree.content}
2024-01-27 19:30:15 +00:00
selectedNode={selectedNode}
fullSource={props.value}
2024-01-27 19:30:15 +00:00
/>
2024-01-24 03:06:47 +00:00
</div>
2024-01-24 02:27:12 +00:00
);
}
2024-01-24 03:06:47 +00:00
};
2024-01-24 02:27:12 +00:00
interface OrgNodeReference {
uid: string;
start: number;
end: number;
}
2024-01-27 19:30:15 +00:00
const OrgAstNode = (props: {
selectNode: Function;
startHoverNode: Function;
endHoverNode: Function;
2024-01-27 19:30:15 +00:00
node: any;
uid: string;
selectedNode: OrgNodeReference | null;
fullSource: string;
2024-01-27 19:30:15 +00:00
}) => {
const [isHovered, setIsHovered] = useState(false);
2024-01-27 19:04:44 +00:00
function selectNode() {
2024-01-27 19:30:15 +00:00
props.selectNode(
props.uid,
props.node["standard-properties"]["begin"],
props.node["standard-properties"]["end"],
);
}
function hoverNode() {
props.startHoverNode(
props.uid,
props.node["standard-properties"]["begin"],
props.node["standard-properties"]["end"],
2024-01-27 19:30:15 +00:00
);
setIsHovered(true);
2024-01-27 19:04:44 +00:00
}
function endHoverNode() {
props.endHoverNode(props.uid);
setIsHovered(false);
}
function unicodeAwareSlice(text: string, start: number, end: number) {
// Boooo javascript
let i = 0;
let output = "";
for (const chr of text) {
if (i >= end) {
break;
}
if (i >= start) {
output += chr;
}
++i;
}
return output;
}
let nodeClassName = styles.OrgAstNode;
if (props.selectedNode?.uid === props.uid) {
nodeClassName = nodeClassName + " " + styles.selected;
}
if (isHovered) {
nodeClassName = nodeClassName + " " + styles.hovered;
}
2024-01-27 19:04:44 +00:00
const selfSource = JSON.stringify(
unicodeAwareSlice(
props.fullSource,
props.node["standard-properties"].begin - 1,
props.node["standard-properties"].end - 1,
),
);
2024-01-28 03:04:22 +00:00
if (props.node["ast-node"] === "plain-text") {
return (
<div className={nodeClassName}>
<div
className={styles.OrgAstNodeType}
onClick={selectNode}
onMouseEnter={hoverNode}
onMouseLeave={endHoverNode}
title={selfSource}
>
<span>{props.node["ast-node"]}</span>
<span>{selfSource}</span>
</div>
</div>
);
}
2024-01-24 02:27:12 +00:00
return (
2024-01-27 19:04:44 +00:00
<div className={nodeClassName}>
<div
className={styles.OrgAstNodeType}
onClick={selectNode}
onMouseEnter={hoverNode}
onMouseLeave={endHoverNode}
title={selfSource}
>
<span>{props.node["ast-node"]}</span>
<span>{selfSource}</span>
2024-01-27 19:30:15 +00:00
</div>
2024-01-24 04:07:26 +00:00
<details>
2024-01-24 03:27:31 +00:00
<summary>Standard Properties</summary>
2024-01-27 22:01:09 +00:00
<OrgPropertiesList
selectNode={props.selectNode}
startHoverNode={props.startHoverNode}
endHoverNode={props.endHoverNode}
2024-01-27 22:01:09 +00:00
parentUniqueId={props.uid}
selectedNode={props.selectedNode}
properties={props.node["standard-properties"]}
fullSource={props.fullSource}
2024-01-27 22:01:09 +00:00
/>
2024-01-24 03:27:31 +00:00
</details>
2024-01-27 20:27:23 +00:00
{!!Object.keys(props.node.properties).length ? (
<>
<details>
<summary>Properties</summary>
2024-01-27 22:01:09 +00:00
<OrgPropertiesList
selectNode={props.selectNode}
startHoverNode={props.startHoverNode}
endHoverNode={props.endHoverNode}
2024-01-27 20:27:23 +00:00
parentUniqueId={props.uid}
selectedNode={props.selectedNode}
properties={props.node.properties}
fullSource={props.fullSource}
2024-01-27 20:27:23 +00:00
/>
</details>
</>
2024-01-27 22:01:09 +00:00
) : null}
2024-01-24 03:06:47 +00:00
{Array.isArray(props.node.children) && props.node.children.length > 0 ? (
2024-01-24 04:07:26 +00:00
<details open={true}>
2024-01-24 03:27:31 +00:00
<summary>Children</summary>
2024-01-24 04:07:26 +00:00
<div className={styles.OrgAstChildren}>
2024-01-27 19:30:15 +00:00
<OrgAstNodeList
selectNode={props.selectNode}
startHoverNode={props.startHoverNode}
endHoverNode={props.endHoverNode}
parentUniqueId={props.uid}
2024-01-27 19:30:15 +00:00
selectedNode={props.selectedNode}
node_list={props.node.children}
fullSource={props.fullSource}
2024-01-27 19:30:15 +00:00
/>
2024-01-24 04:07:26 +00:00
</div>
2024-01-24 03:27:31 +00:00
</details>
2024-01-24 03:06:47 +00:00
) : null}
2024-01-24 02:27:12 +00:00
</div>
);
2024-01-24 03:06:47 +00:00
};
2024-01-24 02:27:12 +00:00
2024-01-27 19:30:15 +00:00
const OrgAstNodeList = (props: {
selectNode: Function;
startHoverNode: Function;
endHoverNode: Function;
2024-01-27 19:30:15 +00:00
parentUniqueId: string;
selectedNode: OrgNodeReference | null;
2024-01-27 19:30:15 +00:00
node_list: any[];
fullSource: string;
2024-01-27 19:30:15 +00:00
}): React.JSX.Element[] => {
2024-01-24 04:07:26 +00:00
return props.node_list.map((node) => {
2024-01-27 22:01:09 +00:00
const uid =
props.parentUniqueId +
"_" +
node["ast-node"] +
"/" +
node["standard-properties"]["begin"] +
"/" +
node["standard-properties"]["end"] +
"#";
2024-01-27 19:30:15 +00:00
return (
<OrgAstNode
key={uid}
uid={uid}
2024-01-27 19:30:15 +00:00
selectNode={props.selectNode}
startHoverNode={props.startHoverNode}
endHoverNode={props.endHoverNode}
2024-01-27 19:30:15 +00:00
selectedNode={props.selectedNode}
node={node}
fullSource={props.fullSource}
2024-01-27 19:30:15 +00:00
/>
);
2024-01-24 04:07:26 +00:00
});
};
2024-01-27 20:27:23 +00:00
const OrgPropertiesList = (props: {
selectNode: Function;
startHoverNode: Function;
endHoverNode: Function;
2024-01-27 20:27:23 +00:00
parentUniqueId: string;
selectedNode: OrgNodeReference | null;
2024-01-27 20:27:23 +00:00
properties: Object;
fullSource: string;
2024-01-27 20:27:23 +00:00
}): React.JSX.Element => {
2024-01-27 22:01:09 +00:00
const entries = Object.entries(props.properties)
.sort((a, b) => {
if (a[0] < b[0]) {
return -1;
} else if (a[0] > b[0]) {
return 1;
} else {
return 0;
}
})
.filter((entry) => {
return !(is_object(entry[1]) && entry[1]["noop"] == "Noop");
})
2024-01-27 22:01:09 +00:00
.map(([key, value]) => {
return (
<Fragment key={key}>
<tr>
<th scope="row">{key}:</th>
<td>
<OrgPropertyValue
selectNode={props.selectNode}
startHoverNode={props.startHoverNode}
endHoverNode={props.endHoverNode}
parentUniqueId={props.parentUniqueId}
selectedNode={props.selectedNode}
value={value}
fullSource={props.fullSource}
/>
</td>
2024-01-27 22:01:09 +00:00
</tr>
</Fragment>
);
});
2024-01-27 20:27:23 +00:00
return (
2024-01-27 21:38:34 +00:00
<table className={styles.OrgAstProperties}>
2024-01-27 22:01:09 +00:00
<tbody>{entries}</tbody>
2024-01-27 21:38:34 +00:00
</table>
2024-01-27 20:27:23 +00:00
);
};
const OrgPropertyValue = (props: {
selectNode: Function;
startHoverNode: Function;
endHoverNode: Function;
parentUniqueId: string;
selectedNode: OrgNodeReference | null;
value: any;
fullSource: string;
}): React.ReactNode => {
2024-01-27 23:12:51 +00:00
if (
props.value === null ||
is_primitive(props.value) ||
(is_array(props.value) && props.value.length === 0)
) {
return JSON.stringify(props.value);
} else if (is_list_of_ast_nodes(props.value)) {
return (
<div className={styles.OrgAstChildren}>
<OrgAstNodeList
selectNode={props.selectNode}
startHoverNode={props.startHoverNode}
endHoverNode={props.endHoverNode}
2024-01-27 23:12:51 +00:00
parentUniqueId={props.parentUniqueId}
selectedNode={props.selectedNode}
node_list={props.value}
fullSource={props.fullSource}
2024-01-27 23:12:51 +00:00
/>
</div>
);
2024-01-28 01:49:43 +00:00
} else if (is_optional_pair(props.value)) {
return (
<table className={styles.OrgAstOptionalPair}>
<tbody>
<tr>
<th scope="row">Optional value:</th>
<td>{JSON.stringify(props.value.optval)}</td>
</tr>
<tr>
<th scope="row">Value:</th>
<td>{JSON.stringify(props.value.val)}</td>
</tr>
</tbody>
</table>
);
2024-01-27 23:12:51 +00:00
} else if (is_object_tree(props.value)) {
return (
<OrgObjectTree
selectNode={props.selectNode}
startHoverNode={props.startHoverNode}
endHoverNode={props.endHoverNode}
2024-01-27 23:12:51 +00:00
parentUniqueId={props.parentUniqueId}
selectedNode={props.selectedNode}
value={props.value}
fullSource={props.fullSource}
2024-01-27 23:12:51 +00:00
/>
);
} else {
alert("Unhandled property value! " + JSON.stringify(props.value));
}
};
2024-01-27 23:12:51 +00:00
interface OrgObjectTreeProps {
selectNode: Function;
startHoverNode: Function;
endHoverNode: Function;
2024-01-27 23:12:51 +00:00
parentUniqueId: string;
selectedNode: OrgNodeReference | null;
2024-01-27 23:12:51 +00:00
value: any;
fullSource: string;
2024-01-27 23:12:51 +00:00
}
function OrgObjectTree({
selectNode,
startHoverNode,
endHoverNode,
2024-01-27 23:12:51 +00:00
parentUniqueId,
selectedNode,
value,
fullSource,
2024-01-27 23:12:51 +00:00
}: OrgObjectTreeProps): React.ReactNode {
const entries = value["object-tree"].map((entry: any) => {
return (
<tbody>
<tr>
<th scope="row">Optional value:</th>
<td>
<OrgAstNodeList
selectNode={selectNode}
startHoverNode={startHoverNode}
endHoverNode={endHoverNode}
2024-01-27 23:12:51 +00:00
parentUniqueId={parentUniqueId}
selectedNode={selectedNode}
node_list={entry[0]}
fullSource={fullSource}
2024-01-27 23:12:51 +00:00
/>
</td>
</tr>
<tr>
<th scope="row">Value:</th>
<td>
<OrgAstNodeList
selectNode={selectNode}
startHoverNode={startHoverNode}
endHoverNode={endHoverNode}
2024-01-27 23:12:51 +00:00
parentUniqueId={parentUniqueId}
selectedNode={selectedNode}
node_list={entry[1]}
fullSource={fullSource}
2024-01-27 23:12:51 +00:00
/>
</td>
</tr>
</tbody>
);
});
return <table className={styles.OrgAstObjectTree}>{entries}</table>;
}
function is_object(val: any): boolean {
return val instanceof Object && !(val instanceof Array);
}
2024-01-27 23:12:51 +00:00
function is_array(val: any): boolean {
return val instanceof Array;
}
function is_primitive(val: any): boolean | null {
if (val === null) {
return null;
}
return !(val instanceof Object || val instanceof Array);
}
function is_list_of_ast_nodes(val: any): boolean {
if (!is_array(val)) {
return false;
}
return val.every((entry: any) => {
return is_object(entry) && entry.hasOwnProperty("ast-node");
});
}
2024-01-28 01:49:43 +00:00
function is_optional_pair(val: any): boolean {
return (
is_object(val) && val.hasOwnProperty("optval") && val.hasOwnProperty("val")
);
2024-01-28 01:49:43 +00:00
}
2024-01-27 23:12:51 +00:00
function is_object_tree(val: any): boolean {
return is_object(val) && val.hasOwnProperty("object-tree");
}
export { OrgAst as default, OrgNodeReference };