Skip to content

Commit 4660503

Browse files
committed
Restructure / Refactor Playground UI
- Add HighlightJS dark styling - Change the way Codemirror is dynamically loaded - Add ~fixed option to Navigation - Use hljs output field instead of CodeMirror instance
1 parent 8d99c04 commit 4660503

File tree

10 files changed

+484
-340
lines changed

10 files changed

+484
-340
lines changed

common/HighlightJs.re

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,47 @@ type highlightResult = {value: string};
44
[@bs.module "highlight.js/lib/highlight"]
55
external highlight: (~lang: string, ~value: string) => highlightResult =
66
"highlight";
7+
8+
let renderHLJS =
9+
(
10+
~highlightedLines=[||],
11+
~darkmode=false,
12+
~code: string,
13+
~lang: string,
14+
(),
15+
) => {
16+
// If the language couldn't be parsed, we will fall back to text
17+
let (lang, highlighted) =
18+
try((lang, highlight(~lang, ~value=code)->valueGet)) {
19+
| Js.Exn.Error(_) => ("text", code)
20+
};
21+
22+
// Add line highlighting as well
23+
let highlighted =
24+
if (Belt.Array.length(highlightedLines) > 0) {
25+
Js.String2.split(highlighted, "\n")
26+
->Belt.Array.mapWithIndex((i, line) =>
27+
if (Js.Array2.find(highlightedLines, lnum => lnum === i + 1) !== None) {
28+
"<span class=\"hljs-line-highlight\">" ++ line ++ "</span>";
29+
} else {
30+
line;
31+
}
32+
)
33+
->Js.Array2.joinWith("\n");
34+
} else {
35+
highlighted;
36+
};
37+
38+
let dark = darkmode ? "dark" : "";
39+
ReactDOMRe.createElementVariadic(
40+
"code",
41+
~props=
42+
ReactDOMRe.objToDOMProps({
43+
"className": "wrap hljs lang-" ++ lang ++ " " ++ dark,
44+
"dangerouslySetInnerHTML": {
45+
"__html": highlighted,
46+
},
47+
}),
48+
[||],
49+
);
50+
};

components/CodeExample.re

Lines changed: 2 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,5 @@
11
open Util.ReactStuff;
22

3-
let renderHLJS = (~highlightedLines=[||], ~code: string, ~lang: string, ()) => {
4-
// If the language couldn't be parsed, we will fall back to text
5-
let (lang, highlighted) =
6-
try((lang, HighlightJs.(highlight(~lang, ~value=code)->valueGet))) {
7-
| Js.Exn.Error(_) => ("text", code)
8-
};
9-
10-
// Add line highlighting as well
11-
let highlighted =
12-
if (Belt.Array.length(highlightedLines) > 0) {
13-
Js.String2.split(highlighted, "\n")
14-
->Belt.Array.mapWithIndex((i, line) =>
15-
if (Js.Array2.find(highlightedLines, lnum => lnum === i + 1) !== None) {
16-
"<span class=\"hljs-line-highlight\">" ++ line ++ "</span>";
17-
} else {
18-
line;
19-
}
20-
)
21-
->Js.Array2.joinWith("\n");
22-
} else {
23-
highlighted;
24-
};
25-
26-
ReactDOMRe.createElementVariadic(
27-
"code",
28-
~props=
29-
ReactDOMRe.objToDOMProps({
30-
"className": "wrap hljs lang-" ++ lang,
31-
"dangerouslySetInnerHTML": {
32-
"__html": highlighted,
33-
},
34-
}),
35-
[||],
36-
);
37-
};
38-
393
let langShortname = (lang: string) => {
404
switch (lang) {
415
| "ocaml" => "ml"
@@ -49,7 +13,7 @@ let langShortname = (lang: string) => {
4913

5014
[@react.component]
5115
let make = (~highlightedLines=[||], ~code: string, ~showLabel=true, ~lang="text") => {
52-
let children = renderHLJS(~highlightedLines, ~code, ~lang, ());
16+
let children = HighlightJs.renderHLJS(~highlightedLines, ~code, ~lang, ());
5317

5418
let label = if(showLabel) {
5519
let label = langShortname(lang);
@@ -136,7 +100,7 @@ module Toggle = {
136100
Belt.Array.get(multiple, selected)
137101
->Belt.Option.map(tab => {
138102
let lang = Belt.Option.getWithDefault(tab.lang, "text");
139-
renderHLJS(
103+
HighlightJs.renderHLJS(
140104
~highlightedLines=?tab.highlightedLines,
141105
~code=tab.code,
142106
~lang,

components/CodeMirror.re

Lines changed: 0 additions & 67 deletions
This file was deleted.

common/CodeMirrorBase.re renamed to components/CodeMirror2.re

Lines changed: 45 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -176,30 +176,6 @@ module Error = {
176176
};
177177
};
178178

179-
module Props = {
180-
[@bs.deriving {abstract: light}]
181-
type t = {
182-
[@bs.optional]
183-
errors: array(Error.t),
184-
[@bs.optional]
185-
minHeight: string, // minHeight of the scroller element
186-
[@bs.optional]
187-
maxHeight: string, // maxHeight of the scroller element
188-
[@bs.optional]
189-
className: string,
190-
[@bs.optional]
191-
style: ReactDOMRe.Style.t,
192-
value: string,
193-
[@bs.optional]
194-
onChange: string => unit,
195-
[@bs.optional]
196-
onMarkerFocus: ((int, int)) => unit, // (row, column)
197-
[@bs.optional]
198-
onMarkerFocusLeave: ((int, int)) => unit, // (row, column)
199-
options: CM.Options.t,
200-
};
201-
};
202-
203179
module GutterMarker = {
204180
// Note: this is not a React component
205181
let make =
@@ -269,7 +245,7 @@ let updateErrors =
269245

270246
Belt.Array.forEachWithIndex(
271247
errors,
272-
(idx, e) => {
248+
(_idx, e) => {
273249
open DomUtil;
274250
open Error;
275251

@@ -359,42 +335,44 @@ let updateErrors =
359335
);
360336
};
361337

362-
let default = (props: Props.t): React.element => {
338+
[@react.component]
339+
let make =
340+
// props relevant for the react wrapper
341+
(
342+
~errors: array(Error.t)=[||],
343+
~minHeight: option(string)=?,
344+
~maxHeight: option(string)=?,
345+
~className: option(string)=?,
346+
~style: option(ReactDOMRe.Style.t)=?,
347+
~onChange: option(string => unit)=?,
348+
~onMarkerFocus: option(((int, int)) => unit)=?, // (row, column)
349+
~onMarkerFocusLeave: option(((int, int)) => unit)=?, // (row, column)
350+
~value: string,
351+
// props for codemirror options
352+
~mode,
353+
~readOnly=false,
354+
~lineNumbers=true,
355+
~scrollbarStyle="overlay",
356+
~lineWrapping=false,
357+
)
358+
: React.element => {
363359
let inputElement = React.useRef(Js.Nullable.null);
364360
let cmRef: React.Ref.t(option(CM.t)) = React.useRef(None);
365361
let cmStateRef = React.useRef({marked: [||]});
366362

367-
// Destruct all our props here
368-
let minHeight = props->Props.minHeight;
369-
let maxHeight = props->Props.maxHeight;
370-
let onChange = props->Props.onChange;
371-
let value = props->Props.value;
372-
let errors = Belt.Option.getWithDefault(props->Props.errors, [||]);
373-
let className = props->Props.className;
374-
let style = props->Props.style;
375-
let cmOptions = props->Props.options;
376-
377-
let onMarkerFocus = props->Props.onMarkerFocus;
378-
let onMarkerFocusLeave = props->Props.onMarkerFocusLeave;
379-
380363
React.useEffect0(() => {
381364
switch (inputElement->React.Ref.current->Js.Nullable.toOption) {
382365
| Some(input) =>
383366
let options =
384367
CM.Options.t(
385368
~theme="material",
386369
~gutters=[|CM.errorGutterId, "CodeMirror-linenumbers"|],
387-
~mode=cmOptions->CM.Options.mode,
388-
~lineWrapping=
389-
Belt.Option.getWithDefault(
390-
cmOptions->CM.Options.lineWrapping,
391-
false,
392-
),
370+
~mode,
371+
~lineWrapping,
393372
~fixedGutter=false,
394-
~readOnly=
395-
Belt.Option.getWithDefault(cmOptions->CM.Options.readOnly, false),
396-
~lineNumbers=true,
397-
~scrollbarStyle="overlay",
373+
~readOnly,
374+
~lineNumbers,
375+
~scrollbarStyle,
398376
(),
399377
);
400378
let cm = CM.fromTextArea(input, options);
@@ -419,7 +397,7 @@ let default = (props: Props.t): React.element => {
419397
React.Ref.setCurrent(cmRef, Some(cm));
420398

421399
let cleanup = () => {
422-
/*Js.log2("cleanup", options->CM.Options.mode);*/
400+
Js.log2("cleanup", options->CM.Options.mode);
423401

424402
// This will destroy the CM instance
425403
cm->CM.toTextArea;
@@ -458,7 +436,7 @@ let default = (props: Props.t): React.element => {
458436
~state,
459437
~cm,
460438
errors,
461-
);
439+
)
462440
});
463441
cm->CM.setValue(value);
464442
}
@@ -493,7 +471,7 @@ let default = (props: Props.t): React.element => {
493471
~state,
494472
~cm,
495473
errors,
496-
);
474+
)
497475
})
498476
| None => ()
499477
};
@@ -502,6 +480,21 @@ let default = (props: Props.t): React.element => {
502480
[|errorsFingerprint|],
503481
);
504482

483+
/*
484+
Needed in case the className visually hides / shows
485+
a codemirror instance.
486+
*/
487+
React.useEffect1(
488+
() => {
489+
switch (cmRef->React.Ref.current) {
490+
| Some(cm) => cm->CM.refresh
491+
| None => ()
492+
};
493+
None;
494+
},
495+
[|className|],
496+
);
497+
505498
<div ?className ?style>
506499
<textarea className="hidden" ref={ReactDOMRe.Ref.domRef(inputElement)} />
507500
</div>;

components/Markdown.re

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -293,7 +293,7 @@ module Code = {
293293
};
294294

295295
let makeCodeElement = (~code, ~metastring, ~lang) => {
296-
let baseClass = "md-code font-mono w-full block leading-tight mt-4 mb-10";
296+
let baseClass = "md-code font-mono w-full block mt-4 mb-10";
297297
let codeElement =
298298
switch (metastring) {
299299
| None => <CodeExample code lang />

components/Navigation.re

Lines changed: 7 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -393,7 +393,7 @@ module MobileNav = {
393393

394394
/* isOverlayOpen: if the mobile overlay is toggled open */
395395
[@react.component]
396-
let make = (~overlayState: (bool, (bool => bool) => unit)) => {
396+
let make = (~fixed=true, ~overlayState: (bool, (bool => bool) => unit)) => {
397397
let minWidth = "20rem";
398398
let router = Next.Router.useRouter();
399399

@@ -471,11 +471,16 @@ let make = (~overlayState: (bool, (bool => bool) => unit)) => {
471471
[||],
472472
);
473473

474+
let fixedNav = fixed ? "fixed z-20 top-0" : "";
475+
474476
<nav
475477
ref={ReactDOMRe.Ref.domRef(outerRef)}
476478
id="header"
477479
style={Style.make(~minWidth, ())}
478-
className="fixed flex xs:justify-center z-20 top-0 w-full h-18 bg-night-dark shadow text-white-80 text-base">
480+
className={
481+
fixedNav
482+
++ " flex xs:justify-center w-full h-18 bg-night-dark shadow text-white-80 text-base"
483+
}>
479484
<div
480485
className="flex justify-between mx-4 md:mx-8 items-center h-full w-full max-w-1280">
481486
<div className="h-8 w-8">
@@ -569,18 +574,6 @@ let make = (~overlayState: (bool, (bool => bool) => unit)) => {
569574
<div className="hidden sm:block ml-8"> <DocSearch /> </div>
570575
</div>
571576
</div>
572-
/*<a*/
573-
/*href="https://discord.gg/reasonml"*/
574-
/*rel="noopener noreferrer"*/
575-
/*target="_blank"*/
576-
/*className=link*/
577-
/*onMouseEnter=nonCollapsibleOnMouseEnter>*/
578-
/*<Icon.Discord className="w-5 h-5" />*/
579-
/*</a>*/
580-
/*<button*/
581-
/*className="hidden sm:flex sm:px-4 sm:items-center sm:justify-center sm:border-l sm:border-r sm:border-night sm:h-full">*/
582-
/*<Icon.MagnifierGlass className="w-5 h-5 hover:text-white" />*/
583-
/*</button>*/
584577
/* Burger Button */
585578
<button
586579
className="h-full px-4 xs:hidden flex items-center hover:text-white"

components/Navigation.rei

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
[@react.component]
2-
let make: (~overlayState: (bool, (bool => bool) => unit)) => React.element;
2+
let make: (~fixed: bool=?, ~overlayState: (bool, (bool => bool) => unit)) => React.element;

0 commit comments

Comments
 (0)