Compare commits

...

6 Commits

Author SHA1 Message Date
Tom Alexander
acaf757ce3
Merge branch 'isolate_editor' 2024-01-28 18:24:24 -05:00
Tom Alexander
1cc5dd7911
Move flexbox handling up to the explorer component. 2024-01-28 18:21:38 -05:00
Tom Alexander
083b0aa376
Move explorer styles into local styles. 2024-01-28 18:20:22 -05:00
Tom Alexander
d158fafd77
Move the highlightable textarea into its own component. 2024-01-28 18:14:57 -05:00
Tom Alexander
33ed9c4f56
Add types to Explorer props. 2024-01-28 17:32:27 -05:00
Tom Alexander
a11201363e
Rename Editor to Explorer.
This component was both the editor and the AST view so it doesn't make sense to call it Editor. Instead the highlightable textarea is going to be moved into a new Editor component.
2024-01-28 17:28:43 -05:00
9 changed files with 135 additions and 96 deletions

View File

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

View File

@ -1,13 +1,4 @@
.Editor {
display: flex;
flex-direction: row;
height: 100%;
width: 100%;
}
.Editor-textwrapper {
flex: 1;
flex-basis: 0;
.EditorTextWrapper {
position: relative;
background: white;
font-family: ui-monospace, "Cascadia Code", "Source Code Pro", Menlo, Consolas,
@ -17,8 +8,8 @@
text-align: initial;
}
.Editor-textarea,
.Editor-underlay {
.EditorTextArea,
.EditorUnderlay {
font-family: inherit;
font-weight: inherit;
font-size: inherit;
@ -28,7 +19,7 @@
line-height: normal;
}
.Editor-textarea {
.EditorTextArea {
position: absolute;
top: 0;
bottom: 0;
@ -43,7 +34,7 @@
outline: none;
}
.Editor-underlay {
.EditorUnderlay {
width: 100%;
height: 100%;
pointer-events: none;
@ -52,7 +43,7 @@
word-wrap: break-word;
overflow-y: scroll;
.highlighted {
.EditorHighlighted {
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 "./Editor.css";
import React, { useEffect, useRef } from "react";
import { Highlight } from "./highlight";
import { buildShadow } from "./shadow";
import OrgAst, { OrgNodeReference } from "./OrgAst";
import { parse_org } from "../../organic/target/wasm32-unknown-unknown/js/wasm";
import styles from "./Editor.module.css";
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
`;
function Editor({ defaultValue = default_org_source }) {
interface EditorProps {
value: string;
setValue: Function;
highlights: Highlight[];
clearHighlights: Function;
}
function Editor({
value,
setValue,
highlights,
clearHighlights,
}: EditorProps): React.ReactNode {
function handleChange(event: React.ChangeEvent<HTMLTextAreaElement>) {
setValue(event.target.value);
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 shadowRef = useRef<HTMLDivElement>(null);
function onTextAreaScroll() {
@ -78,26 +35,18 @@ function Editor({ defaultValue = default_org_source }) {
}, []);
return (
<div className="Editor">
<div className="Editor-textwrapper">
<textarea
ref={textAreaRef}
onChange={handleChange}
className="Editor-textarea"
value={value}
onScroll={onTextAreaScroll}
/>
<div ref={shadowRef} className="Editor-underlay">
{buildShadow(highlights, value)}
<br />
</div>
</div>
<OrgAst
setHighlight={setHighlight}
clearHighlights={clearHighlights}
<div className={styles.EditorTextWrapper}>
<textarea
ref={textAreaRef}
onChange={handleChange}
className={styles.EditorTextArea}
value={value}
astTree={astTree}
onScroll={onTextAreaScroll}
/>
<div ref={shadowRef} className={styles.EditorUnderlay}>
{buildShadow(highlights, value)}
<br />
</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 {
flex: 1;
background: #eeeeee;
padding: 5px;
overflow: auto;

View File

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