143 lines
5.5 KiB
TypeScript
143 lines
5.5 KiB
TypeScript
import * as React from "react";
|
|
import { useState } from "react";
|
|
import * as ReactDOM from "react-dom"
|
|
|
|
const formatter = new Intl.NumberFormat(undefined, { maximumFractionDigits: 2 });
|
|
|
|
type HighlightFormProps = {
|
|
onSubmit(code: string, language: string);
|
|
}
|
|
|
|
const HighlightForm = ({ onSubmit }: HighlightFormProps) => {
|
|
const [code, setCode] = useState<string>("");
|
|
const [language, setLanguage] = useState<string>("");
|
|
|
|
const handleReset = () => {
|
|
setCode("");
|
|
setLanguage("");
|
|
};
|
|
|
|
const handleSubmit = () => onSubmit && onSubmit(code, language);
|
|
|
|
return (
|
|
<form className="demo__form">
|
|
<div className="form-group">
|
|
<label htmlFor="form_source">
|
|
<span className="far fa-code" aria-hidden="true"/> try it
|
|
</label>
|
|
<textarea name="source" className="form-control" id="form_source" rows={ 15 }
|
|
onChange={ ev => setCode((ev.target as HTMLTextAreaElement).value) }
|
|
value={ code }
|
|
/>
|
|
</div>
|
|
<div className="form-group row">
|
|
<label htmlFor="form_language" className="col-md-2 col-form-label">
|
|
<span className="far fa-lightbulb" aria-hidden="true"/> language
|
|
</label>
|
|
<div className="col-md-10">
|
|
<input type="text" name="language" id="form_language" className="form-control" required
|
|
value={ language }
|
|
onChange={ ev => setLanguage((ev.target as HTMLInputElement).value) }
|
|
/>
|
|
</div>
|
|
</div>
|
|
<div className="form-group">
|
|
<div id="language-selector" className="standalone languages">
|
|
</div>
|
|
</div>
|
|
<div className="demo__actions">
|
|
<button type="button" className="btn btn-sm btn-outline-danger" onClick={ handleReset }>
|
|
<span className="far fa-trash fa-fw" aria-hidden="true"/> reset
|
|
</button>
|
|
<button type="button" className="btn btn-sm btn-outline-primary" onClick={ handleSubmit }>
|
|
<span className="far fa-code fa-fw" aria-hidden="true"/> highlight
|
|
</button>
|
|
</div>
|
|
</form>
|
|
)
|
|
};
|
|
|
|
const Loading = () => <div className="loading">
|
|
<span className="fad fa-spinner-third fa-spin fa-10x" />
|
|
</div>;
|
|
|
|
type HighlightedCodeProps = {
|
|
highlighted: HighlightedResponse;
|
|
onClose?(): void;
|
|
}
|
|
|
|
const HighlightedCode = ({ highlighted, onClose }: HighlightedCodeProps) =>
|
|
<>
|
|
<pre className="keylighter" dangerouslySetInnerHTML={{ __html: highlighted.html }}/>
|
|
|
|
<div className="d-flex align-items-center">
|
|
<div className="demo__times">
|
|
<div className="demo__time-splits">
|
|
<div className="demo__time-split demo__time-split--tokenization" style={{ width: `${highlighted.times.tokenization / highlighted.times.total * 100}%` }}/>
|
|
<div className="demo__time-split demo__time-split--processing" style={{ width: `${highlighted.times.processing / highlighted.times.total * 100}%` }}/>
|
|
<div className="demo__time-split demo__time-split--formatting" style={{ width: `${highlighted.times.formatting / highlighted.times.total * 100}%` }}/>
|
|
</div>
|
|
<div className="demo__time-total">
|
|
Total time: { formatter.format(highlighted.times.total) }ms
|
|
</div>
|
|
</div>
|
|
<div className="demo__actions ml-auto">
|
|
<button type="button" className="btn btn-sm btn-outline-primary" onClick={ () => onClose && onClose() }>
|
|
<span className="far fa-sync fa-fw mr-1" aria-hidden="true"/>
|
|
try another
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</>;
|
|
|
|
type DemoState = "form" | "submitting" | "show";
|
|
type HighlightedResponse = {
|
|
html: string;
|
|
times: {
|
|
total: number;
|
|
tokenization: number;
|
|
processing: number;
|
|
formatting: number;
|
|
}
|
|
}
|
|
|
|
const KeylighterDemo = props => {
|
|
const [state, setState] = useState<DemoState>("form");
|
|
const [highlighted, setHighlighted] = useState<HighlightedResponse>(null);
|
|
|
|
const handleCodeHighlight = async (source, language) => {
|
|
setState("submitting");
|
|
setHighlighted(await fetch('/highlight', {
|
|
credentials: "include",
|
|
method: "POST",
|
|
body: JSON.stringify({ source, language }),
|
|
headers: {
|
|
'Content-Type': 'application/json'
|
|
}
|
|
}).then(res => res.json()));
|
|
setState("show");
|
|
};
|
|
|
|
return (
|
|
<div id="try-form" className="demo">
|
|
<div className="container">
|
|
{ state === "form" && <HighlightForm onSubmit={ handleCodeHighlight }/> }
|
|
{ state === "submitting" && <Loading /> }
|
|
{ state === "show" && <HighlightedCode highlighted={ highlighted } onClose={ () => setState("form") } /> }
|
|
</div>
|
|
</div>
|
|
)
|
|
};
|
|
|
|
export function replace<T extends Element>(component: any, element: HTMLElement, callback?: () => void) {
|
|
const parent = document.createElement('div');
|
|
|
|
ReactDOM.render(component, parent, () => {
|
|
element.replaceWith(...Array.from(parent.childNodes));
|
|
callback && callback();
|
|
});
|
|
}
|
|
|
|
const root = document.getElementById('try-form');
|
|
replace(<KeylighterDemo />, root);
|