Change build system to Rescript
This commit is contained in:
parent
4db127086e
commit
01990dbe9f
|
@ -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
16
foretold/components/.gitignore
vendored
Normal 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
|
15
foretold/components/.vscode/settings.json
vendored
Normal file
15
foretold/components/.vscode/settings.json
vendored
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
{
|
||||||
|
"spellright.language": [
|
||||||
|
"en"
|
||||||
|
],
|
||||||
|
"spellright.documentTypes": [
|
||||||
|
"markdown",
|
||||||
|
"latex",
|
||||||
|
"plaintext"
|
||||||
|
],
|
||||||
|
"spellright.parserByClass": {
|
||||||
|
"reason": {
|
||||||
|
"parser": "plain"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
29
foretold/components/README.md
Normal file
29
foretold/components/README.md
Normal 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`.
|
37
foretold/components/bsconfig.json
Normal file
37
foretold/components/bsconfig.json
Normal 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
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
7
foretold/components/example/App.re
Normal file
7
foretold/components/example/App.re
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
FC.Base.Globals.load();
|
||||||
|
ReactDOMRe.renderToElementWithId(
|
||||||
|
<div className=Css.(style([fontFamily(Settings.Text.standardFont)]))>
|
||||||
|
ExampleFullPage.make
|
||||||
|
</div>,
|
||||||
|
"app",
|
||||||
|
);
|
124
foretold/components/example/Example/ExampleFullPage.re
Normal file
124
foretold/components/example/Example/ExampleFullPage.re
Normal 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>;
|
|
@ -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>;
|
|
@ -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>;
|
|
@ -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>;
|
|
@ -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>;
|
|
@ -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>;
|
4016
foretold/components/example/ExampleCdfs.re
Normal file
4016
foretold/components/example/ExampleCdfs.re
Normal file
File diff suppressed because it is too large
Load Diff
17
foretold/components/example/index.html
Normal file
17
foretold/components/example/index.html
Normal 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>
|
0
foretold/components/example/styles.css
Normal file
0
foretold/components/example/styles.css
Normal file
BIN
foretold/components/logo.png
Normal file
BIN
foretold/components/logo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 17 KiB |
48
foretold/components/package.json
Normal file
48
foretold/components/package.json
Normal 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"
|
||||||
|
}
|
||||||
|
}
|
23
foretold/components/showcase/Entries.re
Normal file
23
foretold/components/showcase/Entries.re
Normal 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;
|
30
foretold/components/showcase/EntryTypes.re
Normal file
30
foretold/components/showcase/EntryTypes.re
Normal 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});
|
||||||
|
};
|
7
foretold/components/showcase/Index.re
Normal file
7
foretold/components/showcase/Index.re
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
ReactDOMRe.renderToElementWithId(
|
||||||
|
<div className=Css.(style([fontFamily(Settings.Text.standardFont)]))>
|
||||||
|
<Lib.Index />
|
||||||
|
</div>,
|
||||||
|
"main",
|
||||||
|
);
|
||||||
|
ReasonReactRouter.push("");
|
201
foretold/components/showcase/Lib.re
Normal file
201
foretold/components/showcase/Lib.re
Normal 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>;
|
||||||
|
};
|
||||||
|
};
|
104
foretold/components/showcase/entries/Scoring.re
Normal file
104
foretold/components/showcase/entries/Scoring.re
Normal 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 />));
|
48
foretold/components/showcase/entries/Showcase_AgentLink.re
Normal file
48
foretold/components/showcase/entries/Showcase_AgentLink.re
Normal file
File diff suppressed because one or more lines are too long
12
foretold/components/showcase/entries/Showcase_Alerts.re
Normal file
12
foretold/components/showcase/entries/Showcase_Alerts.re
Normal 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));
|
36
foretold/components/showcase/entries/Showcase_Buttons.re
Normal file
36
foretold/components/showcase/entries/Showcase_Buttons.re
Normal 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));
|
18
foretold/components/showcase/entries/Showcase_Charts.re
Normal file
18
foretold/components/showcase/entries/Showcase_Charts.re
Normal 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),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
];
|
109
foretold/components/showcase/entries/Showcase_Colors.re
Normal file
109
foretold/components/showcase/entries/Showcase_Colors.re
Normal 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 />));
|
46
foretold/components/showcase/entries/Showcase_Dropdown.re
Normal file
46
foretold/components/showcase/entries/Showcase_Dropdown.re
Normal 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),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
];
|
|
@ -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),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
];
|
|
@ -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),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
];
|
|
@ -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));
|
33
foretold/components/showcase/entries/Showcase_Menu.re
Normal file
33
foretold/components/showcase/entries/Showcase_Menu.re
Normal 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),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
];
|
|
@ -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));
|
|
@ -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 />));
|
19
foretold/components/showcase/entries/Showcase_PageCard.re
Normal file
19
foretold/components/showcase/entries/Showcase_PageCard.re
Normal 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
14051
foretold/components/showcase/entries/samples/sample-measurements.json
Normal file
14051
foretold/components/showcase/entries/samples/sample-measurements.json
Normal file
File diff suppressed because it is too large
Load Diff
23
foretold/components/showcase/index.html
Normal file
23
foretold/components/showcase/index.html
Normal 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>
|
98
foretold/components/src/AgentLink.re
Normal file
98
foretold/components/src/AgentLink.re
Normal 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)} />;
|
28
foretold/components/src/AppHeader.re
Normal file
28
foretold/components/src/AppHeader.re
Normal 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>;
|
35
foretold/components/src/Base/Alert.re
Normal file
35
foretold/components/src/Base/Alert.re
Normal 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>;
|
||||||
|
};
|
25
foretold/components/src/Base/Avatar.re
Normal file
25
foretold/components/src/Base/Avatar.re
Normal 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>;
|
19
foretold/components/src/Base/Base.re
Normal file
19
foretold/components/src/Base/Base.re
Normal 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;
|
8
foretold/components/src/Base/BaseStyles.re
Normal file
8
foretold/components/src/Base/BaseStyles.re
Normal 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)]);
|
135
foretold/components/src/Base/Button.re
Normal file
135
foretold/components/src/Base/Button.re
Normal 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>;
|
48
foretold/components/src/Base/Div.re
Normal file
48
foretold/components/src/Base/Div.re
Normal 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>;
|
||||||
|
};
|
104
foretold/components/src/Base/Dropdown.re
Normal file
104
foretold/components/src/Base/Dropdown.re
Normal 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;
|
58
foretold/components/src/Base/DropdownMenu.re
Normal file
58
foretold/components/src/Base/DropdownMenu.re
Normal 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>;
|
||||||
|
};
|
175
foretold/components/src/Base/E.re
Normal file
175
foretold/components/src/Base/E.re
Normal 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);
|
||||||
|
};
|
17
foretold/components/src/Base/Globals.re
Normal file
17
foretold/components/src/Base/Globals.re
Normal 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))]));
|
||||||
|
};
|
61
foretold/components/src/Base/Icon.re
Normal file
61
foretold/components/src/Base/Icon.re
Normal 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>;
|
||||||
|
};
|
29
foretold/components/src/Base/Link.re
Normal file
29
foretold/components/src/Base/Link.re
Normal 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>;
|
||||||
|
};
|
375
foretold/components/src/Base/Menu.re
Normal file
375
foretold/components/src/Base/Menu.re
Normal 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;
|
127
foretold/components/src/Base/ReactKitIcon.js
Normal file
127
foretold/components/src/Base/ReactKitIcon.js
Normal 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
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
15
foretold/components/src/Base/ReactKitIcon.re
Normal file
15
foretold/components/src/Base/ReactKitIcon.re
Normal 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;
|
93
foretold/components/src/Base/Settings.re
Normal file
93
foretold/components/src/Base/Settings.re
Normal 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";
|
||||||
|
};
|
20
foretold/components/src/Base/TabList.re
Normal file
20
foretold/components/src/Base/TabList.re
Normal 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>
|
||||||
|
);
|
||||||
|
|
78
foretold/components/src/Base/Types.re
Normal file
78
foretold/components/src/Base/Types.re
Normal 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;
|
||||||
|
};
|
69
foretold/components/src/Base/stats.js
Normal file
69
foretold/components/src/Base/stats.js
Normal 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
|
||||||
|
};
|
1
foretold/components/src/BotDefaultImage.re
Normal file
1
foretold/components/src/BotDefaultImage.re
Normal file
File diff suppressed because one or more lines are too long
45
foretold/components/src/Charts/CdfChart__Base.re
Normal file
45
foretold/components/src/Charts/CdfChart__Base.re
Normal 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;
|
29
foretold/components/src/Charts/CdfChart__Large.re
Normal file
29
foretold/components/src/Charts/CdfChart__Large.re
Normal 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>;
|
||||||
|
};
|
37
foretold/components/src/Charts/CdfChart__Plain.re
Normal file
37
foretold/components/src/Charts/CdfChart__Plain.re
Normal 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>;
|
||||||
|
};
|
46
foretold/components/src/Charts/CdfChart__Small.re
Normal file
46
foretold/components/src/Charts/CdfChart__Small.re
Normal 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>;
|
||||||
|
};
|
31
foretold/components/src/Charts/CdfChart__StatSummary.re
Normal file
31
foretold/components/src/Charts/CdfChart__StatSummary.re
Normal 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>
|
||||||
|
</>;
|
65
foretold/components/src/Charts/cdfChart.js
Normal file
65
foretold/components/src/Charts/cdfChart.js
Normal 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;
|
303
foretold/components/src/Charts/cdfChartd3.js
Normal file
303
foretold/components/src/Charts/cdfChartd3.js
Normal 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;
|
23
foretold/components/src/FC.re
Normal file
23
foretold/components/src/FC.re
Normal 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;
|
||||||
|
};
|
109
foretold/components/src/Footer.re
Normal file
109
foretold/components/src/Footer.re
Normal 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>;
|
60
foretold/components/src/Form/DropdownSelect.re
Normal file
60
foretold/components/src/Form/DropdownSelect.re
Normal 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>;
|
||||||
|
};
|
26
foretold/components/src/Form/FormStyles.re
Normal file
26
foretold/components/src/Form/FormStyles.re
Normal 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;
|
||||||
|
};
|
5
foretold/components/src/Form/InputLabel.re
Normal file
5
foretold/components/src/Form/InputLabel.re
Normal 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>;
|
5
foretold/components/src/Form/TextArea.re
Normal file
5
foretold/components/src/Form/TextArea.re
Normal 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>;
|
7
foretold/components/src/Form/TextInput.re
Normal file
7
foretold/components/src/Form/TextInput.re
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
[@react.component]
|
||||||
|
let make = (~fullWidth=false, ~placeholder=?) =>
|
||||||
|
<input
|
||||||
|
type_="text"
|
||||||
|
className={FormStyles.widthStyle(~fullWidth, ())}
|
||||||
|
?placeholder
|
||||||
|
/>;
|
70
foretold/components/src/GroupHeader.re
Normal file
70
foretold/components/src/GroupHeader.re
Normal 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>;
|
||||||
|
};
|
47
foretold/components/src/HelpDropdown.re
Normal file
47
foretold/components/src/HelpDropdown.re
Normal 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>;
|
97
foretold/components/src/MeasurableForm.re
Normal file
97
foretold/components/src/MeasurableForm.re
Normal 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>;
|
||||||
|
};
|
117
foretold/components/src/MyCommunities.re
Normal file
117
foretold/components/src/MyCommunities.re
Normal 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>;
|
||||||
|
};
|
32
foretold/components/src/NumberShower/NumberShower.re
Normal file
32
foretold/components/src/NumberShower/NumberShower.re
Normal 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>;
|
||||||
|
};
|
12
foretold/components/src/NumberShower/PercentageShower.re
Normal file
12
foretold/components/src/NumberShower/PercentageShower.re
Normal 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>;
|
||||||
|
};
|
65
foretold/components/src/NumberShower/numberShower.js
Normal file
65
foretold/components/src/NumberShower/numberShower.js
Normal 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();
|
||||||
|
}
|
200
foretold/components/src/PageCard.re
Normal file
200
foretold/components/src/PageCard.re
Normal 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>;
|
||||||
|
};
|
58
foretold/components/src/StateStatus.re
Normal file
58
foretold/components/src/StateStatus.re
Normal 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>;
|
59
foretold/components/src/SubmenuElements/PaginationButtons.re
Normal file
59
foretold/components/src/SubmenuElements/PaginationButtons.re
Normal 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)}
|
||||||
|
</>;
|
63
foretold/components/src/SubmenuElements/Tab.re
Normal file
63
foretold/components/src/SubmenuElements/Tab.re
Normal 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>;
|
51
foretold/components/src/SubmenuElements/Tab2.re
Normal file
51
foretold/components/src/SubmenuElements/Tab2.re
Normal 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>;
|
||||||
|
};
|
156
foretold/components/src/Table.re
Normal file
156
foretold/components/src/Table.re
Normal 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>;
|
39
foretold/components/src/lib/AutosizeTextareaInput.js
Normal file
39
foretold/components/src/lib/AutosizeTextareaInput.js
Normal 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,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
112
foretold/components/src/lib/GuesstimateInput.js
Normal file
112
foretold/components/src/lib/GuesstimateInput.js
Normal 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}
|
||||||
|
/>;
|
||||||
|
}
|
||||||
|
}
|
15
foretold/components/src/lib/Hooks.re
Normal file
15
foretold/components/src/lib/Hooks.re
Normal 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";
|
36
foretold/components/src/lib/ReAutosizeTextareaInput.re
Normal file
36
foretold/components/src/lib/ReAutosizeTextareaInput.re
Normal 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;
|
32
foretold/components/src/lib/ReGuesstimateInput.re
Normal file
32
foretold/components/src/lib/ReGuesstimateInput.re
Normal 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;
|
24
foretold/components/src/lib/Title.re
Normal file
24
foretold/components/src/lib/Title.re
Normal 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;
|
||||||
|
};
|
48
foretold/components/src/lib/vega/PercentilesChart.js
Normal file
48
foretold/components/src/lib/vega/PercentilesChart.js
Normal 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,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
11
foretold/components/src/lib/vega/RePercentilesChart.re
Normal file
11
foretold/components/src/lib/vega/RePercentilesChart.re
Normal 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;
|
109
foretold/components/src/lib/vega/spec-percentiles.json
Normal file
109
foretold/components/src/lib/vega/spec-percentiles.json
Normal 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"}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
15
package.json
15
package.json
|
@ -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
5
shell.nix
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
{ pkgs ? import <nixpkgs> {} }:
|
||||||
|
pkgs.mkShell {
|
||||||
|
name = "squiggle";
|
||||||
|
buildInputs = with pkgs; [ yarn yarn2nix ];
|
||||||
|
}
|
|
@ -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;
|
||||||
|
|
|
@ -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
Loading…
Reference in New Issue
Block a user