Commit 797e8e17 authored by Laurent Wouters's avatar Laurent Wouters
Browse files

Initial import

parent 6a073851fb76
......@@ -18,7 +18,9 @@
"suffix": ".bs.js",
"namespace": true,
"bs-dependencies": [
"reason-react"
"reason-react",
"bs-fetch",
"bs-json"
],
"refmt": 3
}
......@@ -6,6 +6,7 @@
"start": "bsb -make-world -w",
"clean": "bsb -clean-world",
"test": "echo \"Error: no test specified\" && exit 1",
"serve": "bsb -clean-world && bsb -make-world && webpack && webpack-dev-server",
"webpack": "webpack -w",
"webpack:production": "NODE_ENV=production webpack"
},
......@@ -18,11 +19,15 @@
"react": "^16.2.0",
"react-dom": "^16.2.0",
"reason-react": ">=0.4.0",
"bootstrap": "^3.3.7"
"bs-fetch": "^0.3.0",
"bs-json": "^1.0.1",
"bootstrap": "^4.0.0"
},
"devDependencies": {
"bs-platform": "^3.1.5",
"webpack": "^4.0.1",
"webpack-cli": "^2.0.10"
"webpack-cli": "^2.0.10",
"webpack-dev-server": "^3.0.0",
"html-webpack-plugin": "^3.0.0"
}
}
ReactDOMRe.renderToElementWithId(<App message="Hello!" />, "app");
ReactDOMRe.renderToElementWithId(<App />, "app");
\ No newline at end of file
This diff is collapsed.
/* This is the basic component. */
let component = ReasonReact.statelessComponent("Page");
type state =
| ShowingRoot
| ShowingCollection(string);
/* Your familiar handleClick from ReactJS. This mandatorily takes the payload,
then the `self` record, which contains state (none here), `handle`, `reduce`
and other utilities */
let handleClick = (_event, _self) => Js.log("clicked!");
type action =
| GoToRoot
| GoToCollection(string);
/* `make` is the function that mandatorily takes `children` (if you want to use
`JSX). `message` is a named argument, which simulates ReactJS props. Usage:
let component = ReasonReact.reducerComponent("Page");
`<Page message="hello" />`
let handleGoToCollection = (_event, _self, link: Hypermedia.schemaLink) =>
ReasonReact.Router.push(link.href);
Which desugars to
let handleGoToEntity = (_event, _self, entity_name, id) =>
ReasonReact.Router.push("/" ++ entity_name ++ "/" ++ id);
`ReasonReact.element(Page.make(~message="hello", [||]))` */
let make = (~message, _children) => {
let make = _children => {
...component,
didMount: self => {
let watcherID =
ReasonReact.Router.watchUrl(url =>
switch (url.path) {
| [first] => self.send(GoToCollection(first))
| _ => self.send(GoToRoot)
}
);
self.onUnmount(() => ReasonReact.Router.unwatchUrl(watcherID));
},
initialState: () => {
let url = ReasonReact.Router.dangerouslyGetInitialUrl();
switch (url.path) {
| [first] => ShowingCollection(first)
| _ => ShowingRoot
};
},
reducer: (action, _state) =>
switch (action) {
| GoToRoot => ReasonReact.Update(ShowingRoot)
| GoToCollection(name) => ReasonReact.Update(ShowingCollection(name))
},
render: self =>
<div onClick=(self.handle(handleClick))>
(ReasonReact.string(message))
</div>,
};
switch (self.state) {
| ShowingRoot => <Root gotoCollectionHandler=handleGoToCollection />
| ShowingCollection(x) => <Collection gotoEntityHandler=handleGoToEntity name=x />
},
};
\ No newline at end of file
type state =
| Loading
| PartialPreviews(list(Hypermedia.entityPreview))
| PartialSchema(Hypermedia.arraySchema)
| Ready(list(Hypermedia.entityPreview))
| Failed;
type action =
| LoadingFailed
| LoadedPreviews(list(Hypermedia.entityPreview))
| LoadedSchema(Hypermedia.arraySchema);
let fetchPreviews = name =>
Js.Promise.(
Fetch.fetchWithInit(
Hypermedia.baseUrl ++ "/" ++ name ++ "/",
Fetch.RequestInit.make(
~headers=Fetch.HeadersInit.make(Hypermedia.headersForResources),
(),
),
)
|> then_(Fetch.Response.json)
|> then_(json =>
json
|> Hypermedia.Decode.entityPreviews
|> (previews => Some(previews) |> resolve)
)
|> catch(_err => resolve(None))
);
let component = ReasonReact.reducerComponent("Collection");
let make = (~gotoEntityHandler, ~name, _children) => {
...component,
initialState: () => Loading,
didMount: self =>
Js.Promise.(
fetchPreviews(name)
|> then_(result =>
switch (result) {
| Some(previews) => resolve(self.send(LoadedPreviews(previews)))
| None => resolve(self.send(LoadingFailed))
}
)
|> ignore
),
reducer: (action, state) =>
switch (action) {
| LoadingFailed => ReasonReact.Update(Failed)
| LoadedPreviews(previews) =>
switch (state) {
| Loading => ReasonReact.Update(Ready(previews))
| PartialSchema(schema) => ReasonReact.Update(Ready(previews))
| _ => ReasonReact.NoUpdate
}
| LoadedSchema(schema) =>
switch (state) {
| Loading => ReasonReact.Update(PartialSchema(schema))
| PartialPreviews(previews) => ReasonReact.Update(Ready(previews))
| _ => ReasonReact.NoUpdate
}
},
render: self =>
switch (self.state) {
| Ready(previews) =>
<div>
<h1 />
(
previews
|> List.map((preview: Hypermedia.entityPreview) =>
<li>
<a
href=("/" ++ name ++ "/" ++ preview.id)
onClick=(
self.handle((event, self) =>
gotoEntityHandler(event, self, name, preview.id)
)
)>
(ReasonReact.string(preview.title))
</a>
</li>
)
|> Array.of_list
|> ReasonReact.array
)
</div>
| Failed => <div> (ReasonReact.string("Failed")) </div>
| _ => <div> (ReasonReact.string("Loading")) </div>
},
};
\ No newline at end of file
/* State declaration */
type state = {
count: int,
show: bool,
};
/* Action declaration */
type action =
| Click
| Toggle;
/* Component template declaration.
Needs to be **after** state and action declarations! */
let component = ReasonReact.reducerComponent("Example");
/* greeting and children are props. `children` isn't used, therefore ignored.
We ignore it by prepending it with an underscore */
let make = (~greeting, _children) => {
/* spread the other default fields of component here and override a few */
...component,
initialState: () => {count: 0, show: true},
/* State transitions */
reducer: (action, state) =>
switch (action) {
| Click => ReasonReact.Update({...state, count: state.count + 1})
| Toggle => ReasonReact.Update({...state, show: ! state.show})
},
render: self => {
let message =
"You've clicked this " ++ string_of_int(self.state.count) ++ " times(s)";
<div>
<button onClick=(_event => self.send(Click))>
(ReasonReact.string(message))
</button>
<button onClick=(_event => self.send(Toggle))>
(ReasonReact.string("Toggle greeting"))
</button>
(self.state.show ? ReasonReact.string(greeting) : ReasonReact.null)
</div>;
},
};
type state =
| Loading
| Ready(Hypermedia.rootSchema)
| Failed;
type action =
| LoadingFailed
| Loaded(Hypermedia.rootSchema);
let fetchRootSchema = () =>
Js.Promise.(
Fetch.fetchWithInit(
Hypermedia.baseUrl ++ "/schema",
Fetch.RequestInit.make(
~headers=Fetch.HeadersInit.make(Hypermedia.headersForSchema),
(),
),
)
|> then_(Fetch.Response.json)
|> then_(json =>
json
|> Hypermedia.Decode.rootSchema
|> (rootSchema => Some(rootSchema) |> resolve)
)
|> catch(_err => resolve(None))
);
let component = ReasonReact.reducerComponent("Root");
let make = (~gotoCollectionHandler, _children) => {
...component,
initialState: () => Loading,
didMount: self =>
Js.Promise.(
fetchRootSchema()
|> then_(result =>
switch (result) {
| Some(rootSchema) => resolve(self.send(Loaded(rootSchema)))
| None => resolve(self.send(LoadingFailed))
}
)
|> ignore
),
reducer: (action, _state) =>
switch (action) {
| LoadingFailed => ReasonReact.Update(Failed)
| Loaded(schema) => ReasonReact.Update(Ready(schema))
},
render: self =>
switch (self.state) {
| Loading => <div> (ReasonReact.string("Loading")) </div>
| Ready(schema) =>
<div>
<h1> (ReasonReact.string(schema.title)) </h1>
(
schema.links
|> List.map((link: Hypermedia.schemaLink) =>
<li>
<a
href=link.href
onClick=(
self.handle((event, self) =>
gotoCollectionHandler(event, self, link)
)
)>
(ReasonReact.string(link.title))
</a>
</li>
)
|> Array.of_list
|> ReasonReact.array
)
</div>
| Failed => <div> (ReasonReact.string("Failed")) </div>
},
};
\ No newline at end of file
......@@ -3,13 +3,11 @@
<head>
<meta charset="UTF-8">
<title>Cubicweb</title>
<link rel="stylesheet" type="text/css" href="bootstrap.min.css">
<title><%= htmlWebpackPlugin.options.title %></title>
</head>
<body>
<div id="app"></div>
<script src="../build/Index.js"></script>
</body>
</html>
\ No newline at end of file
type reference = {ref: string};
type schemaLink = {
href: string,
rel: string,
submissionSchema: reference,
targetSchema: reference,
title: string,
};
type rootSchema = {
links: list(schemaLink),
title: string,
};
type entityPreview = {
id: string,
title: string,
meta: string,
};
type itemLink = {
anchor: string,
href: string,
rel: string,
};
type valueSchema =
| Primitive(string)
| Object(objectSchema)
| Array(arraySchema)
and propertySchema = {
name: string,
meta: valueSchema,
}
and objectSchema = {
links: list(itemLink),
properties: list(propertySchema),
}
and arraySchema = {
title: string,
links: list(schemaLink),
items: valueSchema,
};
type propertyValue =
| Simple(string)
| Collection(list(string));
type property = {
name: string,
value: propertyValue,
};
type entity = {
id: string,
properties: Js.Dict.t(propertyValue),
};
module Decode = {
let reference = json => Json.Decode.{ref: json |> field("$ref", string)};
let schemaLink = json =>
Json.Decode.{
href: json |> field("href", string),
rel: json |> field("rel", string),
submissionSchema: json |> field("submissionSchema", reference),
targetSchema: json |> field("targetSchema", reference),
title: json |> field("title", string),
};
let rootSchema = json =>
Json.Decode.{
title: json |> field("title", string),
links: json |> field("links", Json.Decode.list(schemaLink)),
};
let entityPreview = json =>
Json.Decode.{
id: json |> field("id", string),
title: json |> field("title", string),
meta: json |> field("type", string),
};
let entityPreviews = json => Json.Decode.list(entityPreview, json);
let propertyValue = (json: Js.Json.t) =>
if (Js_array.isArray(json)) Json.Decode.list(Json.Decode.string, json)
else Json.Decode.string(json);
};
let baseUrl = "http://localhost:6543";
let headersForSchema = [%bs.raw
{|
{
"Content-Type": "application/schema+json",
"Accept": "application/schema+json"
}
|}
];
let headersForResources = [%bs.raw
{|
{
"Content-Type": "application/json",
"Accept": "application/json"
}
|}
];
\ No newline at end of file
const path = require('path');
const outputDir = path.join(__dirname, "build/");
const HtmlWebpackPlugin = require('html-webpack-plugin');
const isProd = process.env.NODE_ENV === 'production';
......@@ -11,4 +12,17 @@ module.exports = {
publicPath: outputDir,
filename: 'Index.js',
},
plugins: [
new HtmlWebpackPlugin({
title: "Cubicweb client",
template: path.join(__dirname, 'src', 'index.hbs')
})
],
devServer: {
contentBase: path.join(__dirname, 'build'),
compress: true,
historyApiFallback: {
index: 'index.html'
}
}
};
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment