docs/src/components/Interpreter/index.tsx

Summary

Maintainability
C
1 day
Test Coverage
import { useLayoutEffect, useRef, useState, MouseEvent, CSSProperties } from 'react';
import Markdown from 'react-markdown';
import clsx from 'clsx';

import useIsBrowser from '@docusaurus/useIsBrowser';
import Head from '@docusaurus/Head';
import useScripts from '@site/src/hooks/useScripts';
import CodeBlock from '@theme/CodeBlock';
import { initTerminal, destroyTerminal } from './terminal';
import examples from './examples';

import './styles.css';

export interface TerminalProps extends CSSProperties {
  '--size': string;
}

const replReady = () => {
    return (
        globalThis.jQuery &&
        globalThis.jQuery.terminal &&
        globalThis.terminal &&
        globalThis.lips
    );
}

export default function Interpreter(): JSX.Element {
  const [activeSnippet, setActiveSnippet] = useState(0);
  const [size, setSize] = useState(1);
  const ref = useRef<HTMLDivElement>();

  const isProd = process.env.NODE_ENV === 'production';
  const isBrowser = useIsBrowser();
  const isStatic = isProd && !isBrowser && !globalThis.jQuery;

  useScripts(!globalThis.jQuery && [
    'https://cdn.jsdelivr.net/npm/jquery',
    'https://cdn.jsdelivr.net/combine/npm/jquery.terminal/js/jquery.terminal.min.js,npm/js-polyfills/keyboard.js,npm/prismjs/prism.js,npm/jquery.terminal/js/prism.js,npm/prismjs/components/prism-scheme.min.js',
    'https://cdn.jsdelivr.net/gh/jcubic/lips@devel/lib/js/terminal.js',
    'https://cdn.jsdelivr.net/gh/jcubic/lips@devel/lib/js/prism.js'
  ]);

  useLayoutEffect(() => {
    (function loop() {
      if (replReady() && styleReady()) {
        initTerminal();
      } else {
        setTimeout(loop, 100);
      }
    })();
    return destroyTerminal;
  }, []);

  function execSnippet(selector = '.example:visible') {
    const $ = globalThis.jQuery;
    const code = $(selector).text();
    const term = $('.term').terminal();
    term.echo(term.get_prompt(), { formatters: false });
    term.exec(code, true);
    if (typeof screen.orientation === 'undefined') {
      setTimeout(() => term.focus(), 0);
    }
  }

  function makeChangeSnippet(index: number) {
    return (event: MouseEvent<HTMLAnchorElement>) => {
      event.preventDefault();
      setActiveSnippet(index);
    };
  }

  function sizeInrement(increment: number) {
    return (event: MouseEvent<HTMLAnchorElement>) => {
      event.preventDefault();
      setSize(size => size + increment);
    };
  }

  function fullScreen(event: MouseEvent<HTMLAnchorElement>) {
    event.preventDefault();
    const $ = globalThis.jQuery;
    $(document.body).addClass('full-screen');
  }

  function exitFullScreen(event: MouseEvent<HTMLAnchorElement>) {
    event.preventDefault();
    const $ = globalThis.jQuery;
    $(document.body).removeClass('full-screen');
  }

  function styleReady() {
    // hack to prevent initalizaing of jQuery Terminal before style is loaded
    return !!getComputedStyle(ref.current).getPropertyValue('--base-background');
  }

  const terminalStyle = {
    '--size': size.toFixed(1)
  } as TerminalProps;

  return (
    <>
      <Head>
        <link rel="preconnect" href="https://cdn.jsdelivr.net" />
        <link href="https://cdn.jsdelivr.net/combine/npm/jquery.terminal/css/jquery.terminal.min.css,npm/terminal-prism@0.4.1/css/prism-coy.css" rel="stylesheet"/>
        <link href="https://cdn.jsdelivr.net/gh/jcubic/lips@devel/lib/css/terminal.css"
              rel="stylesheet"/>
        {isStatic && <script src="https://cdn.jsdelivr.net/npm/jquery" />}
        {isStatic && <script src="https://cdn.jsdelivr.net/combine/npm/jquery.terminal/js/jquery.terminal.min.js,npm/js-polyfills/keyboard.js,npm/prismjs/prism.js,npm/jquery.terminal/js/prism.js,npm/prismjs/components/prism-scheme.min.js" />}
        {isStatic && <script src="https://cdn.jsdelivr.net/gh/jcubic/lips@devel/lib/js/terminal.js" />}
        {isStatic && <script src="https://cdn.jsdelivr.net/gh/jcubic/lips@devel/lib/js/prism.js" />}
        {!globalThis.lips && <script src="https://cdn.jsdelivr.net/gh/jcubic/lips@devel/dist/lips.min.js"
                                     data-bootstrap="https://cdn.jsdelivr.net/gh/jcubic/lips@devel/dist/std.xcb"/>}
      </Head>
      <div className="intro">
        <div className="actions-wrapper">
          <ul className="actions">
            <li className="zoom-in icon">
              <a href="#" onClick={sizeInrement(0.1)} title="Zoom In">Zoom In</a>
            </li>
            <li className="zoom-out icon">
              <a href="#" onClick={sizeInrement(-0.1)} title="Zoom Out">Zoom Out</a>
            </li>
            <li className="full-screen">
              <ul>
                <li className="full-screen icon">
                  <a href="#" onClick={fullScreen} title="Full Screen">Full Screen</a>
                </li>
                <li className="exit-full-screen icon">
                  <a href="#" onClick={exitFullScreen} title="Exit Full Screen">Exit Full Screen</a>
                </li>
              </ul>
            </li>
          </ul>
        </div>
        <div className="terminal marker" ref={ref}></div>
        <div className="term" style={terminalStyle}>
          <div className="loader-container">
            <div className="loader">
              <div>.</div>
              <div>..</div>
              <div>...</div>
              <div>....</div>
              <div>.....</div>
              <div>......</div>
            </div>
          </div>
        </div>
        <div className="examples terminal-external">
          <div className="egg">
            <button onClick={() => execSnippet('li.hidden .example')}>π</button>
          </div>
          <button className="run" onClick={() => execSnippet()}>run</button>
          <ul className="list">
            {examples.map((example, index) => {
              return (
                <li key={index} className={clsx({active: index === activeSnippet, hidden: example.hidden})}>
                  <div className="example">
                    <CodeBlock language="scheme" className="lips">
                      {example.code}
                    </CodeBlock>
                  </div>
                  <div className="description"><Markdown>{example.description}</Markdown></div>
                </li>
              );
            })}
          </ul>
          <ul className="pagination">
            {examples.map((example, index) => {
              return (
                <li key={index} className={clsx({active: index === activeSnippet, hidden: example.hidden})}>
                  <a href="#" onClick={makeChangeSnippet(index)}>{ index + 1 }</a>
                </li>
              );
            })}
          </ul>
        </div>
      </div>
    </>
  );
};