organic_ast_explorer/src/Explorer.tsx

109 lines
3.2 KiB
TypeScript
Raw Normal View History

import React, { ReactNode, useEffect, useMemo, useRef, useState } from "react";
import "./Explorer.css";
2024-01-22 02:11:38 +00:00
import { Highlight } from "./highlight";
2024-01-23 02:59:59 +00:00
import { buildShadow } from "./shadow";
import OrgAst, { OrgNodeReference } from "./OrgAst";
2024-01-27 19:56:12 +00:00
import { parse_org } from "../../organic/target/wasm32-unknown-unknown/js/wasm";
2024-01-13 03:07:59 +00:00
const default_org_source: string = `* Welcome to the Organic Ast Explorer!
Type your Org [fn:1] source in this text box, and it will be parsed by Organic [fn:2] that has been compiled into wasm and embedded in this page. The resulting AST will be rendered to the right.
In the AST on the right, you can:
1. Click on an AST node to highlight the corresponding portion of the Org source on the left.
2. Expand/Collapse the children, properties, and standard properties.
* Footnotes
[fn:1] https://orgmode.org/
[fn:2] https://code.fizz.buzz/talexander/organic
`;
2024-01-28 22:32:27 +00:00
interface ExplorerProps {
defaultValue?: string;
}
function Explorer({ defaultValue = default_org_source }: ExplorerProps) {
2024-01-13 02:42:12 +00:00
function handleChange(event: React.ChangeEvent<HTMLTextAreaElement>) {
setValue(event.target.value);
clearHighlights();
2024-01-13 02:42:12 +00:00
}
const [value, setValue] = useState(defaultValue);
const [highlights, setHighlights] = useState<Array<Highlight>>([]);
const astTree = useMemo(() => {
const astTree = parse_org(value);
console.log(JSON.stringify(astTree));
return astTree;
}, [value]);
function setHighlight(nodes: OrgNodeReference[]) {
let new_highlights = nodes.map((node: OrgNodeReference) => {
return new Highlight(node.start - 1, node.end - 1);
});
new_highlights.sort(function (a, b) {
if (a.start < b.start) return -1;
if (a.start > b.start) return 1;
return 0;
});
2024-01-27 19:04:44 +00:00
setHighlights(new_highlights);
}
function addHighlight(start: number, end: number) {
2024-01-23 02:59:59 +00:00
let new_highlights = [...highlights, new Highlight(start, end)];
2024-01-24 03:06:47 +00:00
new_highlights.sort(function (a, b) {
2024-01-23 02:59:59 +00:00
if (a.start < b.start) return -1;
if (a.start > b.start) return 1;
return 0;
});
setHighlights(new_highlights);
}
function clearHighlights() {
setHighlights([]);
}
const textAreaRef = useRef<HTMLTextAreaElement>(null);
const shadowRef = useRef<HTMLDivElement>(null);
function onTextAreaScroll() {
if (shadowRef.current !== null && textAreaRef.current !== null) {
const textAreaScrollTop = textAreaRef.current.scrollTop;
shadowRef.current.scrollTop = textAreaScrollTop;
}
}
useEffect(() => {
// Make sure the text area and shadow div start out in sync.
onTextAreaScroll();
}, []);
2024-01-13 02:42:12 +00:00
return (
<div className="Explorer">
<div className="Explorer-textwrapper">
2024-01-22 02:11:38 +00:00
<textarea
ref={textAreaRef}
2024-01-22 02:11:38 +00:00
onChange={handleChange}
className="Explorer-textarea"
2024-01-22 02:11:38 +00:00
value={value}
onScroll={onTextAreaScroll}
2024-01-22 02:11:38 +00:00
/>
<div ref={shadowRef} className="Explorer-underlay">
{buildShadow(highlights, value)}
<br />
</div>
2024-01-13 03:16:31 +00:00
</div>
2024-01-27 19:30:15 +00:00
<OrgAst
setHighlight={setHighlight}
clearHighlights={clearHighlights}
value={value}
2024-01-27 19:56:12 +00:00
astTree={astTree}
2024-01-27 19:30:15 +00:00
/>
2024-01-13 03:07:59 +00:00
</div>
2024-01-13 02:42:12 +00:00
);
}
export default Explorer;