Change build system to Rescript

This commit is contained in:
Sam Nolan 2022-01-12 18:33:04 +11:00
parent 4db127086e
commit 01990dbe9f
105 changed files with 23759 additions and 42 deletions

View File

@ -45,6 +45,9 @@
"reschema" "reschema"
], ],
"refmt": 3, "refmt": 3,
"warnings": {
"number": "+A-42-48-9-30-4-102"
},
"ppx-flags": [ "ppx-flags": [
"lenses-ppx/ppx" "lenses-ppx/ppx"
] ]

16
foretold/components/.gitignore vendored Normal file
View File

@ -0,0 +1,16 @@
.DS_Store
.merlin
.bsb.lock
npm-debug.log
/node_modules/
.cache
.cache/*
dist
lib/*
*.cache
build
yarn-error.log
*.bs.js
# Local Netlify folder
.netlify
.idea

View File

@ -0,0 +1,15 @@
{
"spellright.language": [
"en"
],
"spellright.documentTypes": [
"markdown",
"latex",
"plaintext"
],
"spellright.parserByClass": {
"reason": {
"parser": "plain"
}
}
}

View File

@ -0,0 +1,29 @@
# Components
This repo has many of the relevant Foretold Components.
There's an example page in the `example/App.re` file. To run this, do:
```bash
yarn dev
```
in a separate terminal
```bash
yarn dev
```
### Showcase component viewer
To open the component viewer, run:
```bash
yarn showcase
```
Showcase should then be available on `http://localhost:1234/showcase/index.html`,
while the example page is available on `http://localhost:1234/index.html`
(given no port conflict).
Components included are specified through `showcase/Entries.re`.

View File

@ -0,0 +1,37 @@
{
"name": "@foretold/components",
"reason": {
"react-jsx": 3
},
"refmt": 3,
"bs-dependencies": [
"bs-css",
"bs-moment",
"reason-react",
"rationale"
],
"warnings": {
"error": "+5"
},
"suffix": ".bs.js",
"package-specs": [{
"module": "commonjs",
"in-source": true
}],
"namespace": true,
"sources": [{
"dir": "src",
"subdirs": true
},
{
"dir": "example",
"type": "dev",
"subdirs": true
},
{
"dir": "showcase",
"type": "dev",
"subdirs": true
}
]
}

View File

@ -0,0 +1,7 @@
FC.Base.Globals.load();
ReactDOMRe.renderToElementWithId(
<div className=Css.(style([fontFamily(Settings.Text.standardFont)]))>
ExampleFullPage.make
</div>,
"app",
);

View File

@ -0,0 +1,124 @@
open Base;
open FC;
let make =
<Div>
<AppHeader
links={
[|
<Link
isDisabled=false
className=Css.(
style([
marginRight(`em(2.)),
color(Settings.Text.LightBackground.main),
hover([color(Settings.Text.LightBackground.main)]),
])
)
href="#">
{"Public Groups" |> ReasonReact.string}
</Link>,
<Link
isDisabled=false
className=Css.(
style([
marginRight(`em(2.)),
color(Settings.Text.LightBackground.main),
hover([color(Settings.Text.LightBackground.main)]),
])
)
href="#">
{"Entity Explorer" |> ReasonReact.string}
</Link>,
|]
|> ReasonReact.array
}
/>
Example__AppGroupHeader.make
<Div
styles=[
Css.(
style(
[
marginTop(`em(1.)),
width(`percent(100.)),
paddingLeft(`em(2.)),
paddingRight(`em(2.)),
]
@ BaseStyles.fullWidthFloatLeft,
)
),
]>
Example__MeasurableIndexPage.make
</Div>
<Div
styles=[
Css.(
style(
[
marginTop(`em(1.)),
width(`percent(100.)),
paddingLeft(`em(2.)),
paddingRight(`em(2.)),
boxSizing(`borderBox),
]
@ BaseStyles.fullWidthFloatLeft,
)
),
]>
<Div flexDirection=`row>
<Div
flex={`num(5.0)} styles=[Css.(style([paddingRight(`em(2.0))]))]>
<Div> Example__MeasurableTopCard.make </Div>
<Div
styles=[
Css.(
style(
[marginTop(`em(1.0))] @ BaseStyles.fullWidthFloatLeft,
)
),
]>
Example__CardMeasurableMeasurements.make
</Div>
</Div>
<Div flex={`num(2.0)}>
Example__MeasurableTopCard.make
<Div styles=[Css.(style([clear(`both), paddingTop(`em(1.0))]))]>
<MeasurableForm cdf=ExampleCdfs.Example1.cdf />
</Div>
</Div>
</Div>
</Div>
<Div
styles=[
Css.(
style(
[
marginTop(`em(2.)),
width(`percent(100.)),
paddingLeft(`em(2.)),
paddingRight(`em(2.)),
boxSizing(`borderBox),
]
@ BaseStyles.fullWidthFloatLeft,
)
),
]>
<Div flexDirection=`row>
<Div
flex={`num(5.0)} styles=[Css.(style([paddingRight(`em(2.0))]))]>
<Div> Example__MemberTableCard.make </Div>
</Div>
<Div flex={`num(2.0)} />
</Div>
</Div>
<Footer
logo={React.string({js|2019 \u00a9 Foretold|js})}
links=[|
<a href="#"> {React.string("About")} </a>,
<a href="#"> {React.string("Help")} </a>,
<a href="#"> {React.string("Documentation")} </a>,
<a href="#"> {React.string("Privacy Policy")} </a>,
<a href="#"> {React.string("Terms of Service")} </a>,
|]
/>
</Div>;

View File

@ -0,0 +1,59 @@
open FC;
open Base;
let make =
<Div>
<GroupHeader>
<Div float=`left>
<div
className=Css.(
style([
fontSize(`em(1.4)),
marginTop(`px(5)),
Colors.FontWeights.heavy,
color(Colors.darkLink),
])
)>
{"Great Community" |> ReasonReact.string}
</div>
<p
className=Css.(
style([
color(`hex("36485c")),
marginTop(`px(5)),
marginBottom(`px(3)),
])
)>
{"Lots of description for the group would be here"
|> ReasonReact.string}
</p>
</Div>
<Div float=`right>
<Button
variant=Button.Secondary
isDisabled=false
size=Button.(Medium)
className=GroupHeader.Styles.actionButtonPosition>
{"Leave Channel" |> ReasonReact.string}
</Button>
<Button
isDisabled=false
size=Button.(Medium)
className=GroupHeader.Styles.actionButtonPosition>
{"Create Question" |> ReasonReact.string}
</Button>
<Button
isDisabled=false
size=Button.(Medium)
className=GroupHeader.Styles.actionButtonPosition>
{"Create Question" |> ReasonReact.string}
</Button>
</Div>
</GroupHeader>
<GroupHeader.SubHeader>
<Tab isActive=true> {"Questions" |> ReasonReact.string} </Tab>
<Tab isActive=false> {"Knowledge Graph" |> ReasonReact.string} </Tab>
<Tab isActive=false> {"Leaderboard" |> ReasonReact.string} </Tab>
<Tab isActive=false> {"Settings" |> ReasonReact.string} </Tab>
</GroupHeader.SubHeader>
</Div>;

View File

@ -0,0 +1,143 @@
open FC;
open Base;
let pastTime = 1483232400;
let format_standard = "LLL";
let cdf = ExampleCdfs.Example1.cdf;
let cellStyle =
Css.(style([paddingTop(`em(0.7)), paddingBottom(`em(0.4))]));
let row =
<Table.Row
bottomSubRow={Table.Row.textSection(
"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut vulputate tortor a sapien aliquet ullamcorper. Nunc non varius sapien, quis elementum sapien. Morbi ac tristique quam. Cras hendrerit accumsan pretium. Praesent id nisl sit amet eros imperdiet placerat. Vestibulum sodales posuere diam vel laoreet."
|> ReasonReact.string,
)}>
<Table.Cell flex={`num(1.0)} className=cellStyle>
<AgentLink
agent={AgentLink.Agent.makeUser(
~name="Roger Adams",
~image=
"https://lh3.googleusercontent.com/-1sj3EqkojJ4/AAAAAAAAAAI/AAAAAAAAAAA/ACHi3rfWCVqnuJxxM41Zird4HZx0BbRpbQ/photo.jpg",
(),
)}
/>
</Table.Cell>
<Table.Cell
flex={`num(2.0)}
className=Css.(
style([paddingTop(`em(0.6)), paddingBottom(`em(0.0))])
)>
<CdfChart__Plain cdf minX=2.0 color={`hex("#d9dcdf")} maxX=12.0 />
</Table.Cell>
<Table.Cell
flex={`num(1.0)}
className=Css.(
style([paddingTop(`em(0.2)), paddingBottom(`em(0.3))])
)>
<CdfChart__StatSummary cdf />
</Table.Cell>
<Table.Cell flex={`num(1.0)} className=cellStyle>
<span className=Css.(style([color(Settings.textMedium)]))>
{MomentRe.momentWithUnix(pastTime)
|> MomentRe.Moment.format(format_standard)
|> ReasonReact.string}
</span>
</Table.Cell>
</Table.Row>;
let row2 =
<Table.Row>
<Table.Cell flex={`num(1.0)} className=cellStyle>
<AgentLink
agent={AgentLink.Agent.makeUser(
~name="Samantha Hope",
~image=
"https://lh3.googleusercontent.com/-1sj3EqkojJ4/AAAAAAAAAAI/AAAAAAAAAAA/ACHi3rfWCVqnuJxxM41Zird4HZx0BbRpbQ/photo.jpg",
(),
)}
/>
</Table.Cell>
<Table.Cell
flex={`num(2.0)}
className=Css.(
style([paddingTop(`em(0.6)), paddingBottom(`em(0.0))])
)>
<CdfChart__Plain cdf minX=2.0 color={`hex("#d9dcdf")} maxX=12.0 />
</Table.Cell>
<Table.Cell
flex={`num(1.0)}
className=Css.(
style([paddingTop(`em(0.2)), paddingBottom(`em(0.3))])
)>
<CdfChart__StatSummary cdf />
</Table.Cell>
<Table.Cell flex={`num(1.0)} className=cellStyle>
<span className=Css.(style([color(Settings.textMedium)]))>
{MomentRe.momentWithUnix(pastTime)
|> MomentRe.Moment.format(format_standard)
|> ReasonReact.string}
</span>
</Table.Cell>
</Table.Row>;
let make =
<PageCard>
<PageCard.HeaderRow>
<Div>
<Div
styles=[
Css.style([BaseStyles.floatLeft, Css.paddingTop(`em(0.2))]),
]>
<Tab isActive=true> {"Predictions" |> ReasonReact.string} </Tab>
<Tab isActive=false> {"Settings" |> ReasonReact.string} </Tab>
</Div>
</Div>
<Div>
<Div
float=`right
styles=[Css.style([PageCard.HeaderRow.Styles.itemTopPadding])]>
{PaginationButtons.make({
currentValue: Range(3, 10),
max: 100,
pageLeft: {
isDisabled: false,
onClick: _ => (),
},
pageRight: {
isDisabled: true,
onClick: _ => (),
},
})}
</Div>
</Div>
</PageCard.HeaderRow>
<Div styles=[Css.style(BaseStyles.fullWidthFloatLeft)]>
<Table>
<Table.HeaderRow>
<Table.Cell flex={`num(2.0)}>
{"Prediction Distribution" |> ReasonReact.string}
</Table.Cell>
<Table.Cell flex={`num(1.0)}>
{"Prediction Value" |> ReasonReact.string}
</Table.Cell>
<Table.Cell flex={`num(1.0)}>
{"Agent" |> ReasonReact.string}
</Table.Cell>
<Table.Cell flex={`num(1.0)}>
{"Time" |> ReasonReact.string}
</Table.Cell>
</Table.HeaderRow>
row
row2
row2
row2
row
row
row2
row2
</Table>
</Div>
</PageCard>;

View File

@ -0,0 +1,153 @@
open FC;
open Base;
let cdf = ExampleCdfs.Example1.cdf;
let futureTime = 1559005200;
let row =
<Table.Row onClick={_ => Js.log("Row Clicked")}>
<Table.Cell flex={`num(4.)}>
<span className=Table.Styles.Elements.primaryText>
{"What will be the " |> ReasonReact.string}
<Link
isDisabled=false
className=Css.(
style([
textDecoration(`underline),
color(`hex("384e67")),
hover([color(Colors.link)]),
])
)>
{"GDP" |> ReasonReact.string}
</Link>
<Link
href="d"
isDisabled=false
className=Css.(
style([
textDecoration(`underline),
color(`hex("384e67")),
hover([color(Colors.link)]),
])
)>
{"GDP" |> ReasonReact.string}
</Link>
{" of " |> ReasonReact.string}
<Link
href="China"
isDisabled=false
className=Css.(
style([
textDecoration(`underline),
color(`hex("384e67")),
hover([color(Colors.link)]),
])
)>
{"China" |> ReasonReact.string}
</Link>
{" in " |> ReasonReact.string}
<Link
href="2018"
isDisabled=false
className=Css.(
style([
textDecoration(`underline),
color(`hex("384e67")),
hover([color(Colors.link)]),
])
)>
{"2018" |> ReasonReact.string}
</Link>
</span>
{StateStatus.make(
~state=OPEN(MomentRe.momentWithUnix(futureTime)),
~fontSize=`em(0.85),
(),
)}
</Table.Cell>
<Table.Cell flex={`num(2.)}>
<CdfChart__Small
cdf
minX={Some(2.0)}
color={`hex("#d9dcdf")}
maxX={Some(12.0)}
/>
</Table.Cell>
<Table.Cell flex={`num(1.)} properties=Css.[paddingTop(`em(0.3))]>
<Div>
<Link className={Table.Styles.Elements.link(~isUnderlined=false, ())}>
{"Series A" |> ReasonReact.string}
</Link>
<Link className={Table.Styles.Elements.link(~isUnderlined=false, ())}>
{"19" |> ReasonReact.string}
</Link>
</Div>
<Div>
<Link className={Table.Styles.Elements.link(~isUnderlined=true, ())}>
{"Edit" |> ReasonReact.string}
</Link>
<Link className={Table.Styles.Elements.link(~isUnderlined=true, ())}>
{"Archive" |> ReasonReact.string}
</Link>
</Div>
</Table.Cell>
</Table.Row>;
let make =
<PageCard>
<PageCard.HeaderRow>
<Div
float=`left
className={Css.style([
PageCard.HeaderRow.Styles.itemTopPadding,
PageCard.HeaderRow.Styles.itemBottomPadding,
])}>
<Tab2 isActive=true number=12> {"Open" |> ReasonReact.string} </Tab2>
<Tab2 isActive=false number=18>
{"Pending Resolution" |> ReasonReact.string}
</Tab2>
<Tab2 isActive=false number=831>
{"Closed" |> ReasonReact.string}
</Tab2>
</Div>
<Div
float=`right
styles=[Css.style([PageCard.HeaderRow.Styles.itemTopPadding])]>
{PaginationButtons.make({
currentValue: Range(3, 10),
max: 100,
pageLeft: {
isDisabled: false,
onClick: _ => (),
},
pageRight: {
isDisabled: true,
onClick: _ => (),
},
})}
</Div>
</PageCard.HeaderRow>
<Table>
<Table.HeaderRow>
<Table.Cell flex={`num(4.)}>
{"Name & Status" |> ReasonReact.string}
</Table.Cell>
<Table.Cell flex={`num(2.)}>
{"Aggregate Prediction" |> ReasonReact.string}
</Table.Cell>
<Table.Cell flex={`num(1.)}>
{"Details" |> ReasonReact.string}
</Table.Cell>
</Table.HeaderRow>
row
row
row
row
row
row
row
row
row
</Table>
</PageCard>;

View File

@ -0,0 +1,80 @@
open FC;
open Base;
let pastTime = 1483232400;
let futureTime = 1559005200;
let make =
<PageCard>
<PageCard.HeaderRow>
<Div>
<Div
styles=[
Css.style([
BaseStyles.floatLeft,
PageCard.HeaderRow.Styles.itemTopPadding,
PageCard.HeaderRow.Styles.itemBottomPadding,
]),
]>
<Button size=Button.Small>
{"< Back" |> ReasonReact.string}
</Button>
</Div>
<Div
float=`right
styles=[
Css.style([
PageCard.HeaderRow.Styles.itemTopPadding,
PageCard.HeaderRow.Styles.itemBottomPadding,
]),
]>
{PaginationButtons.make({
currentValue: Range(3, 10),
max: 100,
pageLeft: {
isDisabled: false,
onClick: _ => (),
},
pageRight: {
isDisabled: true,
onClick: _ => (),
},
})}
</Div>
</Div>
</PageCard.HeaderRow>
<Div
styles=[
Css.style(
[Css.padding2(~v=`em(1.5), ~h=`em(1.5))]
@ BaseStyles.fullWidthFloatLeft,
),
]>
<Div flexDirection=`row>
<Div flex={`num(5.)}>
<PageCard.H1>
{"What would the US GDP Be in 2020?" |> ReasonReact.string}
</PageCard.H1>
{StateStatus.make(
~state=RESOLVED(MomentRe.momentWithUnix(pastTime)),
~fontSize=`em(1.0),
(),
)}
</Div>
<AgentLink
agent={AgentLink.Agent.makeUser(
~name="Roger Adams",
~image=
"https://lh3.googleusercontent.com/-1sj3EqkojJ4/AAAAAAAAAAI/AAAAAAAAAAA/ACHi3rfWCVqnuJxxM41Zird4HZx0BbRpbQ/photo.jpg",
(),
)}
/>
</Div>
<Div styles=[Css.style([Css.marginTop(`em(2.0))])]>
<PageCard.P>
{"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut vulputate tortor a sapien aliquet ullamcorper. Nunc non varius sapien, quis elementum sapien. Morbi ac tristique quam. Cras hendrerit accumsan pretium. Praesent id nisl sit amet eros imperdiet placerat. Vestibulum sodales posuere diam vel laoreet."
|> ReasonReact.string}
</PageCard.P>
</Div>
</Div>
</PageCard>;

View File

@ -0,0 +1,36 @@
open FC;
open Base;
let make =
<PageCard>
<PageCard.HeaderRow>
<Div float=`left>
<PageCard.HeaderRow.Title>
{"Pending Resolution" |> ReasonReact.string}
</PageCard.HeaderRow.Title>
</Div>
<Div
float=`right
className={Css.style([
PageCard.HeaderRow.Styles.itemTopPadding,
PageCard.HeaderRow.Styles.itemBottomPadding,
])}>
<Button variant=Button.Primary size=Button.Small>
{"< Back" |> ReasonReact.string}
</Button>
</Div>
</PageCard.HeaderRow>
<Table>
<Table.HeaderRow>
<Table.Cell flex={`num(4.)}>
{"Name & Status" |> ReasonReact.string}
</Table.Cell>
<Table.Cell flex={`num(2.)}>
{"Aggregate Prediction" |> ReasonReact.string}
</Table.Cell>
<Table.Cell flex={`num(1.)}>
{"Details" |> ReasonReact.string}
</Table.Cell>
</Table.HeaderRow>
</Table>
</PageCard>;

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,17 @@
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<link href="https://fonts.googleapis.com/css?family=Lato:300,400,700,900" rel="stylesheet">
<title>Example</title>
</head>
<body>
<div id="app"></div>
<script src=" ./App.bs.js "></script>
</body>
</html>

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

View File

@ -0,0 +1,48 @@
{
"name": "@foretold/components",
"version": "0.0.6",
"description": "Kit-demo of UX components",
"private": false,
"scripts": {
"build": "bsb -make-world",
"clean": "bsb -clean-world",
"predev": "yarn build",
"dev": "PORT=12346 parcel example/index.html",
"showcase": "PORT=12345 parcel example/index.html showcase/index.html",
"start": "bsb -make-world -w"
},
"keywords": [
"foretold",
"bucklescript",
"reason",
"css"
],
"author": "Ozzie Gooen <ozzieagooen@gmail.com>",
"license": "MIT",
"dependencies": {
"@foretold/cdf": "1.0.16",
"@foretold/guesstimator": "1.0.11",
"bs-css": "11.0.0",
"bs-moment": "0.4.5",
"bs-platform": "7.2.2",
"d3": "5.16.0",
"emotion": "10.0.27",
"lodash": "4.17.15",
"moment": "2.24.0",
"rationale": "0.2.0",
"rc-dropdown": "3.0.2",
"rc-menu": "8.0.3",
"react": "16.12.0",
"react-dom": "16.12.0",
"react-icons-kit": "1.3.1",
"vega-embed": "6.6.0",
"vega": "5.11.1",
"react-use": "14.2.0",
"reason-apollo": "0.20.0",
"reason-react": "0.7.1",
"react-textarea-autosize": "7.1.2"
},
"devDependencies": {
"parcel-bundler": "1.12.3"
}
}

View File

@ -0,0 +1,23 @@
let entries =
EntryTypes.[
Showcase_Buttons.entry,
Showcase_PageCard.entry,
Showcase_NumberShower.entry,
Showcase_MeasurableForm.entry,
Showcase_Colors.entry,
Showcase_AgentLink.entry,
Showcase_MyCommunities.entry,
folder(
~title="Link",
~children=[
entry(~title="Link1b", ~render=() =>
<Link> "Test link"->React.string </Link>
),
],
),
]
@ Showcase_Charts.entries
@ Showcase_Dropdown.entries
@ Showcase_Menu.entries
@ Showcase_DropdownMenu.entries
@ Showcase_DropdownSelect.entries;

View File

@ -0,0 +1,30 @@
type compEntry = {
mutable id: string,
title: string,
render: unit => React.element,
container: containerType,
}
and folderEntry = {
mutable id: string,
title: string,
children: list(navEntry),
}
and navEntry =
| CompEntry(compEntry)
| FolderEntry(folderEntry)
and containerType =
| FullWidth
| Sidebar;
let entry = (~title, ~render): navEntry => {
CompEntry({id: "", title, render, container: FullWidth});
};
// Maybe different api, this avoids breaking changes
let sidebar = (~title, ~render): navEntry => {
CompEntry({id: "", title, render, container: Sidebar});
};
let folder = (~title, ~children): navEntry => {
FolderEntry({id: "", title, children});
};

View File

@ -0,0 +1,7 @@
ReactDOMRe.renderToElementWithId(
<div className=Css.(style([fontFamily(Settings.Text.standardFont)]))>
<Lib.Index />
</div>,
"main",
);
ReasonReactRouter.push("");

View File

@ -0,0 +1,201 @@
open FC.Base;
open EntryTypes;
module HS = Belt.HashMap.String;
let entriesByPath: HS.t(navEntry) = HS.make(~hintSize=100);
/* Creates unique id's per scope based on title */
let buildIds = entries => {
let genId = (title, path) => {
let noSpaces = Js.String.replaceByRe([%bs.re "/\\s+/g"], "-", title);
if (!HS.has(entriesByPath, path ++ "/" ++ noSpaces)) {
noSpaces;
} else {
let rec loop = num => {
let testId = noSpaces ++ "-" ++ string_of_int(num);
if (!HS.has(entriesByPath, path ++ "/" ++ testId)) {
testId;
} else {
loop(num + 1);
};
};
loop(2);
};
};
let rec processFolder = (f: folderEntry, curPath) => {
f.id = curPath ++ "/" ++ genId(f.title, curPath);
HS.set(entriesByPath, f.id, FolderEntry(f));
f.children
|> E.L.iter(e =>
switch (e) {
| CompEntry(c) => processEntry(c, f.id)
| FolderEntry(f) => processFolder(f, f.id)
}
);
}
and processEntry = (c: compEntry, curPath) => {
c.id = curPath ++ "/" ++ genId(c.title, curPath);
HS.set(entriesByPath, c.id, CompEntry(c));
};
entries
|> E.L.iter(e =>
switch (e) {
| CompEntry(c) => processEntry(c, "")
| FolderEntry(f) => processFolder(f, "")
}
);
};
let entries = Entries.entries;
buildIds(entries);
module Styles = {
open Css;
let pageContainer = style([display(`flex), height(`vh(100.))]);
let leftNav =
style([
padding(`em(2.)),
flexBasis(`px(200)),
flexShrink(0.),
backgroundColor(`hex("eaeff3")),
boxShadows([
Shadow.box(
~x=px(-1),
~blur=px(1),
~inset=true,
rgba(0, 0, 0, 0.1),
),
]),
]);
let folderNav =
style([
selector(
">h4",
[
cursor(`pointer),
margin2(~v=`em(0.3), ~h=`zero),
hover([color(`hex("7089ad"))]),
],
),
]);
let folderChildren = style([paddingLeft(`px(7))]);
let compNav =
style([
cursor(`pointer),
paddingBottom(`px(3)),
hover([color(`hex("7089ad"))]),
]);
let compContainer = style([padding(`em(2.)), flexGrow(1.)]);
// Approximate sidebar container for entry
let sidebarContainer = style([maxWidth(`px(430))]);
let folderChildContainer = style([marginBottom(`em(2.))]);
};
let baseUrl = "/showcase/index.html";
module Index = {
type state = {route: ReasonReactRouter.url};
type action =
| ItemClick(string)
| ChangeRoute(ReasonReactRouter.url);
let changeId = (id: string) => {
ReasonReactRouter.push(baseUrl ++ "#" ++ id);
();
};
let buildNav = setRoute => {
let rec buildFolder = (f: folderEntry) => {
<div key={f.id} className=Styles.folderNav>
<h4 onClick={_e => changeId(f.id)}> f.title->React.string </h4>
<div className=Styles.folderChildren>
{(
f.children
|> E.L.fmap(e =>
switch (e) {
| FolderEntry(folder) => buildFolder(folder)
| CompEntry(entry) => buildEntry(entry)
}
)
|> E.L.toArray
)
->React.array}
</div>
</div>;
}
and buildEntry = (e: compEntry) => {
<div key={e.id} className=Styles.compNav onClick={_e => changeId(e.id)}>
e.title->React.string
</div>;
};
(
entries
|> E.L.fmap(e =>
switch (e) {
| FolderEntry(folder) => buildFolder(folder)
| CompEntry(entry) => buildEntry(entry)
}
)
|> E.L.toArray
)
->React.array;
};
let renderEntry = e => {
switch (e.container) {
| FullWidth => e.render()
| Sidebar => <div className=Styles.sidebarContainer> {e.render()} </div>
};
};
[@react.component]
let make = () => {
let (route, setRoute) =
React.useState(() => {
let url: ReasonReactRouter.url = {path: [], hash: "", search: ""};
url;
});
React.useState(() => {
ReasonReactRouter.watchUrl(url => setRoute(_ => url));
();
})
|> ignore;
<div className=Styles.pageContainer>
<div className=Styles.leftNav> {buildNav(setRoute)} </div>
<div className=Styles.compContainer>
{if (route.hash == "") {
React.null;
} else {
switch (HS.get(entriesByPath, route.hash)) {
| Some(navEntry) =>
switch (navEntry) {
| CompEntry(c) => renderEntry(c)
| FolderEntry(f) =>
/* Rendering immediate children */
(
f.children
|> E.L.fmap(child =>
switch (child) {
| CompEntry(c) =>
<div className=Styles.folderChildContainer key={c.id}>
{renderEntry(c)}
</div>
| _ => React.null
}
)
|> E.L.toArray
)
->React.array
}
| None => <div> "Component not found"->React.string </div>
};
}}
</div>
</div>;
};
};

View File

@ -0,0 +1,104 @@
open FC.Base;
let multimodal = "=mm(uniform(0,100), uniform(10,20), [.33,0.9])";
let mm1 = "=mm(uniform(1,100), normal(50, 5), [.01, .99])";
let mm2 = "=mm(uniform(1,100), normal(50, 8), [.01, .99])";
module Scoring = {
type dist = Types.Dist.t;
[@react.component]
let make = () => {
let (varA, setVarA) = React.useState(() => None);
let (varB, setVarB) = React.useState(() => None);
let (varC, setVarC) = React.useState(() => None);
let distributionScoreDistribution =
switch (varA, varB, varC) {
| (Some(a), Some(b), Some(c)) =>
Types.Dist.distributionScoreDistribution([|a, b, c|])
| _ => None
};
let distributionScoreNumber =
switch (varA, varB, varC) {
| (Some(a), Some(b), Some(c)) =>
Some(Types.Dist.distributionScoreNumber([|a, b, c|]))
| _ => None
};
let minX =
[|varA, varB, varC, distributionScoreDistribution|]
|> E.A.O.concatSome
|> Types.Dists.minX(0.01);
let maxX =
[|varA, varB, varC, distributionScoreDistribution|]
|> E.A.O.concatSome
|> Types.Dists.maxX(0.99);
Js.log2("MIN", min);
<div>
<h3> {"Variable A" |> ReasonReact.string} </h3>
<ReGuesstimateInput
focusOnRender=true
initialValue={Some(mm1)}
sampleCount=50000
onUpdate={event =>
{let (ys, xs, hasLimitError) = event
setVarA(_ => Types.Dist.requireLength({ys, xs}))
Js.log2(xs, ys)}
|> ignore
}
/>
{varA
|> E.O.React.fmapOrNull(v =>
<CdfChart__Large cdf=v minX maxX width=None />
)}
<h3> {"Variable B" |> ReasonReact.string} </h3>
<ReGuesstimateInput
focusOnRender=true
sampleCount=50000
initialValue={Some(mm2)}
onUpdate={event =>
{let (ys, xs, hasLimitError) = event
setVarB(_ => Types.Dist.requireLength({ys, xs}))}
|> ignore
}
/>
{varB
|> E.O.React.fmapOrNull(v =>
<CdfChart__Large minX maxX cdf=v width=None />
)}
<h3> {"Variable C" |> ReasonReact.string} </h3>
<ReGuesstimateInput
focusOnRender=true
sampleCount=50000
initialValue={Some("20 to 60")}
onUpdate={event =>
{let (ys, xs, hasLimitError) = event
setVarC(_ => Types.Dist.requireLength({ys, xs}))}
|> ignore
}
/>
{varC
|> E.O.React.fmapOrNull(v =>
<CdfChart__Large minX maxX cdf=v width=None />
)}
<h3> {"C * Log2(A / B)" |> ReasonReact.string} </h3>
{switch (distributionScoreDistribution) {
| None => ReasonReact.null
| Some(divideBy) =>
<CdfChart__Large minX maxX cdf=divideBy width=None />
}}
<h3> {"Final Score" |> ReasonReact.string} </h3>
{switch (distributionScoreNumber) {
| None => ReasonReact.null
| Some(scoreNumber) =>
scoreNumber |> E.Float.with3DigitsPrecision |> ReasonReact.string
}}
</div>;
};
};
let entry = EntryTypes.(entry(~title="Scoring", ~render=() => <Scoring />));

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,12 @@
open FC.Base;
let alerts = () =>
<div>
<Alert type_=`primary> "Primary alert"->React.string </Alert>
<Alert type_=`info> "Info alert"->React.string </Alert>
<Alert type_=`success> "Success alert"->React.string </Alert>
<Alert type_=`warning> "Warning alert"->React.string </Alert>
<Alert type_=`error> "Error alert"->React.string </Alert>
</div>;
let entry = EntryTypes.(entry(~title="Alerts", ~render=alerts));

View File

@ -0,0 +1,36 @@
open FC;
open Button;
let clear = Css.(style([clear(`both)]));
let button = Css.(style([margin(`em(0.5))]));
let render = () =>
<>
<div> "Secondary"->React.string </div>
<div>
<Button size=MediumShort className=button>
"Small Button"->React.string
</Button>
<Button size=Medium className=button>
"Medium Button"->React.string
</Button>
<Button size=Large className=button>
"Large Button"->React.string
</Button>
</div>
<div className=clear />
<div> "Primary"->React.string </div>
<div>
<Button size=MediumShort variant=Primary className=button>
"Small Button"->React.string
</Button>
<Button size=Medium variant=Primary className=button>
"Medium Button"->React.string
</Button>
<Button size=Large variant=Primary className=button>
"Large Button"->React.string
</Button>
</div>
</>;
let entry = EntryTypes.(entry(~title="Buttons", ~render));

View File

@ -0,0 +1,18 @@
[@bs.module] external data1: Js.Json.t = "./samples/sample-measurements.json";
[@bs.module]
external data2: Js.Json.t = "./samples/sample-measurements-aggregation.json";
let chart1 = () => <div> <RePercentilesChart data=data1 /> </div>;
let chart2 = () => <div> <RePercentilesChart data=data2 /> </div>;
let entries =
EntryTypes.[
folder(
~title="Charts",
~children=[
entry(~title="Chart 1", ~render=chart1),
entry(~title="Chart 2", ~render=chart2),
],
),
];

View File

@ -0,0 +1,109 @@
open FC;
open FC.Base;
let colors =
Colors.[
("white", white),
("black", black),
("greyO4", greyO4),
("whiteO2", whiteO2),
("whiteOc", whiteOc),
("clear", clear),
("textDarker", textDarker),
("textDark", textDark),
("textMedium", textMedium),
("smokeWhite", smokeWhite),
("buttonHover", buttonHover),
("lightGrayBackground", lightGrayBackground),
("lighterGrayBackground", lighterGrayBackground),
("grayBackground", grayBackground),
("greydisabled", greydisabled),
("accentBlue", accentBlue),
("accentBlueO8", accentBlueO8),
("accentBlue1a", accentBlue1a),
("mainBlue", mainBlue),
("link", link),
("linkHover", linkHover),
("linkAccent", linkAccent),
("darkLink", darkLink),
("darkAccentBlue", darkAccentBlue),
("grey1", grey1),
("border", border),
("primary", primary),
];
let colorBoxStyle =
Css.(
style([
flexBasis(`px(220)),
flexGrow(1.),
height(`px(90)),
marginRight(`px(35)),
marginBottom(`px(35)),
textAlign(`center),
])
);
let colorContainer = bgColor =>
Css.(
style([
display(`flex),
flexWrap(`wrap),
padding(`em(1.5)),
backgroundColor(bgColor),
])
);
module ColorDisplay = {
[@react.component]
let make = () => {
let (bgColor, setBgColor) = React.useState(() => Colors.white);
let (bgName, setBgName) = React.useState(() => "white");
<PageCard>
<PageCard.HeaderRow>
<PageCard.HeaderRow.Title>
"Colors"->React.string
</PageCard.HeaderRow.Title>
</PageCard.HeaderRow>
<PageCard.Section border=`bottom>
"Background: "->React.string
<select
value=bgName
onChange={e => {
let bgName = ReactEvent.Form.target(e)##value;
setBgName(_ => bgName);
setBgColor(_ => {
let (_, bgColor) =
colors |> E.L.find(((n, _)) => n == bgName);
bgColor;
});
}}>
{{colors
|> E.L.fmap(((name, _c)) =>
<option key=name value=name> name->React.string </option>
)
|> E.L.toArray}
->React.array}
</select>
</PageCard.Section>
<div className={colorContainer(bgColor)}>
{{colors
|> E.L.fmap(((name, c)) =>
<div
key=name
className=Css.(
merge([colorBoxStyle, style([backgroundColor(c)])])
)>
name->React.string
</div>
)
|> E.L.toArray}
->React.array}
</div>
</PageCard>;
};
};
let entry =
EntryTypes.(entry(~title="Colors", ~render=() => <ColorDisplay />));

View File

@ -0,0 +1,46 @@
open FC.Base;
let staticOverlay =
<div
className=Css.(
style([
border(`px(1), `solid, Colors.grey1),
backgroundColor(Colors.white),
width(`px(200)),
])
)>
{"Overlay" |> React.string}
</div>;
let divAreaStyle =
Css.(
style([
backgroundColor(Colors.smokeWhite),
width(`px(300)),
textAlign(`center),
padding2(~v=`em(1.), ~h=`em(1.)),
])
);
let simpleDropdown = () =>
<Dropdown overlay=staticOverlay>
<div className=divAreaStyle>
"Dropdown default trigger (hover)"->React.string
</div>
</Dropdown>;
let menuDropdown = () =>
<Dropdown overlay={Showcase_Menu.subMenu()} trigger=Dropdown.Hover>
<div className=divAreaStyle> "Submenu"->React.string </div>
</Dropdown>;
let entries =
EntryTypes.[
folder(
~title="Dropdown",
~children=[
entry(~title="Dropdown1", ~render=simpleDropdown),
entry(~title="Menu dropdown", ~render=menuDropdown),
],
),
];

View File

@ -0,0 +1,39 @@
open FC.Base;
let simpleMenu = () =>
<DropdownMenu title="Simple menu">
Menu.(
<Menu onClick={e => Js.log(e.key)}>
<Item key="item1"> "1st menu item"->React.string </Item>
<Item key="item2"> "2nd menu item"->React.string </Item>
<Divider />
<Item key="item3"> "3nd menu item"->React.string </Item>
</Menu>
)
</DropdownMenu>;
let subMenu = () =>
<DropdownMenu title="Submenu" trigger=Dropdown.Hover>
Menu.(
<Menu onClick={e => Js.log(e.key)}>
<Item key="item1"> "Item1"->React.string </Item>
<Divider />
<Item key="item2"> "Item2"->React.string </Item>
<SubMenu title="Submenu1">
<Item key="item3"> "Item3"->React.string </Item>
<Item key="item4"> "Item4"->React.string </Item>
</SubMenu>
</Menu>
)
</DropdownMenu>;
let entries =
EntryTypes.[
folder(
~title="Dropdown menu",
~children=[
entry(~title="Simple", ~render=simpleMenu),
entry(~title="Submenu", ~render=subMenu),
],
),
];

View File

@ -0,0 +1,60 @@
open FC.Base;
let stringSelect = () =>
<DropdownSelect
initialValue=None
values=[("key1", "Label 1"), ("key2", "Label 2"), ("key3", "Label 3")]
onSelect={v =>
switch (v) {
| Some(k) => Js.log2("Selected ", k)
| None => Js.log("Selected none")
}
}
/>;
let intSelect = () =>
<DropdownSelect
initialValue={Some(2)}
values=[(1, "Int label 1"), (2, "Int label 2"), (3, "Int label 3")]
onSelect={v =>
switch (v) {
| Some(k) => Js.log2("Selected ", k)
| None => Js.log("Selected none")
}
}
/>;
type customType =
| Option1
| Option2
| Option3;
let customSelect = () =>
<DropdownSelect
initialValue={Some(Option3)}
values=[
(Option1, "Option1"),
(Option2, "Option2"),
(Option3, "Option3"),
]
onSelect={v =>
switch (v) {
| Some(Option1) => Js.log("Option 1")
| Some(Option2) => Js.log("Option 2")
| Some(Option3) => Js.log("Option 3")
| None => Js.log("None")
}
}
/>;
let entries =
EntryTypes.[
folder(
~title="DropdownSelect",
~children=[
entry(~title="Select1", ~render=stringSelect),
entry(~title="Int select", ~render=intSelect),
entry(~title="Custom select", ~render=customSelect),
],
),
];

View File

@ -0,0 +1,11 @@
open FC;
let cdf: Types.Dist.t = {
xs: [|0.2, 0.4, 0.6, 0.8, 1.0|],
ys: [|0.2, 0.3, 0.5, 0.3, 0.2|],
};
let measurableForm = () => <MeasurableForm cdf />;
let entry =
EntryTypes.(sidebar(~title="Question form", ~render=measurableForm));

View File

@ -0,0 +1,33 @@
open FC.Base;
let simpleMenu = () =>
Menu.(
<Menu onClick={e => Js.log(e.key)}>
<Item key="item1"> "Item1"->React.string </Item>
<Item key="item2"> "Item2"->React.string </Item>
</Menu>
);
let subMenu = () =>
Menu.(
<Menu onClick={e => Js.log(e.key)}>
<Item key="item1"> "Item1"->React.string </Item>
<Divider />
<Item key="item2"> "Item2"->React.string </Item>
<SubMenu title="Submenu1">
<Item key="item3"> "Item3"->React.string </Item>
<Item key="item4"> "Item3"->React.string </Item>
</SubMenu>
</Menu>
);
let entries =
EntryTypes.[
folder(
~title="Menu",
~children=[
entry(~title="Simple", ~render=simpleMenu),
entry(~title="Submenu", ~render=subMenu),
],
),
];

View File

@ -0,0 +1,98 @@
let makeItem = (name, icon, bookmark): MyCommunities.item => {
name,
icon,
bookmark,
href: "",
onClick: _ => (),
onBookmark: _ => (),
};
let backgroundBox =
Css.(style([background(`hex("ccc")), padding(`em(3.))]));
let innerBox =
Css.(
style([
fontSize(`rem(1.1)),
width(`em(20.)),
border(`px(1), `solid, `hex("d5d2d2")),
padding2(~v=`em(0.5), ~h=`em(0.)),
borderRadius(`px(5)),
background(`hex("fff")),
])
);
let myCommunities = () =>
<div className=backgroundBox>
<div className=innerBox>
<MyCommunities>
<MyCommunities.Header name="FEEDS" />
<MyCommunities.Item item={makeItem("Home", "HOME", false)} />
<MyCommunities.Item
item={makeItem("All Communities", "LIST", false)}
/>
<MyCommunities.Header name="MY COMMUNITIES" />
<MyCommunities.ChannelItem
item={makeItem("Slate-Star-Codex 2019", "PEOPLE", false)}
/>
<MyCommunities.ChannelItem
item={makeItem("AI Questions", "PEOPLE", false)}
/>
<MyCommunities.ChannelItem
item={makeItem("AI Questions", "PEOPLE", true)}
/>
<MyCommunities.ChannelItem
item={makeItem("AI Questions", "PEOPLE", true)}
/>
<MyCommunities.ChannelItem
item={makeItem("AI Questions", "PEOPLE", false)}
/>
<MyCommunities.ChannelItem
item={makeItem("AI Questions", "PEOPLE", false)}
/>
<MyCommunities.ChannelItem
item={makeItem("AI Questions", "PEOPLE", false)}
/>
<MyCommunities.ChannelItem
item={makeItem("AI Questions", "PEOPLE", false)}
/>
<MyCommunities.ChannelItem
item={makeItem("AI Questions", "PEOPLE", true)}
/>
<MyCommunities.ChannelItem
item={makeItem("AI Questions", "PEOPLE", false)}
/>
<MyCommunities.ChannelItem
item={makeItem("AI Questions", "PEOPLE", false)}
/>
<MyCommunities.ChannelItem
item={makeItem("AI Questions", "PEOPLE", false)}
/>
<MyCommunities.ChannelItem
item={makeItem("AI Questions", "PEOPLE", false)}
/>
<MyCommunities.ChannelItem
item={makeItem("AI Questions", "PEOPLE", false)}
/>
<MyCommunities.ChannelItem
item={makeItem("AI Questions", "PEOPLE", false)}
/>
<MyCommunities.ChannelItem
item={makeItem("Other AI Questions", "LOCK", false)}
/>
<MyCommunities.ChannelItem
item={makeItem(
"My Secret and Very Very Very Very Long-named Community",
"LOCK",
false,
)}
/>
<MyCommunities.Header name="OPTIONS" />
<MyCommunities.Item
item={makeItem("Create a New Community", "CIRCLE_PLUS", true)}
/>
</MyCommunities>
</div>
</div>;
let entry = EntryTypes.(entry(~title="MyCommunities", ~render=myCommunities));

View File

@ -0,0 +1,61 @@
open FC;
open FC.Base;
let numbers = [
0.000000000000000000000000000001,
0.00000000001,
0.00000001,
(-0.00000001),
0.00000001200332,
0.00001,
0.0000130300033,
0.01,
(-0.01),
0.010000303030,
0.0,
0.010001,
1.0,
(-1.0),
1.1000,
1.0100,
1.0010,
1.0001,
100.0,
100.5,
(-100.5),
1000000.0,
1001001.0,
100000000000.0,
100000000000000000.0,
10000000000000000000000.0,
10000100000000000000000.0,
1000000000000100000000000000000.0,
];
module NumbersDisplay = {
[@react.component]
let make = () => {
<PageCard>
<PageCard.HeaderRow>
<PageCard.HeaderRow.Title>
"NumberShower"->React.string
</PageCard.HeaderRow.Title>
</PageCard.HeaderRow>
<div>
{(
numbers
|> E.L.fmap(n =>
<div key={n |> Js.Float.toString}>
<NumberShower number=n precision=3 />
</div>
)
|> E.L.toArray
)
->React.array}
</div>
</PageCard>;
};
};
let entry =
EntryTypes.(entry(~title="NumberShower", ~render=() => <NumbersDisplay />));

View File

@ -0,0 +1,19 @@
open FC;
let render = () =>
<PageCard>
<PageCard.HeaderRow>
<PageCard.HeaderRow.Title>
"PageCard.HeaderRow.Title"->React.string
</PageCard.HeaderRow.Title>
</PageCard.HeaderRow>
<PageCard.BodyPadding>
<PageCard.H1> "PageCard.H1"->React.string </PageCard.H1>
<PageCard.P> "PageCard.P"->React.string </PageCard.P>
</PageCard.BodyPadding>
<PageCard.Section border=`top background=`grey>
"Section, grey + borderTop"->React.string
</PageCard.Section>
</PageCard>;
let entry = EntryTypes.(entry(~title="PageCard", ~render));

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,23 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<link href="https://fonts.googleapis.com/css?family=Lato:300,400,700,900" rel="stylesheet">
<!--<link rel="stylesheet" href="styles.css">-->
<style>
body {
margin: 0;
}
</style>
<title>Showcase</title>
</head>
<body>
<div id="main"></div>
<script src=" ./Index.bs.js "></script>
</body>
</html>

View File

@ -0,0 +1,98 @@
module Styles = {
open Css;
let avatarOuter =
style([float(`left), position(`relative), marginRight(`em(0.4))]);
let avatar = style([marginTop(`em(0.1)), float(`left)]);
let ownerAvatarOuter =
style([
float(`left),
width(`em(1.)),
height(`percent(100.)),
marginLeft(`em(0.1)),
]);
let ownerAvatar = style([position(`absolute), bottom(`zero)]);
};
module Agent = {
type user = {
name: string,
image: option(string),
onClick: ReactEvent.Mouse.t => unit,
}
and bot = {
name: string,
image: option(string),
onClick: ReactEvent.Mouse.t => unit,
owner: option(t),
}
and t =
| User(user)
| Bot(bot);
let onClick = agent =>
switch (agent) {
| User(u) => u.onClick
| Bot(u) => u.onClick
};
let name = agent =>
switch (agent) {
| User(u) => u.name
| Bot(u) => u.name
};
let image = agent =>
switch (agent) {
| User(u) => u.image
| Bot(u) => u.image
};
let owner = agent =>
switch (agent) {
| User(_) => None
| Bot(b) => b.owner
};
let makeUser = (~name: string, ~onClick=_ => (), ~image=?, ()): t =>
User({name, image, onClick});
let makeBot = (~name: string, ~onClick=_ => (), ~image=?, ~owner=?, ()): t =>
Bot({name, image, onClick, owner});
};
module SubItem = {
[@react.component]
let make = (~agent: Agent.t, ~owner: option(Agent.t), ~className) =>
<Link onClick={Agent.onClick(agent)} className>
<div className=Styles.avatarOuter>
<div className=Styles.avatar>
<Avatar
width=1.2
src={
Agent.image(agent)
|> Rationale.Option.default(BotDefaultImage.botDefault)
}
/>
</div>
{owner
|> E.O.React.fmapOrNull(owner =>
<div className=Styles.ownerAvatarOuter>
<div className=Styles.ownerAvatar>
<Avatar
width=0.8
src={
Agent.image(owner)
|> Rationale.Option.default(BotDefaultImage.botDefault)
}
/>
</div>
</div>
)}
</div>
{Agent.name(agent) |> ReasonReact.string}
</Link>;
};
[@react.component]
let make = (~agent: Agent.t, ~className="") =>
<SubItem agent className owner={Agent.owner(agent)} />;

View File

@ -0,0 +1,28 @@
open Base;
module Styles = {
open Css;
let outer =
style(
[
padding2(~v=`em(1.0), ~h=`em(2.)),
backgroundColor(`rgb((255, 255, 255))),
position(`relative),
boxShadows([
Shadow.box(
~x=px(1),
~y=px(1),
~blur=px(2),
~spread=px(1),
~inset=false,
`hex("dee5e9"),
),
]),
]
@ BaseStyles.fullWidthFloatLeft,
);
};
[@react.component]
let make = (~links: ReasonReact.reactElement) =>
<Div styles=[Styles.outer]> <Div float=`left> links </Div> </Div>;

View File

@ -0,0 +1,35 @@
/* For the icons, font-awesome (or similar?) is a possibility */
module Styles = {
open Css;
let alertBox =
style([
borderRadius(Settings.BorderRadius.tight),
padding2(~v=`em(0.5), ~h=`em(0.8)),
marginBottom(`em(0.75)),
]);
// Colors from https://getbootstrap.com/docs/4.0/components/alerts/
// They may look better on white background than grey/smokeWhite
let colors = (t: Settings.Alert.t) =>
style([
color(Settings.Alert.color(t)),
backgroundColor(Settings.Alert.background(t)),
]);
};
/**
* Primary - Communicate information like "Welcome, now you can do..."
* Info - Less significant information
* Success
* Warning
* Error
*/
type type_ = [ | `primary | `info | `success | `warning | `error];
[@react.component]
let make = (~type_: type_=`info, ~children) => {
let classes = Styles.alertBox ++ " " ++ Styles.colors(type_);
<div className=classes> children </div>;
};

View File

@ -0,0 +1,25 @@
module Styles = {
open Css;
let imageCropper = width =>
style([
Css.width(`em(width)),
height(`em(width)),
overflow(`hidden),
float(`left),
position(`relative),
borderRadius(`percent(20.)),
]);
let image =
style([
float(`left),
margin2(~v=`zero, ~h=`auto),
height(`auto),
width(`percent(100.)),
]);
};
[@react.component]
let make = (~src: string, ~width=1., ()) =>
<span className={Styles.imageCropper(width)}>
<img src className=Styles.image />
</span>;

View File

@ -0,0 +1,19 @@
module Types = Types;
module Link = Link;
module Colors = Settings;
module Div = Div;
module E = E;
module Globals = Globals;
module BaseStyles = BaseStyles;
module Button = Button;
module InputLabel = InputLabel;
module TextInput = TextInput;
module TextArea = TextArea;
module DropdownSelect = DropdownSelect;
module Icon = Icon;
module Dropdown = Dropdown;
module Menu = Menu;
module DropdownMenu = DropdownMenu;
module TabList = TabList;
module Alert = Alert;
module Avatar = Avatar;

View File

@ -0,0 +1,8 @@
let floatLeft = Css.float(`left);
let floatRight = Css.float(`right);
let fullWidthFloatLeft =
Css.[floatLeft, width(`percent(100.0)), boxSizing(`borderBox)];
let borderNone = Css.[borderBottom(`px(0), `solid, hex("fff"))];
let clear = Css.style([Css.clear(`both)]);

View File

@ -0,0 +1,135 @@
type color = [ | `hex(Js.String.t)];
/* I made this contain strings instead of colors,
because the type for background is different than
that for the others, which made things pretty messy. */
type variantColors = {
text: string,
textHover: string,
border: string,
background: string,
backgroundHover: string,
};
type variant =
| Primary
| Secondary;
type size =
| Small
| MediumShort
| Medium
| Large;
let varantColors = (variant: variant) =>
Settings.(
switch (variant) {
| Primary => {
text: white |> toS,
textHover: white |> toS,
border: link |> toS,
background: link |> toS,
backgroundHover: linkHover |> toS,
}
| Secondary => {
text: textDark |> toS,
textHover: textDark |> toS,
border: accentBlueO8 |> toS,
background: white |> toS,
backgroundHover: buttonHover |> toS,
}
}
);
let sizeStyles = size => {
switch (size) {
| Small =>
Css.(
style([
padding2(~v=`em(0.15), ~h=`em(1.0)),
fontSize(`px(14)),
minHeight(`em(1.9)),
])
)
| MediumShort =>
Css.(style([padding2(~v=`em(0.2), ~h=`em(1.1)), fontSize(`px(14))]))
| Medium =>
Css.(style([padding2(~v=`em(0.25), ~h=`em(1.4)), fontSize(`px(16))]))
| Large =>
Css.(style([padding2(~v=`em(0.5), ~h=`em(2.4)), fontSize(`px(16))]))
};
};
let styles = (~isDisabled=false, ~variant, ~size, ~fullWidth=false, ()) => {
let colors = varantColors(variant);
let main =
Css.(
style([
fontFamily(Settings.Text.standardFont),
lineHeight(`em(1.5)),
cursor(`pointer),
BaseStyles.floatLeft,
borderRadius(Settings.BorderRadius.medium),
border(`px(1), `solid, `hex(colors.border)),
color(`hex(colors.text)),
background(`hex(colors.background)),
hover([
background(`hex(colors.backgroundHover)),
color(`hex(colors.textHover)),
]),
transition(
~duration=Settings.Transitions.standardLength,
"background",
),
])
);
let disabledStyles =
Css.(style([background(Settings.greydisabled), opacity(0.5)]));
let fullWidthStyle =
Css.(
style([
width(`percent(100.)),
boxSizing(`borderBox),
textAlign(`center),
Css.float(`none),
display(`block),
])
);
let total =
switch (isDisabled, fullWidth) {
| (false, false) => main
| (true, false) => Css.merge([main, disabledStyles])
| (false, true) => Css.merge([main, fullWidthStyle])
| (true, true) => Css.merge([main, disabledStyles, fullWidthStyle])
};
Css.merge([sizeStyles(size), total]);
};
// Button must be button
[@react.component]
let make =
(
~onClick=?,
~variant=Secondary,
~size=Medium,
~isDisabled=false,
~fullWidth=false,
~className="",
~children=ReasonReact.null,
) =>
<button
?onClick
disabled=isDisabled
className={Css.merge([
styles(~isDisabled, ~fullWidth, ~variant, ~size, ()),
className,
])}>
children
</button>;

View File

@ -0,0 +1,48 @@
open Css;
let fnWithDefault = (fn, r) =>
r |> E.O.fmap(e => Css.style(fn(e))) |> E.O.default("");
/* TODO: Instead of accepting styles, this should accept "classNames" and use Css.merge */
[@react.component]
let make =
(
~styles=[],
~className="",
~flex=?,
~flexDirection=?,
~float=?,
~alignItems=?,
~justifyContent=?,
~alignContent=?,
~onClick=_ => (),
~children=ReasonReact.null,
) => {
let flexStyle = flex |> fnWithDefault(e => [Css.flex(e)]);
let floatStyle = float |> fnWithDefault(e => [Css.float(e)]);
let alignItemsStyle =
alignItems |> fnWithDefault(e => [Css.alignItems(e)]);
let justifyContentStyle =
justifyContent |> fnWithDefault(e => [Css.justifyContent(e)]);
let alignContentStyle =
alignContent |> fnWithDefault(e => [Css.alignContent(e)]);
let directionStyle =
flexDirection
|> fnWithDefault(e => [display(`flex), Css.flexDirection(e)]);
let allStyles =
Css.merge([
flexStyle,
directionStyle,
floatStyle,
alignItemsStyle,
justifyContentStyle,
alignContentStyle,
className,
...styles,
]);
<div className=allStyles onClick> children </div>;
};

View File

@ -0,0 +1,104 @@
/**
* Dropdown component provides a way to show an overlay element
* at a position relative to it's trigger element (the children given
* to this element), on various triggers: Click, Hover, ContextMenu and Focus.
*
* It is bindings to https://github.com/react-component/dropdown
*/
[@bs.module "rc-dropdown"]
external rcDropDownClass: ReasonReact.reactClass = "default";
[%bs.raw {|require("rc-dropdown/assets/index.css")|}];
/** On what event to trigger dropdown
* rc-trigger supports an array of triggers.
* This is currently not encoded here currently as
* it was considered a rarer case, many combinations are superfluous,
* and delayed in interest of simplicity.
* If there is only one or a few more, one possiblity
* would be a variant like ClickFocus.
*/
type trigger =
| Click
| Hover
| ContextMenu
| Focus;
let triggerToString = trigger =>
switch (trigger) {
| Click => "click"
| Hover => "hover"
| ContextMenu => "contextMenu"
| Focus => "focus"
};
/* In the documentation of rc-dropdown, the overlay
property is specified to be a rc-menu,
but this doesn't seem to be a hard
requirement in the source */
[@bs.deriving abstract]
type jsProps = {
overlay: ReasonReact.reactElement,
trigger: array(string),
prefixCls: string,
overlayClassName: string,
};
module Styles = {
open Css;
// Styles are based on a prefixCls given to the element
// Some styles can be applied to all dropdowns, while
// others are more specific
// General
// First overlay element, this doesn't apply to submenues, but
// all kinds of direct overlays
global(
".ft-overlay",
[
fontFamily(Settings.Text.standardFont),
fontSize(`rem(1.)),
lineHeight(`rem(1.0)),
zIndex(1070),
position(`absolute),
left(`px(-9999)),
top(`px(-9999)),
],
);
};
/**
* Dropdown component provides a way to show an overlay element
* at a position relative to it's trigger element (the children given
* to this element), on various triggers: Click, Hover, ContextMenu and Focus.
* Can be used for example with the Menu component as overlay
*
* Usage:
* ```
* let staticOverlay = <div> {"Todo: style" |> React.string} </div>;
*
* <Dropdown overlay=staticOverlay trigger=Dropdown.Click>
* <div> {"Trigger element" |> React.string} </div>
* </Dropdown>
* ```
*/
[@react.component]
let make =
(
~overlay,
~trigger=Hover,
~prefixCls="rc-dropdown",
~children=ReasonReact.null,
) =>
ReasonReact.wrapJsForReason(
~reactClass=rcDropDownClass,
~props=
jsProps(
~overlay,
~trigger=[|triggerToString(trigger)|],
~overlayClassName="ft-overlay",
~prefixCls,
),
children,
)
|> ReasonReact.element;

View File

@ -0,0 +1,58 @@
module Styles = {
open Css;
module Colors = Settings;
// Reverting to "rc-dropdown" brings back some styles from the original css file
let prefixCls = "ft-dropdown";
let itemVerticalPadding = 5;
let itemHorizontalPadding = 12;
let bgColor = Colors.white;
let textColor = Colors.textDarker;
let hoverColor = `hex("ebfaff");
let textSize = `rem(0.8);
let textLineHeight = `em(1.3);
Menu.Styles.createStyle(
~prefixCls,
~itemVerticalPadding,
~itemHorizontalPadding,
~textColor,
~bgColor,
~hoverColor,
~textSize,
~textLineHeight,
(),
);
let dropdownTrigger =
style([
border(`px(1), `solid, Settings.border),
borderRadius(`px(3)),
// Subtracting 2 from horizontal padding to account for border
padding2(
~v=`px(itemVerticalPadding),
~h=`px(itemHorizontalPadding - 2),
),
backgroundColor(bgColor),
fontSize(textSize),
lineHeight(textLineHeight),
fontFamily(Settings.Text.standardFont),
color(textColor),
// Selector for trigger element with overlay open
selector(
"&." ++ prefixCls ++ "-open",
[borderColor(`hex("40a9ff")), color(`hex("40a9ff"))],
),
]);
};
[@react.component]
let make = (~title, ~trigger=Dropdown.Hover, ~children) => {
let overlay = <div> children </div>;
<Dropdown trigger overlay prefixCls=Styles.prefixCls>
<button className=Styles.dropdownTrigger>
<span> {title |> React.string} </span>
<Icon.DownArrow />
</button>
</Dropdown>;
};

View File

@ -0,0 +1,175 @@
/* O for option */
open Rationale.Function.Infix;
/* Utils */
module U = {
let isEqual = (a, b) => a == b;
let toA = a => [|a|];
let id = e => e;
};
module O = {
let dimap = (sFn, rFn, e) =>
switch (e) {
| Some(r) => sFn(r)
| None => rFn()
};
();
let fmap = Rationale.Option.fmap;
let bind = Rationale.Option.bind;
let default = Rationale.Option.default;
let isSome = Rationale.Option.isSome;
let toExn = Rationale.Option.toExn;
let some = Rationale.Option.some;
let flatApply = (fn, b) =>
Rationale.Option.apply(fn, Some(b)) |> Rationale.Option.flatten;
let toResult = (error, e) =>
switch (e) {
| Some(r) => Belt.Result.Ok(r)
| None => Error(error)
};
module React = {
let defaultNull = default(ReasonReact.null);
let fmapOrNull = fn => fmap(fn) ||> default(ReasonReact.null);
let flatten = default(ReasonReact.null);
};
};
module Bool = {
type t = bool;
let toString = (t: t) => t ? "TRUE" : "FALSE";
let fromString = str => str == "TRUE" ? true : false;
};
module Float = {
let toString = Js.Float.toFixed;
let with3DigitsPrecision = Js.Float.toPrecisionWithPrecision(_, ~digits=3);
};
module I = {
let increment = n => n + 1;
let decrement = n => n - 1;
};
let safe_fn_of_string = (fn, s: string): option('a) =>
try(Some(fn(s))) {
| _ => None
};
module S = {
let toReact = ReasonReact.string;
let safe_float = float_of_string->safe_fn_of_string;
let safe_int = int_of_string->safe_fn_of_string;
};
module JsDate = {
let fromString = Js.Date.fromString;
let now = Js.Date.now;
let make = Js.Date.make;
let valueOf = Js.Date.valueOf;
};
/* List */
module L = {
let fmap = List.map;
let toArray = Array.of_list;
let fmapi = List.mapi;
let concat = List.concat;
let find = List.find;
let filter = List.filter;
let for_all = List.for_all;
let exists = List.exists;
let sort = List.sort;
let length = List.length;
let filter_opt = Rationale.RList.filter_opt;
let uniqBy = Rationale.RList.uniqBy;
let join = Rationale.RList.join;
let head = Rationale.RList.head;
let uniq = Rationale.RList.uniq;
let flatten = List.flatten;
let last = Rationale.RList.last;
let append = List.append;
let getBy = Belt.List.getBy;
let dropLast = Rationale.RList.dropLast;
let contains = Rationale.RList.contains;
let without = Rationale.RList.without;
let update = Rationale.RList.update;
let iter = List.iter;
let findIndex = Rationale.RList.findIndex;
let withIdx = xs =>
xs
|> Rationale.RList.zip(
Rationale.RList.times(Rationale.Function.identity, length(xs)),
);
module React = {
let fmap = (f, xs) => xs |> fmap(f) |> toArray |> React.array;
let fmapi = (f, xs) => xs |> fmapi(f) |> toArray |> React.array;
};
};
/* A for Array */
module A = {
let fmap: ('a => 'b, array('a)) => array('b) = Array.map;
let fmapi = Array.mapi;
let to_list = Array.to_list;
let of_list = Array.of_list;
let length = Array.length;
let append = Array.append;
// let empty = [||];
let unsafe_get = Array.unsafe_get;
let get = Belt.Array.get;
let fold_left = Array.fold_left;
let fold_right = Array.fold_right;
let concatMany = Belt.Array.concatMany;
let keepMap = Belt.Array.keepMap;
let stableSortBy = Belt.SortArray.stableSortBy;
/* TODO: Is there a better way of doing this? */
/* TODO: Is -1 still the indicator that this is false (as is true with js findIndex)? Wasn't sure. */
let findIndex = (e, i) =>
Js.Array.findIndex(e, i)
|> (
r =>
switch (r) {
| (-1) => None
| r => Some(r)
}
);
let filter = (o, e) => Js.Array.filter(o, e);
module O = {
let concatSomes = (optionals: Js.Array.t(option('a))): Js.Array.t('a) =>
optionals
|> Js.Array.filter(Rationale.Option.isSome)
|> Js.Array.map(
Rationale.Option.toExn("Warning: This should not have happened"),
);
let concatSome = (optionals: array(option('a))): array('a) =>
optionals
|> Js.Array.filter(Rationale.Option.isSome)
|> Js.Array.map(
Rationale.Option.toExn("Warning: This should not have happened"),
);
let defaultEmpty = (o: option(array('a))): array('a) =>
switch (o) {
| Some(o) => o
| None => [||]
};
};
};
module JsArray = {
let concatSomes = (optionals: Js.Array.t(option('a))): Js.Array.t('a) =>
optionals
|> Js.Array.filter(Rationale.Option.isSome)
|> Js.Array.map(
Rationale.Option.toExn("Warning: This should not have happened"),
);
let filter = Js.Array.filter;
};
module FloatArray = {
let min = r => r |> A.fold_left((a, b) => a < b ? a : b, max_float);
let max = r => r |> A.fold_left((a, b) => a > b ? a : b, min_float);
};

View File

@ -0,0 +1,17 @@
let load = () => {
Css.(
global(
"body",
[
fontFamily(Settings.Text.standardFont),
margin(`zero),
height(`percent(100.0)),
background(`hex("F0F1F3")),
fontSize(`px(16)),
lineHeight(`em(1.5)),
],
)
);
Css.(global("html", [height(`percent(100.0))]));
};

View File

@ -0,0 +1,61 @@
module Questionmark = {
// Adapted to pagecard title
let backgroundBlue = Css.background(`hex("C0D0E9"));
let mainBlue = `hex("#1c67c8");
let circle =
Css.(
style([
width(`em(1.0)),
height(`em(1.0)),
borderRadius(`percent(50.)),
backgroundBlue,
])
);
let insideStyle =
Css.(style([color(mainBlue), cursor(`pointer), fontSize(`em(0.9))]));
// Local icon style
let questionMarkstyle = isInteractive =>
Css.(
style([
width(`em(1.0)),
height(`em(1.0)),
textAlign(`center),
borderRadius(`percent(50.)),
display(`inlineBlock),
backgroundBlue,
lineHeight(`em(1.0)),
fontSize(`em(1.0)),
opacity(0.6),
fontWeight(`num(600)),
cursor(`pointer),
hover(isInteractive ? [opacity(1.0)] : []),
])
);
[@react.component]
let make = (~isInteractive=true) =>
<div className={questionMarkstyle(isInteractive)}>
<span className=insideStyle> {React.string("?")} </span>
</div>;
};
module DownArrow = {
/* Down array from ant */
let buttonStyle = Css.(style([marginLeft(`px(8))]));
[@react.component]
let make = () =>
<svg
className=buttonStyle
viewBox="64 64 896 896"
width="0.8em"
height="0.8em"
fill="currentColor"
ariaHidden=true
focusable="false">
<path
d="M884 256h-75c-5.1 0-9.9 2.5-12.9 6.6L512 654.2 227.9 262.6c-3-4.1-7.8-6.6-12.9-6.6h-75c-6.5 0-10.3 7.4-6.5 12.7l352.6 486.1c12.8 17.6 39 17.6 51.7 0l352.6-486.1c3.9-5.3.1-12.7-6.4-12.7z"
/>
</svg>;
};

View File

@ -0,0 +1,29 @@
let headerLink = (~className, ~isDisabled=false, ()) => {
let primaryStyles =
Css.(
style([
textDecoration(`none),
userSelect(`none),
color(Settings.Text.LightBackground.main),
hover([color(Settings.Text.LightBackground.light)]),
focus([textDecoration(`none)]),
])
);
let disabledStyles =
isDisabled ? Css.(style([pointerEvents(`none), cursor(`default)])) : "";
Css.(merge([primaryStyles, className, disabledStyles]));
};
[@react.component]
let make =
(~href="#", ~onClick=?, ~isDisabled=false, ~className="", ~children) => {
<a
disabled=isDisabled
href
className={headerLink(~className, ~isDisabled, ())}
?onClick>
children
</a>;
};

View File

@ -0,0 +1,375 @@
/**
* Menu component and sibling components provides define menu items,
* submenues and dividers. See make for usage.
*
* It is binding to https://github.com/react-component/menu
*/
[@bs.module "rc-menu"]
external rcMenuClass: ReasonReact.reactClass = "default";
[@bs.module "rc-menu"]
external rcSubMenuClass: ReasonReact.reactClass = "SubMenu";
[@bs.module "rc-menu"] external rcItemClass: ReasonReact.reactClass = "Item";
[@bs.module "rc-menu"]
external rcDividerClass: ReasonReact.reactClass = "Divider";
[%bs.raw {|require("rc-menu/assets/index.css")|}];
module Styles = {
open Css;
let menuBorderRadius = `px(3);
// Menu general, applied to menu <ul> elements
global(
".ft-menu-general, .ft-submenu-general>ul",
[
fontFamily(Settings.Text.standardFont),
listStyleType(`none),
position(`relative),
outlineStyle(`none),
borderRadius(menuBorderRadius),
borderStyle(`none),
boxShadows([
Shadow.box(~x=zero, ~y=px(2), ~blur=px(8), rgba(0, 0, 0, 0.15)),
]),
margin(`zero),
padding2(~v=`px(4), ~h=`zero),
// This is applied to menu-items, submenu pointers, and dividers
selector(
">li",
[
// Used to place icon in submenu item currently
position(`relative),
display(`block),
clear(`both),
whiteSpace(`nowrap),
cursor(`default),
],
),
// Taken from default stylesheet, might prevent some small bug
// with border radius
selector(
">li.ft-menu-item-general:first-child",
[
borderTopLeftRadius(menuBorderRadius),
borderTopRightRadius(menuBorderRadius),
],
),
],
);
// Applied to div of submenu
global(
".ft-submenu-general",
[position(`absolute), minWidth(`px(100))],
);
/**
* Create style for menu tied to a "prefixCls"
* This function injects global styles for
* classNames based on prefixCls. It is meant
* to be called only once per prefix, for example
* on a module level.
* For an example, see DropdownMenu
*/
let createStyle =
(
~prefixCls,
~itemVerticalPadding,
~itemHorizontalPadding,
~textColor,
~bgColor,
~hoverColor,
~textSize,
~textLineHeight,
(),
) => {
let prefixPlus = ext => "." ++ prefixCls ++ "-" ++ ext;
// Root
// Dropdown root element. This, like submenu popups, are placed
// in generated divs right inside document body element. Ie only
// influenced by top level styles.
// Note that submenus get their own top level element, so this
// does not apply to submenus
// Note: There is a style in Dropdown that takes care
// of position absolute and zindex
//global("." ++ prefixCls, []);
// Menu ul
// <ul> element that contain the menu items in it's <li>'s
// Both root menu and submenus have this class.
// This ul element will receive the -hidden class when hidden
// Move from Menu.Styles > "ft-menu-general" when styles
// need variation
global(
"ul" ++ prefixPlus("menu"),
[
lineHeight(textLineHeight),
backgroundColor(bgColor),
// Menu-item
selector(
"li" ++ prefixPlus("menu-item"),
[
fontSize(textSize),
color(textColor),
selector(":hover", [backgroundColor(hoverColor)]),
padding2(
~v=`px(itemVerticalPadding),
~h=`px(itemHorizontalPadding),
),
// Reversing outside element padding and adding it on the <a>
// Not sure how needed this will be (taken from ant)
selector(
">a",
[
display(`block),
margin2(
~v=`px(- itemVerticalPadding),
~h=`px(- itemHorizontalPadding),
),
padding2(
~v=`px(itemVerticalPadding),
~h=`px(itemHorizontalPadding),
),
],
),
],
),
// Submenu <li> pointer
selector(
">li" ++ prefixPlus("menu-submenu"),
[
fontSize(textSize),
color(textColor),
selector(":hover", [backgroundColor(hoverColor)]),
],
),
selector(
">li" ++ prefixPlus("menu-submenu-active"),
[backgroundColor(hoverColor)],
),
// Divider
// Can't move to general because the selector
// is prefix specific
selector(
">li" ++ prefixPlus("menu-item-divider"),
[
margin2(~v=`px(4), ~h=`zero),
height(`px(1)),
backgroundColor(`hex("e5e5e5")), // A little lighter than border I think
lineHeight(`zero),
overflow(`hidden),
padding(`zero),
],
),
],
);
// Hidden menu
// Class applied to submenu <ul>'s when menu is hidden. Elements are not initially
// hidden, but added to the dom on demand. Therefore it is quirky to rely
// on this class with regards to animations/transitions.
// For transitions, see api of rc-dropdown/rc-menu
global(prefixPlus("menu-hidden"), [display(`none)]);
// Hidden root menu
global(prefixPlus("hidden"), [display(`none)]);
// Submenu
// Submenu has a title element inside a div, and
// a right arrow added with unicode
global(
prefixPlus("menu-submenu-title"),
[
padding4(
~top=`px(itemVerticalPadding),
~right=`px(itemHorizontalPadding * 2 + 5),
~bottom=`px(itemVerticalPadding),
~left=`px(itemHorizontalPadding),
),
],
);
global(
prefixPlus("menu-submenu-arrow"),
[
position(`absolute),
right(`px(itemHorizontalPadding)),
selector(
"::before",
[
color(Settings.textMedium),
fontStyle(`normal),
fontSize(`em(0.7)),
// One option here is to use font-awesome,
// don't know how much use it would be elsewhere or how to do this,
// rc-menu does this by default
contentRule({js|\u25B6|js}),
],
),
],
);
};
};
module SubMenu = {
// https://github.com/react-component/menu#menusubmenu-props
[@bs.deriving abstract]
type jsProps = {
title: string,
popupClassName: string,
};
[@react.component]
let make = (~title, ~children) =>
ReasonReact.wrapJsForReason(
~reactClass=rcSubMenuClass,
~props=jsProps(~title, ~popupClassName="ft-submenu-general"),
children,
)
|> ReasonReact.element;
};
module Item = {
// https://github.com/react-component/menu#menuitem-props
[@bs.deriving abstract]
type jsProps = {
disabled: bool,
itemIcon: Js.Undefined.t(React.element),
className: string,
};
[@react.component]
let make = (~disabled=false, ~itemIcon=None, ~children) =>
ReasonReact.wrapJsForReason(
~reactClass=rcItemClass,
~props=
jsProps(
~disabled,
~itemIcon=Js.Undefined.fromOption(itemIcon),
~className="ft-menu-item-general",
),
children,
)
|> ReasonReact.element;
};
module Divider = {
// https://github.com/react-component/menu#menuitem-props
[@react.component]
let make = (~children=ReasonReact.null) =>
ReasonReact.wrapJsForReason(
~reactClass=rcDividerClass,
~props=Js.Obj.empty(),
children,
)
|> ReasonReact.element;
};
type callbackType =
Js.Undefined.t(
{
.
"key": string,
"item": React.element,
"domEvent": Dom.event,
"keyPath": array(string),
} =>
unit,
);
// https://github.com/react-component/menu#menu-props
[@bs.deriving abstract]
type jsProps = {
// Gave some namespace confusion on some call site
// Possibly we could slim the callbacks a little
// if there are unneeded arguments
[@bs.as "onClick"]
jsOnClick: callbackType,
selectable: bool,
selectedKeys: array(string),
[@bs.as "onSelect"]
jsOnSelect: callbackType,
className: string,
};
type clickInfo = {
key: string,
item: React.element,
domEvent: Dom.event,
keyPath: array(string),
};
/**
* Menu component and sibling components provides a way to define menu items,
* submenues and dividers. One can register an onClick callback on the
* <Menu> element to receive information about clicks.
*
* To resolve which menu item was clicked, the best way is probably
* to provide a key attribute to the items, then use the key in
* the callback. There is also a keyPath list with parents keys
* in the case of a submenu, the dom Event and the react element
* that was clicked.
*
* The menu could also act more like a select element, as implemented
* in DropdownSelect.
*
* Usage:
* ```
* Menu.(
* <Menu onClick={info => Js.log(info.key)}>
* <Item key="item1"> { "Item 1" |> React.string } </Item>
* <SubMenu title="Submenu">
* <Item key="subitem1"> { "Subitem 1" |> React.string } </Item>
* <Divider />
* <Item key="subitem2"> { "Subitem 2" |> React.string } </Item>
* </SubMenu>
* </Menu>
* )
* ```
*/
[@react.component]
let make =
(~onClick=?, ~selectable=false, ~onSelect=?, ~selectedKey=?, ~children) =>
ReasonReact.wrapJsForReason(
~reactClass=rcMenuClass,
~props=
jsProps(
~jsOnClick=
switch (onClick) {
| Some(onClick) =>
Js.Undefined.return(info =>
onClick({
key: info##key,
item: info##item,
domEvent: info##domEvent,
keyPath: info##keyPath,
})
)
| None => Js.Undefined.empty
},
~selectable,
~jsOnSelect=
switch (onSelect) {
| Some(onSelect) =>
Js.Undefined.return(info =>
onSelect({
key: info##key,
item: info##item,
domEvent: info##domEvent,
keyPath: info##keyPath,
})
)
| None => Js.Undefined.empty
},
~selectedKeys=
switch (selectedKey) {
| None => [||]
| Some(key) => [|key|]
},
~className="ft-menu-general",
),
children,
) /* Menu item group is not implemented (https://react-component.github.io/menu/examples/menuItemGroup.html*/
|> ReasonReact.element;

View File

@ -0,0 +1,127 @@
/** @jsx React.DOM */
import React from 'react';
import {
Icon
} from 'react-icons-kit';
import {
home
} from 'react-icons-kit/typicons/home';
import {
user
} from 'react-icons-kit/ikons/user';
import {
arrowLeft2
} from 'react-icons-kit/icomoon/arrowLeft2';
import {
arrowRight2
} from 'react-icons-kit/icomoon/arrowRight2';
import {
earth
} from 'react-icons-kit/icomoon/earth';
import {
columns
} from 'react-icons-kit/ikons/columns';
import {
ic_people
} from 'react-icons-kit/md/ic_people';
import {
bulb
} from 'react-icons-kit/entypo/bulb';
import {
socialBuffer
} from 'react-icons-kit/ionicons/socialBuffer';
import {
flash
} from 'react-icons-kit/entypo/flash';
import {
gavel
} from 'react-icons-kit/fa/gavel';
import {
plus as circlePlus
} from 'react-icons-kit/metrize/plus';
import {
chevronRight
} from 'react-icons-kit/ionicons/chevronRight';
import {
chevronLeft
} from 'react-icons-kit/ionicons/chevronLeft';
import {
lock
} from 'react-icons-kit/entypo/lock';
import {
chevronDown
} from 'react-icons-kit/fa/chevronDown';
import {
emailUnread
} from 'react-icons-kit/ionicons/emailUnread';
import {
u26FA as tent
} from 'react-icons-kit/noto_emoji_regular/u26FA';
import {
ic_content_copy
} from 'react-icons-kit/md/ic_content_copy';
import {
magicWand
} from 'react-icons-kit/icomoon/magicWand';
import {
pacman
} from 'react-icons-kit/icomoon/pacman';
import {
thList as list
} from 'react-icons-kit/typicons/thList';
import {
starFull
} from 'react-icons-kit/icomoon/starFull';
import {
arrowBack
} from 'react-icons-kit/typicons/arrowBack';
import {
reply
} from 'react-icons-kit/fa/reply';
import {
close
} from 'react-icons-kit/fa/close';
let types = {
'HOME': home,
'LOCK': lock,
'USER': user,
'COLUMNS': columns,
'EARTH': earth,
'TENT': tent,
'LAYERS': socialBuffer,
'PEOPLE': ic_people,
'FLASH': flash,
'GAVEL': gavel,
'BULB': bulb,
'CIRCLE_PLUS': circlePlus,
'ARROW_RIGHT': arrowRight2,
'ARROW_LEFT': arrowLeft2,
'CHEVRON_LEFT': chevronLeft,
'CHEVRON_RIGHT': chevronRight,
'CHEVRON_DOWN': chevronDown,
'EMAIL_UNREAD': emailUnread,
'COPY': ic_content_copy,
'MAGIC_WAND': magicWand,
'PACMAN': pacman,
'LIST': list,
'STAR_FULL': starFull,
'ARROW_BACK': arrowBack,
'REPLY': reply,
'CLOSE': close,
};
export class ReactKitIcon extends React.Component {
render() {
const {
iconType,
size,
className
} = this.props;
return React.createElement(Icon, {
size: size,
icon: types[iconType],
className: className
});
}
}

View File

@ -0,0 +1,15 @@
[@bs.module "./ReactKitIcon.js"]
external reactClass: ReasonReact.reactClass = "ReactKitIcon";
[@react.component]
let make = (~icon=?, ~size=?, ~className=?, ~children=ReasonReact.null) =>
ReasonReact.wrapJsForReason(
~reactClass,
~props={
"iconType": icon |> E.O.default(""),
"size": size |> E.O.default("1em"),
"className": className,
},
children,
)
|> ReasonReact.element;

View File

@ -0,0 +1,93 @@
/* this is used to show hex color; */
let removeHex = Js.String.sliceToEnd(~from=1);
let r = c => c->removeHex->(e => `hex(e));
type col = [ | `hex(Js.String.t)];
let toS = (col: col) =>
switch (col) {
| `hex(c) => c
};
let white = "#FFFFFF"->r;
let black = "#000000"->r;
let greyO4 = "#00000044"->r;
let whiteO2 = "#ffffff20"->r;
let whiteO4 = "#ffffff40"->r;
let whiteOc = "#ffffffc0"->r;
let clear = "#00000000"->r;
let textDarker = "#333333"->r;
let textDark = "#5f6d7d"->r;
let textMedium = "#9a9ea7"->r;
let smokeWhite = "#F0F1F3"->r;
let buttonHover = "#e4ecf5"->r;
let lightGrayBackground = "#f4f6f9"->r;
let lighterGrayBackground = "#fbfcfd"->r;
let grayBackground = "#dcdee0"->r;
let greydisabled = "#e3e4e6"->r;
let accentBlue = "#8C9EB5"->r;
let accentBlueO8 = "#8C9EB540"->r;
let accentBlue1a = "#8c9eb530"->r;
let mainBlue = "#347296"->r;
let link = "#4a72b7"->r;
let linkHover = "#375ea1"->r;
let linkAccent = "#437bff"->r;
let darkLink = "#1a2e45"->r;
let darkAccentBlue = "#5C6E95"->r;
let grey1 = "#868686"->r;
let border = "#D5D7DA"->r;
let primary = mainBlue;
module FontWeights = {
let light = Css.fontWeight(`num(300));
let regular = Css.fontWeight(`num(400));
let heavy = Css.fontWeight(`num(700));
let veryHeavy = Css.fontWeight(`num(900));
};
module Transitions = {
let standardLength = 100;
};
module BorderRadius = {
let medium = `px(4);
let tight = `px(2);
};
module Statuses = {
let green = "#81a952"->r;
let yellow = "#C09C66"->r;
let resolved = accentBlue;
};
module Alert = {
type t = [ | `primary | `info | `success | `warning | `error];
let color = (t: t) =>
switch (t) {
| `primary => "#004085"->r
| `info => "#0c5460"->r
| `success => "#155724"->r
| `warning => "#856404"->r
| `error => "#721c24"->r
};
let background = (t: t) =>
switch (t) {
| `primary => "#cce5ff"->r
| `info => "#d1ecf1"->r
| `success => "#d4edda"->r
| `warning => "#fff3cd"->r
| `error => "#f8d7da"->r
};
};
module Text = {
module LightBackground = {
let main = textDark;
let p = "#3c3c3c"->r;
let light = accentBlue;
let active = "#0C5CD9"->r;
};
let standardFont = "Lato";
};

View File

@ -0,0 +1,20 @@
[@react.component]
let make = (~selected, ~flex=false, ~onClick=?, ~list) =>
list
|> E.L.React.fmapi((i, (key, label)) =>
<Tab
key={string_of_int(i)}
onClick={e =>
switch (onClick) {
| Some(onClick) =>
e->ReactEvent.Synthetic.preventDefault;
onClick(key);
| None => ()
}
}
isActive={selected == key}
flex>
label->React.string
</Tab>
);

View File

@ -0,0 +1,78 @@
module Dist = {
type t = {
xs: array(float),
ys: array(float),
};
type asJson = {
.
"xs": array(float),
"ys": array(float),
};
let hasLength = (t: t): bool => t.xs |> E.A.length > 0;
let empty: t = {xs: [||], ys: [||]};
let toJson = (t: t): asJson => {"xs": t.xs, "ys": t.ys};
let fromJson = (json: asJson): t => {xs: json##xs, ys: json##ys};
module JS = {
[@bs.deriving abstract]
type distJs = {
xs: array(float),
ys: array(float),
};
let fromDist = (d: t) => distJs(~xs=d.xs, ~ys=d.ys);
let toDist = (d: distJs) => {xs: xsGet(d), ys: ysGet(d)};
let doAsDist = (f, d: t) => d |> fromDist |> f |> toDist;
[@bs.module "./stats.js"] external cdfToPdf: distJs => distJs = "cdfToPdf";
[@bs.module "./stats.js"]
external findY: (float, distJs) => float = "findY";
[@bs.module "./stats.js"]
external findX: (float, distJs) => float = "findX";
[@bs.module "./stats.js"] external mean: array(distJs) => distJs = "mean";
[@bs.module "./stats.js"]
external distributionScoreDistribution: array(distJs) => distJs =
"distributionScoreDistribution";
[@bs.module "./stats.js"]
external distributionScoreNumber: array(distJs) => float =
"distributionScoreNumber";
[@bs.module "./stats.js"] external integral: distJs => float = "integral";
};
let toPdf = (dist: t) => dist |> JS.doAsDist(JS.cdfToPdf);
let requireLength = (dist: t) => dist |> hasLength ? Some(dist) : None;
let mean = (dists: array(t)) =>
JS.mean(dists |> Array.map(JS.fromDist)) |> JS.toDist;
let distributionScoreDistribution = (dists: array(t)) => {
JS.distributionScoreDistribution(dists |> Array.map(JS.fromDist))
|> JS.toDist
|> requireLength;
};
let distributionScoreNumber = (dists: array(t)) => {
JS.distributionScoreNumber(dists |> Array.map(JS.fromDist));
};
let findX = (y: float, dist: t) => dist |> JS.fromDist |> JS.findX(y);
let findY = (x: float, dist: t) => dist |> JS.fromDist |> JS.findY(x);
let integral = (dist: t) => dist |> JS.fromDist |> JS.integral;
};
module Dists = {
type t = array(Dist.t);
let minX = (x: float, dists: t) =>
dists |> Array.map(Dist.findX(x)) |> E.FloatArray.min;
let maxX = (x: float, dists: t) =>
dists |> Array.map(Dist.findX(x)) |> E.FloatArray.max;
};

View File

@ -0,0 +1,69 @@
import {
Pdf,
Cdf,
ContinuousDistribution,
ContinuousDistributionCombination,
scoringFunctions
} from '@foretold/cdf';
function cdfToPdf({ xs, ys }) {
let cdf = new Cdf(xs, ys);
let pdf = cdf.toPdf();
return { xs: pdf.xs, ys: pdf.ys };
}
function mean(vars) {
let cdfs = vars.map(r => new Cdf(r.xs, r.ys));
let comb = new ContinuousDistributionCombination(cdfs);
let newCdf = comb.combineYsWithMean(10000);
return { xs: newCdf.xs, ys: newCdf.ys };
}
function distributionScoreDistribution(vars) {
let cdfs = vars.map(r => (new Cdf(r.xs, r.ys)));
let newDist = scoringFunctions.distributionInputDistributionOutputDistribution({
predictionCdf: cdfs[0],
aggregateCdf: cdfs[1],
resultCdf: cdfs[2],
sampleCount: 10000
});
let newCdf = (new Pdf(newDist.xs, newDist.ys)).toCdf();
return { xs: newCdf.xs, ys: newCdf.ys };
}
function distributionScoreNumber(vars) {
let cdfs = vars.map(r => (new Cdf(r.xs, r.ys)));
return scoringFunctions.distributionInputDistributionOutput({
predictionCdf: cdfs[0],
aggregateCdf: cdfs[1],
resultCdf: cdfs[2],
sampleCount: 10000,
});
}
function findY(x, { xs, ys }) {
let cdf = new Cdf(xs, ys);
let result = cdf.findY(x);
return result;
}
function findX(y, { xs, ys }) {
let cdf = new Cdf(xs, ys);
let result = cdf.findX(y);
return result;
}
function integral({ xs, ys }) {
let distribution = new ContinuousDistribution(xs, ys);
return distribution.integral();
}
module.exports = {
cdfToPdf,
findY,
findX,
mean,
distributionScoreDistribution,
distributionScoreNumber,
integral
};

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,45 @@
[@bs.module "./cdfChart.js"]
external cdfChart: ReasonReact.reactClass = "default";
type data = {
.
"xs": array(float),
"ys": array(float),
};
[@react.component]
let make =
(
~width=?,
~height=?,
~verticalLine=?,
~showVerticalLine=?,
~marginBottom=?,
~marginTop=?,
~showDistributionLines=?,
~maxX=?,
~minX=?,
~onHover=?,
~primaryDistribution=?,
~children=[||],
) =>
ReasonReact.wrapJsForReason(
~reactClass=cdfChart,
~props=
makeProps(
~width?,
~height?,
~verticalLine?,
~marginBottom?,
~marginTop?,
~onHover?,
~showVerticalLine?,
~showDistributionLines?,
~maxX?,
~minX?,
~primaryDistribution?,
(),
),
children,
)
|> ReasonReact.element;

View File

@ -0,0 +1,29 @@
module Styles = {
open Css;
let graph =
style([
selector(".axis", [fontSize(`px(11))]),
selector(".domain", [display(`none)]),
selector(".tick line", [display(`none)]),
selector(".tick text", [color(`hex("2e4c65"))]),
selector(".chart .area-path", [SVG.fill(`hex("7e9db7"))]),
]);
};
[@react.component]
let make = (~cdf: Types.Dist.t, ~minX=?, ~maxX=?, ~width=Some(400)) => {
let pdf = cdf |> Types.Dist.toPdf;
<div className=Styles.graph>
<CdfChart__Base
marginBottom=25
?width
height=200
?maxX
?minX
showVerticalLine=false
showDistributionLines=false
primaryDistribution={"xs": pdf.xs, "ys": pdf.ys}
onHover={_r => ()}
/>
</div>;
};

View File

@ -0,0 +1,37 @@
module Styles = {
open Css;
let textOverlay = style([position(`absolute)]);
let mainText = style([fontSize(`em(1.1)), color(Settings.darkLink)]);
let secondaryText =
style([fontSize(`em(0.9)), color(Settings.accentBlue)]);
let graph = chartColor =>
style([
position(`relative),
selector(".axis", [fontSize(`px(9))]),
selector(".domain", [display(`none)]),
selector(".tick line", [display(`none)]),
selector(".tick text", [color(`hex("bfcad4"))]),
selector(".chart .area-path", [SVG.fill(chartColor)]),
]);
};
[@react.component]
let make = (~cdf: Types.Dist.t, ~minX, ~maxX, ~color=`hex("3562AE66")) => {
let pdf = cdf |> Types.Dist.toPdf;
<div className={Styles.graph(color)}>
<CdfChart__Base
width=200
height=30
minX
maxX
marginBottom=15
marginTop=0
showVerticalLine=false
showDistributionLines=false
primaryDistribution={"xs": pdf.xs, "ys": pdf.ys}
onHover={_r => ()}
/>
</div>;
};

View File

@ -0,0 +1,46 @@
module Styles = {
open Css;
let textOverlay = style([position(`absolute)]);
let mainText = style([fontSize(`em(1.1)), color(Settings.darkLink)]);
let secondaryText =
style([fontSize(`em(0.9)), color(Settings.accentBlue)]);
let graph = chartColor =>
style([
position(`relative),
selector(".axis", [fontSize(`px(9))]),
selector(".domain", [display(`none)]),
selector(".tick line", [display(`none)]),
selector(".tick text", [color(`hex("bfcad4"))]),
selector(".chart .area-path", [SVG.fill(chartColor)]),
]);
};
[@react.component]
let make =
(
~cdf: Types.Dist.t,
~minX=None,
~maxX=None,
~width=200,
~height=40,
~color=`hex("7e9db7"),
) => {
let pdf = cdf |> Types.Dist.toPdf;
<div className={Styles.graph(color)}>
<div className=Styles.textOverlay> <CdfChart__StatSummary cdf /> </div>
<CdfChart__Base
width
height
?minX
?maxX
marginBottom=0
marginTop=0
showVerticalLine=false
showDistributionLines=false
primaryDistribution={"xs": pdf.xs, "ys": pdf.ys}
onHover={_r => ()}
/>
</div>;
};

View File

@ -0,0 +1,31 @@
module Styles = {
open Css;
let textOverlay = style([position(`absolute)]);
let mainText = style([fontSize(`em(1.1)), color(Settings.darkLink)]);
let secondaryText =
style([fontSize(`em(0.9)), color(Settings.accentBlue)]);
};
[@react.component]
let make = (~cdf: Types.Dist.t, ~showMean=true) =>
<>
{showMean
? <div className=Styles.mainText>
<NumberShower
precision=2
number={cdf |> Types.Dist.findX(0.5)}
/>
</div>
: ReasonReact.null}
<div className=Styles.secondaryText>
<NumberShower
precision=2
number={cdf |> Types.Dist.findX(0.05)}
/>
{" to " |> ReasonReact.string}
<NumberShower
precision=2
number={cdf |> Types.Dist.findX(0.95)}
/>
</div>
</>;

View File

@ -0,0 +1,65 @@
import React, { useEffect } from 'react';
import { useSize } from 'react-use';
import chart from './cdfChartd3';
/**
* @param min
* @param max
* @returns {number}
*/
function getRandomInt(min, max) {
min = Math.ceil(min);
max = Math.floor(max);
return Math.floor(Math.random() * (max - min + 1)) + min;
}
/**
* Example input:
* {
* xs: [50,100,300,400,500,600],
* ys: [0.1, 0.4, 0.6, 0.7,0.8, 0.9]}
* }
*/
function CdfChart(props) {
const id = "chart-" + getRandomInt(0, 100000);
const [sized, { width }] = useSize(() => {
return React.createElement("div", {
key: "resizable-div",
});
}, {
width: props.width,
});
useEffect(() => {
chart()
.svgWidth(width)
.svgHeight(props.height)
.maxX(props.maxX)
.minX(props.minX)
.marginBottom(props.marginBottom || 15)
.marginLeft(5)
.marginRight(5)
.marginTop(5)
.showDistributionLines(props.showDistributionLines)
.verticalLine(props.verticalLine)
.showVerticalLine(props.showVerticalLine)
.container("#" + id)
.data({ primary: props.primaryDistribution }).render();
});
const style = !!props.width ? { width: props.width + "px" } : {};
const key = id;
return React.createElement("div", {
style: {
paddingLeft: "10px",
paddingRight: "10px",
},
}, [
sized,
React.createElement("div", { id, style, key }),
]);
}
export default CdfChart;

View File

@ -0,0 +1,303 @@
import * as d3 from 'd3';
function chart() {
// Id for event handlings.
var attrs = {
id: 'ID' + Math.floor(Math.random() * 1000000),
svgWidth: 400,
svgHeight: 400,
marginTop: 5,
marginBottom: 5,
marginRight: 5,
marginLeft: 5,
container: 'body',
minX: false,
maxX: false,
scale: 'linear',
showDistributionLines: true,
areaColors: ['#E1E5EC', '#E1E5EC'],
logBase: 10,
verticalLine: 110,
showVerticalLine: true,
data: null,
onHover: (e) => {
},
};
var main = function main() {
// Drawing containers.
var container = d3.select(attrs.container);
if (container.node() === null) {
return;
}
var containerRect = container.node().getBoundingClientRect();
if (containerRect.width > 0) {
attrs.svgWidth = containerRect.width;
}
// Calculated properties.
// id for event handlings.
var calc = {};
calc.id = 'ID' + Math.floor(Math.random() * 1000000);
calc.chartLeftMargin = attrs.marginLeft;
calc.chartTopMargin = attrs.marginTop;
calc.chartWidth = attrs.svgWidth - attrs.marginRight - attrs.marginLeft;
calc.chartHeight = attrs.svgHeight - attrs.marginBottom - attrs.marginTop;
var areaColor = d3.scaleOrdinal().range(attrs.areaColors);
var dataPoints = [getDatapoints('primary')];
// Scales.
var xScale;
var xMin = d3.min(attrs.data.primary.xs);
var xMax = d3.max(attrs.data.primary.xs);
if (attrs.scale === 'linear') {
xScale = d3.scaleLinear()
.domain([
attrs.minX || xMin,
attrs.maxX || xMax
])
.range([0, calc.chartWidth]);
} else {
xScale = d3.scaleLog()
.base(attrs.logBase)
.domain([
attrs.minX,
attrs.maxX,
])
.range([0, calc.chartWidth]);
}
var yMin = d3.min(attrs.data.primary.ys);
var yMax = d3.max(attrs.data.primary.ys);
var yScale = d3.scaleLinear()
.domain([
yMin,
yMax,
])
.range([calc.chartHeight, 0]);
// Axis generator.
var xAxis = d3.axisBottom(xScale)
.ticks(3)
.tickFormat(d => {
if (Math.abs(d) < 1) {
return d3.format(".2")(d);
} else if (xMin > 1000 && xMax < 3000) {
// Condition which identifies years; 2019, 2020, 2021.
return d3.format(".0")(d);
} else {
var prefix = d3.formatPrefix(".0", d);
var output = prefix(d);
return output.replace("G", "B");
}
});
// Line generator.
var line = d3.line()
.x(function (d, i) {
return xScale(d.x);
})
.y(function (d, i) {
return yScale(d.y);
});
var area = d3.area()
.x(function (d, i) {
return xScale(d.x);
})
.y1(function (d, i) {
return yScale(d.y);
})
.y0(calc.chartHeight);
// Add svg.
var svg = container
.patternify({ tag: 'svg', selector: 'svg-chart-container' })
.attr('width', "100%")
.attr('height', attrs.svgHeight)
.attr('pointer-events', 'none');
// Add container g element.
var chart = svg
.patternify({ tag: 'g', selector: 'chart' })
.attr(
'transform',
'translate(' + calc.chartLeftMargin + ',' + calc.chartTopMargin + ')',
);
// Add axis.
chart.patternify({ tag: 'g', selector: 'axis' })
.attr('transform', 'translate(' + 0 + ',' + calc.chartHeight + ')')
.call(xAxis);
// Draw area.
chart
.patternify({
tag: 'path',
selector: 'area-path',
data: dataPoints
})
.attr('d', area)
.attr('fill', (d, i) => areaColor(i))
.attr('opacity', (d, i) => i === 0 ? 0.7 : 1);
// Draw line.
if (attrs.showDistributionLines) {
chart
.patternify({
tag: 'path',
selector: 'line-path',
data: dataPoints
})
.attr('d', line)
.attr('id', (d, i) => 'line-' + (i + 1))
.attr('opacity', (d, i) => {
return i === 0 ? 0.7 : 1
})
.attr('fill', 'none');
}
if (attrs.showVerticalLine) {
chart.patternify({ tag: 'line', selector: 'v-line' })
.attr('x1', xScale(attrs.verticalLine))
.attr('x2', xScale(attrs.verticalLine))
.attr('y1', 0)
.attr('y2', calc.chartHeight)
.attr('stroke-width', 1.5)
.attr('stroke-dasharray', '6 6')
.attr('stroke', 'steelblue');
}
var hoverLine = chart.patternify({ tag: 'line', selector: 'hover-line' })
.attr('x1', 0)
.attr('x2', 0)
.attr('y1', 0)
.attr('y2', calc.chartHeight)
.attr('opacity', 0)
.attr('stroke-width', 1.5)
.attr('stroke-dasharray', '6 6')
.attr('stroke', '#22313F');
// Add drawing rectangle.
chart.patternify({ tag: 'rect', selector: 'mouse-rect' })
.attr('width', calc.chartWidth)
.attr('height', calc.chartHeight)
.attr('fill', 'transparent')
.attr('pointer-events', 'all')
.on('mouseover', mouseover)
.on('mousemove', mouseover)
.on('mouseout', mouseout);
function mouseover() {
var mouse = d3.mouse(this);
hoverLine.attr('opacity', 1)
.attr('x1', mouse[0])
.attr('x2', mouse[0]);
var range = [
xScale(dataPoints[dataPoints.length - 1][0].x),
xScale(
dataPoints
[dataPoints.length - 1]
[dataPoints[dataPoints.length - 1].length - 1].x,
),
];
var xValue = xScale.invert(mouse[0]).toFixed(2);
if (mouse[0] > range[0] && mouse[0] < range[1]) {
attrs.onHover(xValue);
} else {
attrs.onHover(false);
}
}
function mouseout() {
hoverLine.attr('opacity', 0)
}
/**
* @param key
* @returns {[]}
*/
function getDatapoints(key) {
var dt = [];
var data = attrs.data[key];
var len = data.xs.length;
for (let i = 0; i < len; i++) {
dt.push({
x: data.xs[i],
y: data.ys[i]
})
}
return dt;
}
};
d3.selection.prototype.patternify = function patternify(params) {
var container = this;
var selector = params.selector;
var elementTag = params.tag;
var data = params.data || [selector];
// Pattern in action.
var selection = container.selectAll('.' + selector).data(data, (d, i) => {
if (typeof d === 'object') {
if (d.id) {
return d.id;
}
}
return i;
});
selection.exit().remove();
selection = selection.enter().append(elementTag).merge(selection);
selection.attr('class', selector);
return selection;
};
// @todo: Do not do like that.
// Dynamic keys functions.
// Attach variables to main function.
Object.keys(attrs).forEach((key) => {
main[key] = function (_) {
if (!arguments.length) {
return attrs[key];
}
attrs[key] = _;
return main;
};
});
//Set attrs as property.
main.attrs = attrs;
//Exposed update functions.
main.data = function data(value) {
if (!arguments.length) return attrs.data;
attrs.data = value;
return main;
};
// Run visual.
main.render = function render() {
main();
return main;
};
return main;
}
export default chart;

View File

@ -0,0 +1,23 @@
module Base = Base;
module AppHeader = AppHeader;
module GroupHeader = GroupHeader;
module PageCard = PageCard;
module PaginationButtons = PaginationButtons;
module Tab = Tab;
module Tab2 = Tab2;
module TabButton = Tab.Button;
module Table = Table;
module Footer = Footer;
module StateStatus = StateStatus;
module MeasurableForm = MeasurableForm;
module NumberShower = NumberShower;
module Button = Button;
module HelpDropdown = HelpDropdown;
module MyCommunities = MyCommunities;
module Charts = {
module Large = CdfChart__Large;
module Plain = CdfChart__Plain;
module Small = CdfChart__Small;
};

View File

@ -0,0 +1,109 @@
open Base;
let str = ReasonReact.string;
module Styles = {
open Css;
/* PaddingTop is applied to copyright and link items to get
space between lines when wrapped.
Both also have horizontal margin to ensure minimum space between items,
this is then subtracted from at the edges of the "sections"-class. */
let itemsPaddingTop = `em(1.2);
let betweenItemsMargin = 0.8;
/* Layout box for spacing in the page */
let layoutBox =
style(
[margin2(~v=`em(2.), ~h=`zero), padding2(~v=`zero, ~h=`em(2.))]
@ BaseStyles.fullWidthFloatLeft,
);
/* footerBox inside layoutBox, here the horizontal border is in line with the text.
PaddingTop is added here instead of "itemsPaddingTop" to decrease line distance
when wrap and make items appear more as a group */
let footerBox =
style([
borderTop(`px(1), `solid, Colors.border),
paddingTop(`em(0.3)),
fontSize(`em(0.9)),
fontWeight(`bold),
color(Colors.textMedium),
]);
/* Sections enables wrapping of left and right section */
let sections =
style([
display(`flex),
flexWrap(`wrapReverse),
justifyContent(`spaceBetween),
margin2(~v=`zero, ~h=`em(-. betweenItemsMargin)),
]);
let element =
style([
flexGrow(10.),
paddingTop(itemsPaddingTop),
paddingRight(`em(0.2)),
margin2(~v=`zero, ~h=`em(betweenItemsMargin)),
]);
/* Some extra marginBottom is added to reinforce the links as
a group vs copyright.
The element grows a little bit to add some spacing for larger screens */
let items =
style([
display(`flex),
flexWrap(`wrap),
flexGrow(1.),
media("(min-width: 720px)", [justifyContent(`spaceBetween)]),
listStyleType(`none),
padding(`zero),
margin(`zero),
marginBottom(`em(1.2)),
selector(
"li",
[
paddingTop(itemsPaddingTop),
margin2(~v=`zero, ~h=`em(betweenItemsMargin)),
selector(
"a",
[
textDecoration(`none),
color(Colors.textMedium),
paddingBottom(`px(1)),
selector(
":hover",
[
color(Colors.linkAccent),
borderBottom(`px(1), `solid, Colors.linkAccent),
],
),
],
),
],
),
]);
};
/**
* Shows a footer with an element (ie. name and copyright), and a list of items
*/
[@react.component]
let make =
(
~logo: ReasonReact.reactElement,
~links: array(ReasonReact.reactElement),
) =>
<div className=Styles.layoutBox>
<div className=Styles.footerBox>
<div className=Styles.sections>
<div className=Styles.element> logo </div>
<ul className=Styles.items>
{links->Belt.Array.mapWithIndex((index, item) =>
<li key={"footer-li-" ++ string_of_int(index)}> item </li>
)
|> ReasonReact.array}
</ul>
</div>
</div>
</div>;

View File

@ -0,0 +1,60 @@
module E = E;
/**
* Dropdown select
* Usage
* <DropdownSelect.String
* selected=Some("key2")
* values=[("key1", "Label1"), ("key2", "Label2")]
* onSelect={v =>
* switch (v) {
* | Some(k) => Js.log2("Selected: ", k)
* | None => Js.log("Selected none")
* }
* }
* />
*
*/
[@react.component]
let make =
(
~initialValue,
~values: list(('a, string)),
~onSelect: option(option('a) => unit)=?,
~trigger=Dropdown.Click,
) => {
let initLabel = "";
let (label, setLabel) = React.useState(() => initLabel);
<DropdownMenu title=label trigger>
<Menu
selectable=true
onSelect={(info: Menu.clickInfo) =>
switch (
(values |> E.L.withIdx)
->(E.L.getBy(((i, _)) => info.key == "key" ++ string_of_int(i)))
) {
| Some((_i, (key, label))) =>
setLabel(_ => label);
switch (onSelect) {
| Some(onSelect) => onSelect(Some(key))
| None => ()
};
| None => () // Error, could not find selected key among values
}
}>
// Was a little worried about using a straight int as a key,
// in case some conversions may go wrong or something, so added
// "key" before
{values
|> E.L.React.fmapi((i, (_key, label)) =>
<Menu.Item key={"key" ++ string_of_int(i)}>
label->React.string
</Menu.Item>
)}
</Menu>
</DropdownMenu>;
};

View File

@ -0,0 +1,26 @@
open Css;
let widthStyle = (~fullWidth=false, ()) => {
let formInputInset =
style([
border(`px(1), `solid, Settings.border),
borderRadius(`px(4)),
padding(`em(0.6)),
boxShadows([
Shadow.box(
~x=zero,
~y=px(1),
~blur=px(2),
~inset=true,
rgba(0, 0, 1, 0.08),
),
]),
fontSize(`em(1.0)),
]);
let inputFullWidth =
style([width(`percent(100.)), boxSizing(`borderBox)]);
let textAreaFullWidth = merge([formInputInset, inputFullWidth]);
fullWidth ? textAreaFullWidth : formInputInset;
};

View File

@ -0,0 +1,5 @@
let inputHeader =
Css.(style([paddingLeft(`em(0.4)), paddingBottom(`em(0.6))]));
[@react.component]
let make = (~children) => <div className=inputHeader> children </div>;

View File

@ -0,0 +1,5 @@
[@react.component]
let make = (~fullWidth=false, ~rows=5, ~value=?) =>
<textarea className={FormStyles.widthStyle(~fullWidth, ())} rows>
{value |> E.O.React.fmapOrNull(React.string)}
</textarea>;

View File

@ -0,0 +1,7 @@
[@react.component]
let make = (~fullWidth=false, ~placeholder=?) =>
<input
type_="text"
className={FormStyles.widthStyle(~fullWidth, ())}
?placeholder
/>;

View File

@ -0,0 +1,70 @@
open Base;
module Styles = {
open Css;
let outer =
style(
[
backgroundColor(Colors.white),
borderBottom(`px(1), `solid, Colors.accentBlueO8),
]
@ BaseStyles.fullWidthFloatLeft,
);
let inner =
style(
[boxSizing(`borderBox), padding2(~v=`em(0.5), ~h=`em(2.0))]
@ BaseStyles.fullWidthFloatLeft,
);
let actionButtonPosition =
style([
BaseStyles.floatRight,
marginLeft(`em(2.)),
marginTop(`em(0.2)),
]);
};
let actionButton = (~variant: Button.variant=Primary, ~children) =>
<Button
variant
isDisabled=false
size=Button.(Medium)
className=Css.(merge([Styles.actionButtonPosition]))
?children
/>;
[@react.component]
let make = (~children) =>
<Div styles=[Styles.outer]>
<Div styles=[Styles.inner]> ...children </Div>
</Div>;
module SubHeader = {
[@react.component]
let make = (~children) =>
<Div
styles=[
Css.(
style(
[backgroundColor(Colors.lighterGrayBackground)]
@ BaseStyles.fullWidthFloatLeft,
)
),
]>
<Div
styles=[
Css.(
style(
[
padding2(~v=`em(0.0), ~h=`em(2.0)),
borderBottom(`px(1), `solid, Settings.border),
]
@ BaseStyles.fullWidthFloatLeft,
)
),
]>
children
</Div>
</Div>;
};

View File

@ -0,0 +1,47 @@
type content = {
headerContent: ReasonReact.reactElement,
bodyContent: ReasonReact.reactElement,
};
module Overlay = {
module Styles = {
open Css;
let className = style([maxWidth(`em(30.))]);
};
[@react.component]
let make = (~content) =>
<div className=Styles.className>
<PageCard>
<PageCard.HeaderRow>
<Div float=`left>
<PageCard.HeaderRow.Title>
<span className=Css.(style([marginRight(`em(0.4))]))>
<Icon.Questionmark isInteractive=false />
</span>
{content.headerContent}
</PageCard.HeaderRow.Title>
</Div>
</PageCard.HeaderRow>
<PageCard.Body>
<PageCard.BodyPadding v={`em(0.5)}>
<span
className=Css.(
style([
color(Settings.Text.LightBackground.p),
lineHeight(`em(1.5)),
])
)>
{content.bodyContent}
</span>
</PageCard.BodyPadding>
</PageCard.Body>
</PageCard>
</div>;
};
[@react.component]
let make = (~content) =>
<Dropdown overlay={<Overlay content />} trigger=Dropdown.Hover>
<span> <Icon.Questionmark isInteractive=true /> </span>
</Dropdown>;

View File

@ -0,0 +1,97 @@
open Base;
module PageCard = PageCard;
module Tab = Tab;
module TabList = TabList;
// Could this be in some flexrow or part of Div component?
let flexRowContainer =
Css.(style([margin2(~v=`zero, ~h=`px(-6)), alignItems(`flexEnd)]));
let flexRowItem = Css.(style([margin2(~v=`zero, ~h=`px(6))]));
type tabs =
| SimpleTab
| FreeformTab
| CustomTab;
type state = {selectedTab: tabs};
type action =
| ChangeTab(tabs);
[@react.component]
let make = (~cdf: Types.Dist.t) => {
let (selectedTab, setSelectedTab) = React.useState(() => SimpleTab);
<PageCard>
<PageCard.HeaderRow>
<PageCard.HeaderRow.Title>
"New Prediction"->React.string
<HelpDropdown
content=HelpDropdown.{
headerContent: "sdf" |> ReasonReact.string,
bodyContent: "sdfsdfsd" |> ReasonReact.string,
}
/>
</PageCard.HeaderRow.Title>
</PageCard.HeaderRow>
<PageCard.Section flex=true padding=`none>
<TabList
selected=selectedTab
onClick={key => setSelectedTab(_ => key)}
list=[
(SimpleTab, "Simple"),
(FreeformTab, "Free-form"),
(CustomTab, "Custom"),
]
flex=true
/>
</PageCard.Section>
<PageCard.Section background=`grey border=`bottom padding=`top>
<CdfChart__Large cdf width=None />
</PageCard.Section>
<PageCard.Section background=`grey>
{switch (selectedTab) {
| SimpleTab =>
<Div flexDirection=`row styles=[flexRowContainer]>
<Div flex={`num(1.0)} styles=[flexRowItem]>
<InputLabel> "Min"->React.string </InputLabel>
<TextInput fullWidth=true />
</Div>
<Div flex={`num(1.0)} styles=[flexRowItem]>
<InputLabel> "Max"->React.string </InputLabel>
<TextInput fullWidth=true />
</Div>
<Div styles=[flexRowItem]>
<Button variant=Button.Secondary> "Clear"->React.string </Button>
</Div>
</Div>
| FreeformTab => <TextInput fullWidth=true placeholder="5 to 50" />
| CustomTab =>
<div>
<div>
<DropdownSelect
initialValue={Some("CDF")}
values=[("CDF", "CDF"), ("PDF", "PDF")]
/>
<Icon.Questionmark />
</div>
<PageCard.VerticalSpace />
<Alert type_=`error>
"Input is not a valid PDF"->React.string
</Alert>
<PageCard.VerticalSpace />
<TextArea rows=4 fullWidth=true />
</div>
}}
</PageCard.Section>
<PageCard.Section>
<InputLabel> "Comment"->React.string </InputLabel>
<TextArea fullWidth=true />
<PageCard.VerticalSpace />
<Button variant=Button.Primary fullWidth=true size=Button.(Large)>
"Submit Prediction"->React.string
</Button>
</PageCard.Section>
</PageCard>;
};

View File

@ -0,0 +1,117 @@
type item = {
name: string,
icon: string,
href: string,
bookmark: bool,
onClick: ReactEvent.Mouse.t => unit,
onBookmark: ReactEvent.Mouse.t => unit,
};
module Styles = {
open Css;
let item =
style([
flex(`num(1.)),
textDecoration(`none),
padding4(
~top=`em(0.3),
~left=`em(1.5),
~right=`em(1.5),
~bottom=`em(0.3),
),
hover([background(Settings.buttonHover)]),
display(`flex),
flexDirection(`row),
]);
let header =
style([
flex(`num(1.)),
textDecoration(`none),
fontSize(`rem(0.8)),
padding4(
~top=`em(1.5),
~left=`em(1.8),
~right=`em(1.8),
~bottom=`em(0.9),
),
color(`hex("999")),
display(`flex),
]);
let itemIcon =
style([
color(`hex("7e8aa1")),
fontSize(`rem(1.1)),
marginTop(`em(-0.1)),
cursor(`pointer),
]);
let itemText = style([color(`hex("262c37")), cursor(`pointer)]);
let notBookmarkedIcon =
style([
hover([color(`hex("7e8aa1"))]),
color(`hex("f0f1f4")),
fontSize(`rem(1.1)),
marginTop(`em(-0.1)),
cursor(`pointer),
]);
let bookmarkedIcon =
style([
hover([color(`hex("7e8aa1"))]),
color(`hex("7e8aa1")),
fontSize(`rem(1.1)),
marginTop(`em(-0.1)),
cursor(`pointer),
]);
};
module Item = {
[@react.component]
let make = (~item) => {
<div className=Styles.item>
<Div flex={`num(1.)} onClick={item.onClick} className=Styles.itemIcon>
<ReactKitIcon icon={item.icon} />
</Div>
<Div flex={`num(7.)} onClick={item.onClick} className=Styles.itemText>
{item.name |> ReasonReact.string}
</Div>
</div>;
};
};
module ChannelItem = {
[@react.component]
let make = (~item) => {
let bookmarkStyle =
item.bookmark ? Styles.bookmarkedIcon : Styles.notBookmarkedIcon;
<div className=Styles.item>
<Div flex={`num(1.)} onClick={item.onClick} className=Styles.itemIcon>
<ReactKitIcon icon={item.icon} />
</Div>
<Div flex={`num(7.)} onClick={item.onClick} className=Styles.itemText>
{item.name |> ReasonReact.string}
</Div>
<Div flex={`num(0.5)} onClick={item.onBookmark} className=bookmarkStyle>
<ReactKitIcon icon="STAR_FULL" />
</Div>
</div>;
};
};
module Header = {
[@react.component]
let make = (~name: string) => {
<Div flex={`num(1.)} className=Styles.header>
{name |> ReasonReact.string}
</Div>;
};
};
[@react.component]
let make = (~children) => {
<Div flexDirection=`column> children </Div>;
};

View File

@ -0,0 +1,32 @@
module JS = {
[@bs.deriving abstract]
type numberPresentation = {
value: string,
power: option(float),
symbol: option(string),
};
[@bs.module "./numberShower.js"]
external numberShow: (float, int) => numberPresentation = "numberShow";
};
let sup = Css.(style([fontSize(`em(0.6)), verticalAlign(`super)]));
[@react.component]
let make = (~number, ~precision) => {
let numberWithPresentation = JS.numberShow(number, precision);
<span>
{JS.valueGet(numberWithPresentation) |> ReasonReact.string}
{JS.symbolGet(numberWithPresentation)
|> E.O.React.fmapOrNull(ReasonReact.string)}
{JS.powerGet(numberWithPresentation)
|> E.O.React.fmapOrNull(e =>
<span>
{{j|\u00b710|j} |> ReasonReact.string}
<span className=sup>
{e |> E.Float.toString |> ReasonReact.string}
</span>
</span>
)}
</span>;
};

View File

@ -0,0 +1,12 @@
let percentageSign = Css.(style([opacity(0.5), marginLeft(`em(0.1))]));
[@react.component]
let make = (~percentage, ~precision) => {
let numberWithPresentation =
NumberShower.JS.numberShow(percentage, precision);
<span>
{NumberShower.JS.valueGet(numberWithPresentation)
|> ReasonReact.string}
<span className=percentageSign> {"%" |> ReasonReact.string} </span>
</span>;
};

View File

@ -0,0 +1,65 @@
// 105 -> 3
const orderOfMagnitudeNum = (n) => {
return Math.pow(10, n);
};
// 105 -> 3
const orderOfMagnitude = (n) => {
return Math.floor(Math.log(n) / Math.LN10 + 0.000000001);
};
function withXSigFigs(number, sigFigs) {
const withPrecision = number.toPrecision(sigFigs);
const formatted = Number(withPrecision);
return `${formatted}`;
}
class NumberShower {
constructor(number, precision = 2) {
this.number = number;
this.precision = precision;
}
convert() {
const number = Math.abs(this.number);
const response = this.evaluate(number);
if (this.number < 0) {
response.value = '-' + response.value;
}
return response
}
metricSystem(number, order) {
const newNumber = number / orderOfMagnitudeNum(order);
const precision = this.precision;
return `${withXSigFigs(newNumber, precision)}`;
}
evaluate(number) {
if (number === 0) {
return { value: this.metricSystem(0, 0) }
}
const order = orderOfMagnitude(number);
if (order < -2) {
return { value: this.metricSystem(number, order), power: order };
} else if (order < 4) {
return { value: this.metricSystem(number, 0) };
} else if (order < 6) {
return { value: this.metricSystem(number, 3), symbol: 'K' };
} else if (order < 9) {
return { value: this.metricSystem(number, 6), symbol: 'M' };
} else if (order < 12) {
return { value: this.metricSystem(number, 9), symbol: 'B' };
} else if (order < 15) {
return { value: this.metricSystem(number, 12), symbol: 'T' };
} else {
return { value: this.metricSystem(number, order), power: order };
}
}
}
export function numberShow(number, precision = 2) {
const ns = new NumberShower(number, precision);
return ns.convert();
}

View File

@ -0,0 +1,200 @@
open Base;
[@react.component]
let make = (~children) =>
<Div
styles=[
Css.(
style(
[
background(Colors.white),
border(`px(1), `solid, Colors.border),
borderRadius(`px(5)),
]
@ BaseStyles.fullWidthFloatLeft,
)
),
]>
children
</Div>;
let defaultPadding = Css.padding2(~v=`em(0.0), ~h=`em(1.5));
module HeaderRow = {
module Styles = {
let itemTopPadding = Css.paddingTop(`em(0.5));
let itemBottomPadding = Css.paddingBottom(`em(0.35));
let itemRightPadding = Css.paddingRight(`em(0.9));
};
module Title = {
[@react.component]
let make = (~children) =>
<Div
styles=[
Css.(
style([
color(Colors.textDark),
paddingTop(`em(0.6)),
paddingBottom(`em(0.6)),
Settings.FontWeights.heavy,
])
),
]>
children
</Div>;
};
[@react.component]
let make = (~children) =>
<Div
styles=[
Css.(
style(
[
borderBottom(`px(1), `solid, Colors.accentBlueO8),
defaultPadding,
]
@ BaseStyles.fullWidthFloatLeft,
)
),
]>
children
</Div>;
};
module Body = {
[@react.component]
let make = (~children) =>
<Div styles=[Css.style(BaseStyles.fullWidthFloatLeft)]> children </Div>;
};
module BodyPadding = {
[@react.component]
let make = (~v=`em(1.5), ~children) =>
<Div
styles=[
Css.style(
[Css.padding2(~v, ~h=`em(1.5))] @ BaseStyles.fullWidthFloatLeft,
),
]>
children
</Div>;
};
module Section = {
module StyleProps = {
open Css;
type background = [ | `white | `grey];
type padding = [ | `all | `none | `top | `bottom];
type border = [ | `none | `top | `bottom | `topBottom];
let baseClass = style([clear(`both)]);
let bgGrey = style([backgroundColor(Settings.smokeWhite)]);
let paddingAll = style([padding(`em(1.5))]);
let paddingTop =
style([
padding4(~top=`em(1.5), ~right=`zero, ~bottom=`zero, ~left=`zero),
]);
let paddingBottom =
style([
padding4(~top=`zero, ~right=`zero, ~bottom=`em(1.5), ~left=`zero),
]);
let borderTop =
style([borderTop(`px(1), `solid, Settings.accentBlue1a)]);
let borderBottom =
style([borderBottom(`px(1), `solid, Settings.accentBlue1a)]);
let flexCls = style([display(`flex)]);
[@bs.send] external push: (Js.Array.t('a), 'a) => unit = "";
let toClasses =
(background: background, border: border, padding: padding, flex: bool) => {
// Array for more speed and some imperative ease
let cls = [|baseClass|];
// Background
switch (background) {
| `grey => push(cls, bgGrey)
| _ => ()
};
// Padding
switch (padding) {
| `all => push(cls, paddingAll)
| `none => ()
| `top => push(cls, paddingTop)
| `bottom => push(cls, paddingBottom)
};
// Border
switch (border) {
| `none => ()
| `top => push(cls, borderTop)
| `bottom => push(cls, borderBottom)
| `topBottom =>
push(cls, borderTop);
push(cls, borderBottom);
};
// Flex
if (flex) {
push(cls, flexCls);
};
Js.Array.joinWith(" ", cls);
};
};
/**
* Section of a PageCard
* background: `white (default) | `grey
* border: `top | `bottom | `none (default)
* padding: `none | `top | `bottom | `all (default)
* flex: true | false
*/
[@react.component]
let make =
(
~background: StyleProps.background=`white,
~border: StyleProps.border=`none,
~padding: StyleProps.padding=`all,
~flex=false,
~children,
) =>
<div className={StyleProps.toClasses(background, border, padding, flex)}>
children
</div>;
};
module VerticalSpace = {
let spaceStyle = Css.(style([marginTop(`em(1.5))]));
[@react.component]
let make = _ => <div className=spaceStyle />;
};
module H1 = {
[@react.component]
let make = (~children) =>
<h1
className=Css.(
style(
[
fontSize(`em(1.15)),
color(`hex("192D44")),
Settings.FontWeights.heavy,
marginTop(`em(0.0)),
marginBottom(`em(0.4)),
]
@ BaseStyles.fullWidthFloatLeft,
)
)>
children
</h1>;
};
module P = {
[@react.component]
let make = (~children) =>
<p
className=Css.(
style([color(Colors.Text.LightBackground.p), lineHeight(`em(1.5))])
)>
children
</p>;
};

View File

@ -0,0 +1,58 @@
type expectedResolutionDate = MomentRe.Moment.t;
type resolutionDate = MomentRe.Moment.t;
module State = {
type t =
| OPEN(expectedResolutionDate)
| PENDING(expectedResolutionDate)
| RESOLVED(resolutionDate);
let toColor = (t: t) =>
switch (t) {
| OPEN(_) => Settings.Statuses.green
| PENDING(_) => Settings.Statuses.yellow
| RESOLVED(_) => Settings.Statuses.resolved
};
let toText = (t: t) =>
switch (t) {
| OPEN(_) => "Open"
| PENDING(_) => "Pending"
| RESOLVED(_) => "Resolved"
};
let toSubtext = (t: t) =>
switch (t) {
| OPEN(time) =>
"Resolves " ++ MomentRe.Moment.fromNow(time, ~withoutSuffix=None)
| PENDING(time) =>
"Pending since " ++ MomentRe.Moment.fromNow(time, ~withoutSuffix=None)
| RESOLVED(time) =>
"Resolved " ++ MomentRe.Moment.fromNow(time, ~withoutSuffix=None)
};
};
module Style = {
let token = (state, fontSize') =>
Css.(
style([
Settings.FontWeights.heavy,
color(State.toColor(state)),
marginRight(`em(1.0)),
fontSize(fontSize'),
])
);
let text = fontSize' =>
Css.(style([color(Settings.accentBlue), fontSize(fontSize')]));
};
let make = (~state: State.t, ~fontSize=`em(0.9), ()) =>
<div>
<span className={Style.token(state, fontSize)}>
{State.toText(state) |> ReasonReact.string}
</span>
<span className={Style.text(fontSize)}>
{State.toSubtext(state) |> ReasonReact.string}
</span>
</div>;

View File

@ -0,0 +1,59 @@
open Base;
type directionButton = {
isDisabled: bool,
onClick: ReactEvent.Mouse.t => unit,
};
type currentValue =
| Item(int)
| Range(int, int);
type t = {
currentValue,
max: int,
pageLeft: directionButton,
pageRight: directionButton,
};
let _text = (t: t) => {
let currentValue =
switch (t.currentValue) {
| Item(i) => i |> string_of_int
| Range(a, b) => (a |> string_of_int) ++ " - " ++ (b |> string_of_int)
};
currentValue ++ " of " ++ (t.max |> string_of_int);
};
module Styles = {
let buttonLabel =
Css.(
style([
BaseStyles.floatLeft,
marginRight(`em(0.5)),
marginTop(`em(0.12)),
color(Colors.accentBlue),
])
);
let rightButton = Css.(style([marginLeft(`em(0.3))]));
let leftButton = Css.(style([marginLeft(`em(0.3))]));
};
let _directionLink = (t: directionButton, icon: string, positionStyles) =>
<Button
isDisabled={t.isDisabled}
onClick={t.onClick}
className=positionStyles
size=Button.(Small)>
<ReactKitIcon icon />
</Button>;
let make = (t: t) =>
<>
<span className=Styles.buttonLabel>
{_text(t) |> ReasonReact.string}
</span>
{_directionLink(t.pageLeft, "CHEVRON_LEFT", "")}
{_directionLink(t.pageRight, "CHEVRON_RIGHT", Styles.rightButton)}
</>;

View File

@ -0,0 +1,63 @@
open Settings.Text;
let activeStyles =
Css.[
borderBottom(`px(2), `solid, LightBackground.active),
color(LightBackground.active),
hover([color(LightBackground.active)]),
];
let inactiveStyles =
Css.[
borderBottom(`px(2), `solid, Settings.clear),
color(LightBackground.main),
hover([color(LightBackground.active)]),
];
let allStyles =
Css.[
paddingBottom(`em(0.2)),
paddingTop(`em(0.5)),
paddingLeft(`em(0.4)),
paddingRight(`em(0.4)),
marginRight(`em(1.8)),
BaseStyles.floatLeft,
];
let flexStyles =
Css.[textAlign(`center), flexGrow(1.), padding2(~v=`em(0.8), ~h=`zero)];
module Button = {
open Css;
let noStyles = [
borderWidth(`px(0)),
backgroundColor(`transparent),
userSelect(`none),
cursor(`pointer),
];
[@react.component]
let make = (~isActive=false, ~onClick=?, ~flex=false, ~children) =>
<button
disabled=isActive
?onClick
className={Css.style(
noStyles
@ (isActive ? activeStyles : inactiveStyles)
@ (flex ? flexStyles : allStyles),
)}>
children
</button>;
};
[@react.component]
let make = (~isActive=false, ~onClick=?, ~flex=false, ~children) =>
<Link
isDisabled=false
?onClick
className={Css.style(
(isActive ? activeStyles : inactiveStyles)
@ (flex ? flexStyles : allStyles),
)}>
children
</Link>;

View File

@ -0,0 +1,51 @@
open Base;
let styles = (~isDisabled=false, ~heightPadding=0, ()) => {
let main =
Css.(
style([
padding2(~v=`px(heightPadding), ~h=`px(6)),
BaseStyles.floatLeft,
borderRadius(Colors.BorderRadius.medium),
border(`px(1), `solid, Colors.accentBlueO8),
marginTop(`em(0.05)),
lineHeight(`em(1.35)),
])
);
let disabledStyles = Css.(style([background(Colors.greydisabled)]));
isDisabled ? Css.merge([disabledStyles, main]) : main;
};
[@react.component]
let make = (~isActive, ~onClick=?, ~number: option(int)=?, ~children) => {
let textStyle =
Css.(style([BaseStyles.floatLeft, marginRight(`em(0.4))]));
let colors =
Colors.Text.(
isActive
? Css.[
color(LightBackground.active),
hover([color(LightBackground.active)]),
]
: Css.[
color(LightBackground.main),
hover([color(LightBackground.active)]),
]
);
<Link
?onClick
className=Css.(
style([BaseStyles.floatLeft, marginRight(`em(1.8))] @ colors)
)
isDisabled=false>
<span className=textStyle> children </span>
{number
|> E.O.React.fmapOrNull(number =>
<span className={styles()}>
{number |> string_of_int |> ReasonReact.string}
</span>
)}
</Link>;
};

View File

@ -0,0 +1,156 @@
open Base;
let defaultRowHorizontalPadding = `em(1.5);
module Styles = {
let row =
Css.(
style(
[
padding2(~v=`zero, ~h=defaultRowHorizontalPadding),
borderBottom(`px(1), `solid, Colors.accentBlue1a),
display(`flex),
flexDirection(`row),
selector(":last-child", BaseStyles.borderNone),
]
@ BaseStyles.fullWidthFloatLeft,
)
);
let textArea =
Css.(
style(
[
padding2(~v=`em(0.8), ~h=`em(1.0)),
borderRadius(Colors.BorderRadius.tight),
color(Colors.Text.LightBackground.p),
]
@ BaseStyles.fullWidthFloatLeft,
)
);
let topRow =
Css.(
style(
[
padding2(~v=`zero, ~h=defaultRowHorizontalPadding),
paddingTop(`em(0.4)),
display(`flex),
flexDirection(`row),
]
@ BaseStyles.fullWidthFloatLeft,
)
);
let bottomRow =
Css.(
style(
[
padding2(~v=`zero, ~h=`em(0.4)),
paddingBottom(`em(0.4)),
display(`flex),
flexDirection(`row),
borderBottom(`px(1), `solid, Colors.accentBlue1a),
]
@ BaseStyles.fullWidthFloatLeft,
)
);
let clickableRow =
Css.(
style([
hover([background(Colors.lightGrayBackground)]),
cursor(`pointer),
])
);
module Elements = {
let primaryText =
Css.style([
Css.fontSize(`em(1.05)),
Css.lineHeight(`em(1.5)),
Css.fontWeight(`num(600)),
Css.color(`hex("384e67")),
]);
let link' =
Css.[
marginRight(`em(1.0)),
color(Colors.textMedium),
hover([color(Colors.textDark)]),
];
let link = (~isUnderlined=false, ()) =>
Css.(
style(isUnderlined ? link' @ [textDecoration(`underline)] : link')
);
};
};
module Cell = {
let standardCellPadding =
Css.(style([paddingTop(`em(0.7)), paddingBottom(`em(0.5))]));
let style = flexAmount => Css.(style([flex(flexAmount)]));
[@react.component]
let make = (~flex, ~className="", ~properties=[], ~children) =>
<Div
className={Css.merge([
style(flex),
standardCellPadding,
className,
Css.style(properties),
])}>
children
</Div>;
};
module HeaderRow = {
module Styles = {
let headerRow =
Css.(
style(
[
background(Colors.lightGrayBackground),
color(Colors.Text.LightBackground.main),
borderBottom(`px(1), `solid, Colors.accentBlueO8),
display(`flex),
flexDirection(`row),
padding2(~v=`em(0.1), ~h=defaultRowHorizontalPadding),
]
@ BaseStyles.fullWidthFloatLeft,
)
);
};
[@react.component]
let make = (~children) => <Div styles=[Styles.headerRow]> children </Div>;
};
module Row = {
let textSection = text => <Div styles=[Styles.textArea]> text </Div>;
[@react.component]
let make = (~className="", ~bottomSubRow=?, ~onClick=?, ~children) => {
switch (bottomSubRow) {
| Some(bottomSubRow) =>
let commonClasses =
onClick |> E.O.isSome
? [Styles.clickableRow, className] : [className];
<Div styles=commonClasses ?onClick>
<Div styles=[Styles.topRow]> children </Div>
<Div styles=[Styles.bottomRow]> bottomSubRow </Div>
</Div>;
| None =>
let commonClasses =
onClick |> E.O.isSome
? [Styles.row, Styles.clickableRow, className]
: [Styles.row, className];
<Div styles=commonClasses ?onClick> children </Div>;
};
};
};
[@react.component]
let make = (~children) => <div> children </div>;

View File

@ -0,0 +1,39 @@
import TextareaAutosize from 'react-textarea-autosize';
import React from 'react';
export class AutosizeTextareaInput extends React.Component {
/**
* @param props
*/
constructor(props) {
super(props);
this.state = { value: this.props.value || "" };
this.handleChange = this.handleChange.bind(this);
this.handleHeightChange = this.handleHeightChange.bind(this);
}
handleChange(event) {
const { value } = event.target;
this.setState({ value });
this.props.onChange(value);
}
handleHeightChange() {
this.props.onHeightChange();
}
render() {
return React.createElement(TextareaAutosize, {
style: this.props.style,
className: this.props.className,
rows: this.props.rows,
maxRows: this.props.maxRows,
minRows: this.props.minRows,
value: this.state.value,
onChange: this.handleChange,
onHeightChange: this.handleHeightChange,
useCacheForDOMMeasurements: this.props.useCacheForDOMMeasurements,
});
}
}

View File

@ -0,0 +1,112 @@
import React from 'react';
import _ from 'lodash';
import { Guesstimator } from '@foretold/guesstimator';
import { Samples } from '@foretold/cdf';
/**
* @param {number} ratio
* @return {string}
*/
const minMaxRatio = (ratio) => {
if (ratio < 100000) {
return "SMALL";
} else if (ratio < 10000000) {
return "MEDIUM";
} else {
return "LARGE";
}
};
/**
* @param samples
* @return {string}
*/
const ratioSize = samples => {
samples.sort();
const minValue = samples.getPercentile(2);
const maxValue = samples.getPercentile(98);
return minMaxRatio(maxValue / minValue);
};
/**
* @param {number[]} values
* @param {number} min
* @param {number} max
* @return {[number[], number[], boolean]}
*/
const toCdf = (values, min, max) => {
let _values = values;
if (_.isFinite(min)) _values = _.filter(_values, r => r > min);
if (_.isFinite(max)) _values = _.filter(_values, r => r < max);
const samples = new Samples(_values);
const ratioSize$ = ratioSize(samples);
const width = ratioSize$ === "SMALL" ? 20 : 1;
const cdf = samples.toCdf({ size: 1000, width, min, max });
return [cdf.ys, cdf.xs, ratioSize$ === "LARGE"];
};
export class GuesstimateInput extends React.Component {
/**
* @param props
*/
constructor(props) {
super(props);
this.state = { value: this.props.initialValue || "", items: [] };
this.handleChange = this.handleChange.bind(this);
this.textInput = React.createRef();
}
componentDidMount() {
if (this.props.focusOnRender) {
this.textInput.focus();
}
if (this.state.value !== ""){
this.handleChangeValue(this.state.value);
}
}
handleChangeValue(value) {
const text = value;
let [_error, item] = Guesstimator.parse({ text });
let parsedInput = item.parsedInput;
let what = new Guesstimator({ parsedInput: parsedInput });
let foo = what.sample(this.props.sampleCount);
let values = _.filter(foo.values, _.isFinite);
if (!!values) {
this.setState({ value, items: values });
} else {
this.setState({ value, items: [] });
}
if (!values || values.length === 0) {
this.props.onUpdate([[], [], false]);
} else if (values.length === 1) {
this.props.onUpdate([[1], values, false]);
} else {
const min = this.props.min;
const max = this.props.max;
this.props.onUpdate(toCdf(values, min, max));
}
this.props.onChange(text);
}
handleChange(event){
this.handleChangeValue(event.target.value);
}
render() {
return <input
type="text"
placeholder="10 to 100"
value={this.state.value}
onChange={this.handleChange}
ref={input => this.textInput = input}
/>;
}
}

View File

@ -0,0 +1,15 @@
type size = {
.
"width": int,
"height": int,
};
[@bs.module "react-use"]
external useSize:
(size => ReasonReact.reactElement, size) => (React.element, size) =
"useSize";
type useTitleOptions = {. "restoreOnUnmount": bool};
[@bs.module "react-use"]
external useTitle: (string, useTitleOptions) => unit = "useTitle";

View File

@ -0,0 +1,36 @@
let fn = (a: string) => ();
let fn2 = () => ();
[@bs.module "./AutosizeTextareaInput.js"]
external guesstimateInput: ReasonReact.reactClass = "AutosizeTextareaInput";
[@react.component]
let make =
(
~rows: option(int)=?,
~maxRows: option(int)=?,
~minRows: option(int)=?,
~useCacheForDOMMeasurements: option(bool)=?,
~value="",
~onChange=fn,
~onHeightChange=fn2,
~className: option(string)=?,
~style: option(ReactDOMRe.Style.t)=?,
~children=ReasonReact.null,
) =>
ReasonReact.wrapJsForReason(
~reactClass=guesstimateInput,
~props={
"rows": rows,
"maxRows": maxRows,
"minRows": minRows,
"useCacheForDOMMeasurements": useCacheForDOMMeasurements,
"value": value,
"onChange": onChange,
"onHeightChange": onHeightChange,
"className": className,
"style": style,
},
children,
)
|> ReasonReact.element;

View File

@ -0,0 +1,32 @@
let fn = (a: (array(float), array(float), bool)) => ();
let fn2 = (a: string) => ();
[@bs.module "./GuesstimateInput.js"]
external guesstimateInput: ReasonReact.reactClass = "GuesstimateInput";
[@react.component]
let make =
(
~sampleCount=10000,
~min=None,
~max=None,
~initialValue=None,
~onUpdate=fn,
~onChange=fn2,
~focusOnRender=true,
~children=ReasonReact.null,
) =>
ReasonReact.wrapJsForReason(
~reactClass=guesstimateInput,
~props={
"sampleCount": sampleCount,
"onUpdate": onUpdate,
"initialValue": initialValue,
"onChange": onChange,
"min": min,
"max": max,
"focusOnRender": focusOnRender,
},
children,
)
|> ReasonReact.element;

View File

@ -0,0 +1,24 @@
[@bs.val] external document: Dom.document = "document";
[@bs.set] external setTitleDom: (Dom.document, string) => unit = "title";
[@bs.get] external getTitleDom: Dom.document => string = "title";
let getTitle = () => getTitleDom(document);
let setTitle = setTitleDom(document);
let unsetTitle = (previousTitle) => setTitle(previousTitle);
let useTitle = (title: string): unit =>{
let prev = getTitle();
React.useEffect1(
() => {
setTitle(title);
Some(_ => unsetTitle(prev));
},
[|title|],
);
};
[@react.component]
let make = (~title: string) => {
useTitle(title);
React.null;
};

View File

@ -0,0 +1,48 @@
import * as _ from 'lodash';
import React from 'react';
import * as vega from 'vega';
import { Cdf } from '@foretold/cdf';
import spec from './spec-percentiles';
export class PercentilesChart extends React.PureComponent {
constructor(props) {
super(props);
this.containerRef = React.createRef();
this.view = null;
this.data = this.props.data.map(item => {
const cdf = new Cdf(item.xs, item.ys);
const p5 = cdf.findX(0.05);
const p50 = cdf.findX(0.50);
const p95 = cdf.findX(0.95);
const timestamp = item.createdAt;
return { p5, p50, p95, timestamp };
});
this.spec = _.cloneDeep(spec);
this.spec.data[0].values = this.data;
}
componentDidMount() {
this.view = new vega.View(vega.parse(this.spec), {
renderer: 'canvas',
container: this.containerRef.current,
hover: true
});
return this.view.runAsync();
}
componentWillUnmount() {
if (this.view) {
this.view.finalize();
}
}
render() {
return React.createElement("div", {
ref: this.containerRef,
});
}
}

View File

@ -0,0 +1,11 @@
[@bs.module "./PercentilesChart.js"]
external percentilesChart: ReasonReact.reactClass = "PercentilesChart";
[@react.component]
let make = (~data, ~children=ReasonReact.null) =>
ReasonReact.wrapJsForReason(
~reactClass=percentilesChart,
~props={"data": data},
children,
)
|> ReasonReact.element;

View File

@ -0,0 +1,109 @@
{
"$schema": "https://vega.github.io/schema/vega/v5.json",
"width": 900,
"height": 200,
"padding": 5,
"data": [
{
"name": "facet",
"values": [
{"timestamp": "2020-01-20 14:55:01.412", "p5": 2, "p95": 3, "p50": 2.5},
{"timestamp": "2020-01-23 14:55:01.412", "p5": 3, "p95": 5, "p50": 4},
{"timestamp": "2020-01-24 14:55:01.412", "p5": 7, "p95": 8, "p50": 7.5},
{"timestamp": "2020-01-24 14:55:01.412", "p5": 2, "p95": 3, "p50": 2.5},
{"timestamp": "2020-01-28 14:55:01.412", "p5": 3, "p95": 50, "p50": 40},
{"timestamp": "2020-01-29 14:55:01.412", "p5": 2, "p95": 3, "p50": 2.5}
],
"format": {"type": "json", "parse": {"timestamp": "date"}}
},
{
"name": "table",
"source": "facet",
"transform": [
{
"type": "aggregate",
"groupby": ["timestamp"],
"ops": ["mean", "mean", "mean"],
"fields": ["p5", "p95", "p50"],
"as": ["p5", "p95", "p50"]
}
]
}
],
"scales": [
{
"name": "xscale",
"type": "time",
"nice": "day",
"domain": {"data": "facet", "field": "timestamp"},
"range": "width"
},
{
"name": "yscale",
"type": "linear",
"range": "height",
"nice": true,
"zero": true,
"domain": {"data": "facet", "field": "p95"}
}
],
"axes": [
{
"orient": "bottom",
"scale": "xscale",
"format": "%m-%d",
"grid": true,
"title": "Days",
"encode": {
"grid": {"enter": {"stroke": {"value": "#ccc"}}},
"ticks": {"enter": {"stroke": {"value": "#ccc"}}}
}
},
{
"orient": "left",
"scale": "yscale",
"title": "Values and Percentiles",
"grid": true,
"domain": false,
"tickSize": 12,
"encode": {
"grid": {"enter": {"stroke": {"value": "#ccc"}}},
"ticks": {"enter": {"stroke": {"value": "#ccc"}}}
}
}
],
"marks": [
{
"type": "area",
"from": {"data": "table"},
"encode": {
"enter": {"fill": {"value": "#C9D6E5"}},
"update": {
"interpolate": {"value": "monotone"},
"x": {"scale": "xscale", "field": "timestamp"},
"y": {"scale": "yscale", "field": "p5"},
"y2": {"scale": "yscale", "field": "p95"},
"opacity": {"value": 0.75}
},
"hover": {"opacity": {"value": 0.5}}
}
},
{
"type": "line",
"from": {"data": "table"},
"encode": {
"update": {
"interpolate": {"value": "monotone"},
"stroke": {"value": "#4C78A8"},
"strokeWidth": {"value": 2},
"opacity": {"value": 0.8}
},
"hover": {"opacity": {"value": 0.5}},
"enter": {
"x": {"scale": "xscale", "field": "timestamp"},
"y": {"scale": "yscale", "field": "p50"}
}
}
}
]
}

View File

@ -3,11 +3,11 @@
"version": "0.1.0", "version": "0.1.0",
"homepage": "https://foretold-app.github.io/estiband/", "homepage": "https://foretold-app.github.io/estiband/",
"scripts": { "scripts": {
"build": "bsb -make-world", "build": "rescript build",
"build:style": "tailwind build src/styles/index.css -o src/styles/tailwind.css", "build:style": "tailwind build src/styles/index.css -o src/styles/tailwind.css",
"start": "bsb -make-world -w -ws _ ", "start": "rescript build -w",
"clean": "bsb -clean-world", "clean": "rescript clean",
"parcel": "parcel ./src/index.html --public-url / --no-autoinstall -- watch", "parcel": "parcel ./src/index.html",
"parcel-build": "parcel build ./src/index.html --no-source-maps --no-autoinstall", "parcel-build": "parcel build ./src/index.html --no-source-maps --no-autoinstall",
"showcase": "PORT=12345 parcel showcase/index.html", "showcase": "PORT=12345 parcel showcase/index.html",
"server": "moduleserve ./ --port 8000", "server": "moduleserve ./ --port 8000",
@ -26,7 +26,7 @@
"author": "", "author": "",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@foretold/components": "0.0.6", "@foretold/components": "./foretold/components",
"@glennsl/bs-json": "^5.0.2", "@glennsl/bs-json": "^5.0.2",
"ace-builds": "^1.4.12", "ace-builds": "^1.4.12",
"antd": "3.17.0", "antd": "3.17.0",
@ -36,7 +36,7 @@
"bs-ant-design-alt": "2.0.0-alpha.33", "bs-ant-design-alt": "2.0.0-alpha.33",
"bs-css": "11.0.0", "bs-css": "11.0.0",
"bs-moment": "0.4.5", "bs-moment": "0.4.5",
"bs-reform": "9.7.1", "bs-reform": "^10.0.3",
"bsb-js": "1.1.7", "bsb-js": "1.1.7",
"d3": "5.15.0", "d3": "5.15.0",
"gh-pages": "2.2.0", "gh-pages": "2.2.0",
@ -57,7 +57,8 @@
"react-use": "^13.27.0", "react-use": "^13.27.0",
"react-vega": "^7.4.1", "react-vega": "^7.4.1",
"reason-react": ">=0.7.0", "reason-react": ">=0.7.0",
"reschema": "1.3.0", "reschema": "^2.2.0",
"rescript": "^9.1.4",
"tailwindcss": "1.2.0", "tailwindcss": "1.2.0",
"vega": "*", "vega": "*",
"vega-embed": "6.6.0", "vega-embed": "6.6.0",

5
shell.nix Normal file
View File

@ -0,0 +1,5 @@
{ pkgs ? import <nixpkgs> {} }:
pkgs.mkShell {
name = "squiggle";
buildInputs = with pkgs; [ yarn yarn2nix ];
}

View File

@ -100,7 +100,7 @@ module Index = {
| ChangeRoute(ReasonReactRouter.url); | ChangeRoute(ReasonReactRouter.url);
let changeId = (id: string) => { let changeId = (id: string) => {
ReasonReactRouter.push(baseUrl ++ "#" ++ id); let _ = ReasonReactRouter.push(baseUrl ++ "#" ++ id);
(); ();
}; };
@ -157,7 +157,7 @@ module Index = {
}); });
React.useState(() => { React.useState(() => {
ReasonReactRouter.watchUrl(url => setRoute(_ => url)); let _ = ReasonReactRouter.watchUrl(url => setRoute(_ => url));
(); ();
}) })
|> ignore; |> ignore;

View File

@ -3,8 +3,8 @@
var reasonReactBlue = "#48a9dc"; var reasonReactBlue = "#48a9dc";
var style = "\n body {\n background-color: rgb(224, 226, 229);\n display: flex;\n flex-direction: column;\n align-items: center;\n }\n button {\n background-color: white;\n color: " + (String(reasonReactBlue) + (";\n box-shadow: 0 0 0 1px " + (String(reasonReactBlue) + (";\n border: none;\n padding: 8px;\n font-size: 16px;\n }\n button:active {\n background-color: " + (String(reasonReactBlue) + ";\n color: white;\n }\n .container {\n margin: 12px 0px;\n box-shadow: 0px 4px 16px rgb(200, 200, 200);\n width: 720px;\n border-radius: 12px;\n font-family: sans-serif;\n }\n .containerTitle {\n background-color: rgb(242, 243, 245);\n border-radius: 12px 12px 0px 0px;\n padding: 12px;\n font-weight: bold;\n }\n .containerContent {\n background-color: white;\n padding: 16px;\n border-radius: 0px 0px 12px 12px;\n }\n"))))); var style = "\n body {\n background-color: rgb(224, 226, 229);\n display: flex;\n flex-direction: column;\n align-items: center;\n }\n button {\n background-color: white;\n color: " + reasonReactBlue + ";\n box-shadow: 0 0 0 1px " + reasonReactBlue + ";\n border: none;\n padding: 8px;\n font-size: 16px;\n }\n button:active {\n background-color: " + reasonReactBlue + ";\n color: white;\n }\n .container {\n margin: 12px 0px;\n box-shadow: 0px 4px 16px rgb(200, 200, 200);\n width: 720px;\n border-radius: 12px;\n font-family: sans-serif;\n }\n .containerTitle {\n background-color: rgb(242, 243, 245);\n border-radius: 12px 12px 0px 0px;\n padding: 12px;\n font-weight: bold;\n }\n .containerContent {\n background-color: white;\n padding: 16px;\n border-radius: 0px 0px 12px 12px;\n }\n";
exports.reasonReactBlue = reasonReactBlue; exports.reasonReactBlue = reasonReactBlue;
exports.style = style; exports.style = style;
/* style Not a pure module */ /* No side effect */

Some files were not shown because too many files have changed in this diff Show More