Merge branch 'isolate_editor'

This commit is contained in:
Tom Alexander 2024-01-28 18:24:24 -05:00
commit acaf757ce3
Signed by: talexander
GPG Key ID: D3A179C9A53C0EDE
9 changed files with 135 additions and 96 deletions

View File

@ -1,12 +1,12 @@
import React from "react"; import React from "react";
import Editor from "./Editor"; import Explorer from "./Explorer";
import styles from "./App.module.css"; import styles from "./App.module.css";
import "./reset.css"; import "./reset.css";
function App({}) { function App({}) {
return ( return (
<div className={styles.App}> <div className={styles.App}>
<Editor /> <Explorer />
</div> </div>
); );
} }

View File

@ -1,13 +1,4 @@
.Editor { .EditorTextWrapper {
display: flex;
flex-direction: row;
height: 100%;
width: 100%;
}
.Editor-textwrapper {
flex: 1;
flex-basis: 0;
position: relative; position: relative;
background: white; background: white;
font-family: ui-monospace, "Cascadia Code", "Source Code Pro", Menlo, Consolas, font-family: ui-monospace, "Cascadia Code", "Source Code Pro", Menlo, Consolas,
@ -17,8 +8,8 @@
text-align: initial; text-align: initial;
} }
.Editor-textarea, .EditorTextArea,
.Editor-underlay { .EditorUnderlay {
font-family: inherit; font-family: inherit;
font-weight: inherit; font-weight: inherit;
font-size: inherit; font-size: inherit;
@ -28,7 +19,7 @@
line-height: normal; line-height: normal;
} }
.Editor-textarea { .EditorTextArea {
position: absolute; position: absolute;
top: 0; top: 0;
bottom: 0; bottom: 0;
@ -43,7 +34,7 @@
outline: none; outline: none;
} }
.Editor-underlay { .EditorUnderlay {
width: 100%; width: 100%;
height: 100%; height: 100%;
pointer-events: none; pointer-events: none;
@ -52,7 +43,7 @@
word-wrap: break-word; word-wrap: break-word;
overflow-y: scroll; overflow-y: scroll;
.highlighted { .EditorHighlighted {
background: #ffff00; background: #ffff00;
} }
} }

4
src/Editor.module.css.d.ts vendored Normal file
View File

@ -0,0 +1,4 @@
export const EditorTextWrapper: string;
export const EditorTextArea: string;
export const EditorUnderlay: string;
export const EditorHighlighted: string;

View File

@ -1,68 +1,25 @@
import React, { ReactNode, useEffect, useMemo, useRef, useState } from "react"; import React, { useEffect, useRef } from "react";
import "./Editor.css";
import { Highlight } from "./highlight"; import { Highlight } from "./highlight";
import { buildShadow } from "./shadow"; import { buildShadow } from "./shadow";
import OrgAst, { OrgNodeReference } from "./OrgAst"; import styles from "./Editor.module.css";
import { parse_org } from "../../organic/target/wasm32-unknown-unknown/js/wasm";
const default_org_source: string = `* Welcome to the Organic Ast Explorer! interface EditorProps {
value: string;
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. setValue: Function;
highlights: Highlight[];
In the AST on the right, you can: clearHighlights: Function;
}
1. Click on an AST node to highlight the corresponding portion of the Org source on the left. function Editor({
2. Expand/Collapse the children, properties, and standard properties. value,
setValue,
* Footnotes highlights,
clearHighlights,
[fn:1] https://orgmode.org/ }: EditorProps): React.ReactNode {
[fn:2] https://code.fizz.buzz/talexander/organic
`;
function Editor({ defaultValue = default_org_source }) {
function handleChange(event: React.ChangeEvent<HTMLTextAreaElement>) { function handleChange(event: React.ChangeEvent<HTMLTextAreaElement>) {
setValue(event.target.value); setValue(event.target.value);
clearHighlights(); clearHighlights();
} }
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;
});
setHighlights(new_highlights);
}
function addHighlight(start: number, end: number) {
let new_highlights = [...highlights, new Highlight(start, end)];
new_highlights.sort(function (a, b) {
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 textAreaRef = useRef<HTMLTextAreaElement>(null);
const shadowRef = useRef<HTMLDivElement>(null); const shadowRef = useRef<HTMLDivElement>(null);
function onTextAreaScroll() { function onTextAreaScroll() {
@ -78,26 +35,18 @@ function Editor({ defaultValue = default_org_source }) {
}, []); }, []);
return ( return (
<div className="Editor"> <div className={styles.EditorTextWrapper}>
<div className="Editor-textwrapper"> <textarea
<textarea ref={textAreaRef}
ref={textAreaRef} onChange={handleChange}
onChange={handleChange} className={styles.EditorTextArea}
className="Editor-textarea"
value={value}
onScroll={onTextAreaScroll}
/>
<div ref={shadowRef} className="Editor-underlay">
{buildShadow(highlights, value)}
<br />
</div>
</div>
<OrgAst
setHighlight={setHighlight}
clearHighlights={clearHighlights}
value={value} value={value}
astTree={astTree} onScroll={onTextAreaScroll}
/> />
<div ref={shadowRef} className={styles.EditorUnderlay}>
{buildShadow(highlights, value)}
<br />
</div>
</div> </div>
); );
} }

11
src/Explorer.module.css Normal file
View File

@ -0,0 +1,11 @@
.Explorer {
display: flex;
flex-direction: row;
height: 100%;
width: 100%;
> * {
flex: 1;
flex-basis: 0;
}
}

1
src/Explorer.module.css.d.ts vendored Normal file
View File

@ -0,0 +1 @@
export const Explorer: string;

83
src/Explorer.tsx Normal file
View File

@ -0,0 +1,83 @@
import React, { useMemo, useState } from "react";
import styles from "./Explorer.module.css";
import { Highlight } from "./highlight";
import OrgAst, { OrgNodeReference } from "./OrgAst";
import { parse_org } from "../../organic/target/wasm32-unknown-unknown/js/wasm";
import Editor from "./Editor";
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
`;
interface ExplorerProps {
defaultValue?: string;
}
function Explorer({ defaultValue = default_org_source }: ExplorerProps) {
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;
});
setHighlights(new_highlights);
}
function addHighlight(start: number, end: number) {
let new_highlights = [...highlights, new Highlight(start, end)];
new_highlights.sort(function (a, b) {
if (a.start < b.start) return -1;
if (a.start > b.start) return 1;
return 0;
});
setHighlights(new_highlights);
}
function clearHighlights() {
setHighlights([]);
}
return (
<div className={styles.Explorer}>
<Editor
value={value}
setValue={setValue}
highlights={highlights}
clearHighlights={clearHighlights}
/>
<OrgAst
setHighlight={setHighlight}
clearHighlights={clearHighlights}
value={value}
astTree={astTree}
/>
</div>
);
}
export default Explorer;

View File

@ -1,5 +1,4 @@
.OrgAst { .OrgAst {
flex: 1;
background: #eeeeee; background: #eeeeee;
padding: 5px; padding: 5px;
overflow: auto; overflow: auto;

View File

@ -1,5 +1,6 @@
import React, { ReactNode, useState } from "react"; import React, { ReactNode } from "react";
import { Highlight } from "./highlight"; import { Highlight } from "./highlight";
import styles from "./Editor.module.css";
function buildShadow(highlights: Highlight[], text: string): ReactNode[] { function buildShadow(highlights: Highlight[], text: string): ReactNode[] {
let output: ReactNode[] = []; let output: ReactNode[] = [];
@ -22,7 +23,7 @@ function buildShadow(highlights: Highlight[], text: string): ReactNode[] {
} else if (state == ShadowState.Highlight && !thisCharHighlighted) { } else if (state == ShadowState.Highlight && !thisCharHighlighted) {
// End the span // End the span
output.push( output.push(
<span key={i} className="highlighted"> <span key={i} className={styles.EditorHighlighted}>
{buffer} {buffer}
</span>, </span>,
); );
@ -37,7 +38,7 @@ function buildShadow(highlights: Highlight[], text: string): ReactNode[] {
output.push(buffer); output.push(buffer);
} else if (state == ShadowState.Highlight) { } else if (state == ShadowState.Highlight) {
output.push( output.push(
<span key={i} className="highlighted"> <span key={i} className={styles.EditorHighlighted}>
{buffer} {buffer}
</span>, </span>,
); );