Skip to content

Commit 19e21c4

Browse files
committed
minor cleanup + update intro notebook
at the moment the shared state server is not working
1 parent 929d07f commit 19e21c4

File tree

12 files changed

+192
-117
lines changed

12 files changed

+192
-117
lines changed

examples/chart.js

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import React from "../web_modules/react.js";
2+
import {
3+
VictoryBar,
4+
VictoryChart,
5+
VictoryTheme,
6+
Bar,
7+
} from "../web_modules/victory.js";
8+
import htm from "../web_modules/htm.js";
9+
10+
const html = htm.bind(React.createElement);
11+
12+
export default {
13+
ClickableChart: function ClickableChart(props) {
14+
return html`
15+
<${VictoryChart}
16+
theme=${VictoryTheme.material}
17+
style=${props.style}
18+
domainPadding=${20}
19+
>
20+
<${VictoryBar}
21+
data=${[
22+
{ x: 1, y: 2 },
23+
{ x: 2, y: 4 },
24+
{ x: 3, y: 7 },
25+
{ x: 4, y: 3 },
26+
{ x: 5, y: 5 },
27+
]}
28+
dataComponent=${html`
29+
<${Bar}
30+
events=${{
31+
onClick: props.onClick,
32+
}}
33+
/>
34+
`}
35+
/>
36+
<//>
37+
`;
38+
},
39+
};

examples/example_utils.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,20 @@ def example_uri_root(protocol: str, port: int) -> str:
1818
return "%s://127.0.0.1:%s" % (protocol, port)
1919

2020

21-
class html_link:
21+
def is_on_jupyterhub() -> bool:
22+
return (
23+
"JUPYTER_SERVER_URL" in os.environ
24+
or "JUPYTERHUB_OAUTH_CALLBACK_URL" in os.environ
25+
)
26+
27+
28+
class HtmlLink:
2229
def __init__(self, href: str, text: Optional[str] = None):
2330
self.href, self.text = href, text
2431

32+
def __str__(self) -> str:
33+
return self.href
34+
2535
def _repr_html_(self) -> str:
2636
return f"<a href='{self.href}' target='_blank'>{self.text or self.href}</a>"
2737

examples/introduction.ipynb

Lines changed: 54 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -70,18 +70,25 @@
7070
"outputs": [],
7171
"source": [
7272
"from idom.server.sanic import PerClientState\n",
73-
"from example_utils import example_uri_root, html_link, pretty_dict_string\n",
73+
"from example_utils import (\n",
74+
" example_uri_root,\n",
75+
" HtmlLink,\n",
76+
" pretty_dict_string,\n",
77+
" is_on_jupyterhub,\n",
78+
")\n",
7479
"\n",
75-
"websocket_url = example_uri_root(\"ws\", 8765) + \"/stream\"\n",
76-
"webpage_url = html_link(example_uri_root(\"http\", 8765) + \"/client/index.html\")\n",
80+
"webpage_url = HtmlLink(example_uri_root(\"http\", 8765))\n",
7781
"\n",
7882
"mount, element = idom.hotswap()\n",
79-
"PerClientState(element).daemon(\"127.0.0.1\", 8765, access_log=False)\n",
83+
"PerClientState(element).configure({\"cors\": not is_on_jupyterhub()}).daemon(\n",
84+
" \"127.0.0.1\", 8765, access_log=False\n",
85+
")\n",
86+
"\n",
8087
"\n",
8188
"def display(*args, **kwargs):\n",
8289
" element, *args = args\n",
8390
" mount(element, *args, **kwargs)\n",
84-
" return idom.display(\"jupyter\", websocket_url)"
91+
" return idom.display(\"jupyter\", str(webpage_url), secure=is_on_jupyterhub())"
8592
]
8693
},
8794
{
@@ -653,7 +660,7 @@
653660
"print()\n",
654661
"print(\"Slow internet may cause inconsistent frame pacing 😅\")\n",
655662
"\n",
656-
"display(GameView, 8, 50)"
663+
"display(GameView, 7, 50)"
657664
]
658665
},
659666
{
@@ -662,9 +669,7 @@
662669
"source": [
663670
"# Importing Javascript - Leveraging The Ecosystem\n",
664671
"\n",
665-
"With IDOM, you're able to leverage existing Javascript libraries by specifying packages that you'd like to import and the css needed to style the components. In the following example we display a [date picker](https://ant.design/components/date-picker/) from the [Ant Design System](https://ant.design/) and register a callback to it. The cool part here is that the `onChange` callback conforms to the [specification](https://ant.design/components/date-picker/#DatePicker) defined by Ant and handles two arguments instead of one containing a DOM event dictionary.\n",
666-
"\n",
667-
"Since we've seen what's possible with IDOM all we'll do is print out the event information."
672+
"With IDOM, you're able to leverage existing Javascript libraries by specifying React-based packages that you'd like to install and import. It's worth noting that not every package will work with IDOM since the packages source must be compilable to ES6 using [Snowpack](https://www.snowpack.dev/), many should just work out of the box though. In the following example we display a [Bar Chart](https://formidable.com/open-source/victory/docs/victory-bar/) from the [Victory Chart Library](https://formidable.com/open-source/victory/) using its default data."
668673
]
669674
},
670675
{
@@ -673,38 +678,48 @@
673678
"metadata": {},
674679
"outputs": [],
675680
"source": [
676-
"antd = idom.Import(\"https://dev.jspm.io/antd\", fallback=\"Loading...\")\n",
681+
"print(\"It may take a moment to download and install Victory... 🕒\")\n",
677682
"\n",
683+
"victory = idom.Import(\"victory\", fallback=\"loading...\", install=True)\n",
678684
"\n",
679-
"@idom.element\n",
680-
"async def AntDatePicker(self):\n",
685+
"display(victory.VictoryBar, {\"style\": {\"parent\": {\"width\": \"500px\"}}})"
686+
]
687+
},
688+
{
689+
"cell_type": "markdown",
690+
"metadata": {},
691+
"source": [
692+
"## Making Your Own React Components\n",
681693
"\n",
682-
" async def changed(moment, datestring):\n",
683-
" print(\"CLIENT DATETIME:\", moment)\n",
684-
" print(\"PICKED DATETIME:\", datestring)\n",
694+
"While using existing Javascript packages out of the box is great, sometimes you need to create your own native Javascript modules. This might be because a package you installed doesn't play well with IDOM, or because you need to create your own custom logic that can only be achieved using client-side Javascript. In the case of Victory, it's actually both - unfortunately Victory's higher level components can't be constructed purely using VDOM in Python in addition to the fact that many of its more complex features require writing custom Javascript hooks. \n",
685695
"\n",
686-
" return idom.html.div(\n",
687-
" [\n",
688-
" idom.html.link({\n",
689-
" \"rel\": \"stylesheet\", \"type\": \"text/css\", \"href\": \"https://dev.jspm.io/antd/dist/antd.css\"\n",
690-
" }),\n",
691-
" module.DatePicker({\"onChange\": changed}),\n",
692-
" ]\n",
693-
" )\n",
696+
"Thankfully IDOM allows you to write your own Javascript modules the import and use packages you've installed. In the example below we created a module `chart.js` that exports a `VictoryBar` chart with some extra styling and an `onClick` event adapter that allows Python and Victory to interoperate."
697+
]
698+
},
699+
{
700+
"cell_type": "code",
701+
"execution_count": null,
702+
"metadata": {},
703+
"outputs": [],
704+
"source": [
705+
"print(\"Click the bars to see event data printed in Python 👇\")\n",
694706
"\n",
707+
"chart = idom.widgets.import_module(\"chart\", \"chart.js\")\n",
695708
"\n",
696-
"print(\"Select a date to see the event print-out 📅\")\n",
709+
"async def handle_event(event):\n",
710+
" print(event)\n",
697711
"\n",
698-
"display(AntDatePicker)"
712+
"display(\n",
713+
" chart.ClickableChart,\n",
714+
" {\"onClick\": handle_event, \"style\": {\"parent\": {\"width\": \"500px\"}}},\n",
715+
")"
699716
]
700717
},
701718
{
702719
"cell_type": "markdown",
703720
"metadata": {},
704721
"source": [
705-
"## More Ant Design\n",
706-
"\n",
707-
"Here's a bunch of other fun components that we can use from Ant Design."
722+
"Here's the source that was used to generate the chart above"
708723
]
709724
},
710725
{
@@ -713,33 +728,8 @@
713728
"metadata": {},
714729
"outputs": [],
715730
"source": [
716-
"@idom.element\n",
717-
"async def MoreAntDesign(self):\n",
718-
" \n",
719-
" async def changed(*data):\n",
720-
" print(*data)\n",
721-
"\n",
722-
" return idom.html.div([\n",
723-
" antd.Button({\"onClick\": changed}),\n",
724-
" antd.Switch({\"onChange\": changed}),\n",
725-
" antd.TimePicker({\"onChange\": changed}),\n",
726-
" antd.Progress(\n",
727-
" {\n",
728-
" \"percent\": \"90\",\n",
729-
" \"status\": \"active\",\n",
730-
" \"strokeColor\": {\n",
731-
" \"0%\": \"#108ee9\",\n",
732-
" \"100%\": \"#87d068\",\n",
733-
" },\n",
734-
" }\n",
735-
" )\n",
736-
" ]\n",
737-
" )\n",
738-
"\n",
739-
"\n",
740-
"print(\"A bunch more examples 🎉\")\n",
741-
"\n",
742-
"display(MoreAntDesign)"
731+
"with open(\"chart.js\") as f:\n",
732+
" print(f.read())"
743733
]
744734
},
745735
{
@@ -781,15 +771,18 @@
781771
"source": [
782772
"from idom.server.sanic import SharedClientState\n",
783773
"\n",
784-
"shared_websocket_url = example_uri_root(\"ws\", 5678) + \"/stream\"\n",
785-
"shared_webpage_url = html_link(example_uri_root(\"http\", 5678) + \"/client/index.html\")\n",
774+
"shared_webpage_url = HtmlLink(example_uri_root(\"http\", 5678))\n",
786775
"\n",
787-
"mount_shared, element = idom.hotswap(shared=True)\n",
788-
"SharedClientState(element).daemon(\"127.0.0.1\", 5678, access_log=False)\n",
776+
"mount_shared, element = idom.hotswap()\n",
777+
"SharedClientState(element).configure({\"cors\": not is_on_jupyterhub()}).daemon(\n",
778+
" \"127.0.0.1\", 5678, access_log=False\n",
779+
")\n",
789780
"\n",
790-
"def shared_display(element, *args, **kwargs):\n",
781+
"\n",
782+
"def shared_display(*args, **kwargs):\n",
783+
" element, *args = args\n",
791784
" mount_shared(element, *args, **kwargs)\n",
792-
" return idom.display(\"jupyter\", shared_websocket_url)"
785+
" return idom.display(\"jupyter\", str(shared_webpage_url), secure=is_on_jupyterhub())"
793786
]
794787
},
795788
{

idom/client/__init__.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
CORE_MODULES = CLIENT_DIR / "core_modules"
1313
NODE_MODULES = CLIENT_DIR / "node_modules"
1414
WEB_MODULES = CLIENT_DIR / "web_modules"
15-
ETC_MODULES = CLIENT_DIR / "etc_modules"
15+
USER_MODULES = CLIENT_DIR / "user_modules"
1616

1717

1818
def import_path(prefix: str, name: str) -> str:
@@ -23,10 +23,10 @@ def import_path(prefix: str, name: str) -> str:
2323

2424

2525
def define_module(name: str, source: str) -> str:
26-
path = _create_module_os_path(ETC_MODULES, name)
26+
path = _create_module_os_path(USER_MODULES, name)
2727
with path.open("w+") as f:
2828
f.write(source)
29-
return import_path("etc_modules", name)
29+
return import_path("user_modules", name)
3030

3131

3232
def delete_module(prefix: str, name: str) -> None:
@@ -65,7 +65,7 @@ def install(dependencies: Dict[str, str]) -> None:
6565

6666

6767
def restore() -> None:
68-
_delete_os_paths(WEB_MODULES, NODE_MODULES, ETC_MODULES)
68+
_delete_os_paths(WEB_MODULES, NODE_MODULES, USER_MODULES)
6969
_run_subprocess(["npm", "install"], CLIENT_DIR)
7070
_run_subprocess(["npm", "run", "snowpack"], CLIENT_DIR)
7171

idom/client/core_modules/layout.js

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,7 @@ function Layout({ endpoint }) {
2929
endpoint = new_uri + endpoint;
3030
}
3131

32-
const socket = useMemo(() => {
33-
return new WebSocket(endpoint);
34-
}, [endpoint]);
35-
32+
const socket = useMemo(() => new WebSocket(endpoint), [endpoint]);
3633
const [modelState, setModelState] = useState({ root: null, models: {} });
3734

3835
socket.onmessage = (event) => {
@@ -52,7 +49,7 @@ function Layout({ endpoint }) {
5249
});
5350
};
5451

55-
if (modelState.root && modelState.root) {
52+
if (modelState.root && modelState.models[modelState.root]) {
5653
return html`<${Element}
5754
modelState=${modelState}
5855
model=${modelState.models[modelState.root]}
@@ -115,7 +112,7 @@ function elementAttributes(model, sendEvent) {
115112
if (model.eventHandlers) {
116113
Object.keys(model.eventHandlers).forEach((eventName) => {
117114
const eventSpec = model.eventHandlers[eventName];
118-
attributes[eventName] = function (event) {
115+
attributes[eventName] = function eventHandler(event) {
119116
const data = Array.from(arguments).map((value) => {
120117
if (typeof value === "object" && value.nativeEvent) {
121118
if (eventSpec["preventDefault"]) {

idom/client/core_modules/lazy-component.js

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,15 +15,17 @@ function lazyComponent(model) {
1515
if (!error.stack) {
1616
throw error;
1717
} else {
18+
console.log(error);
1819
return {
1920
default: function Catch() {
2021
return html`
21-
<pre>
22-
<code>${error.stack}</code>
23-
</pre
24-
>
25-
`;
26-
}
22+
<pre>
23+
<h1>Error</h1>
24+
<code>${[error.stack, error.message]}</code>
25+
</pre
26+
>
27+
`;
28+
},
2729
};
2830
}
2931
}

idom/client/index.html

Lines changed: 23 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,29 @@
11
<!DOCTYPE html>
22
<html lang="en">
3+
<head>
4+
<meta charset="utf-8" />
5+
<link rel="shortcut icon" href="favicon.ico" />
6+
<title>IDOM</title>
7+
</head>
8+
<body>
9+
<div id="app"></div>
10+
<script type="module">
11+
import { renderLayout } from "./core_modules/layout.js";
312

4-
<head>
5-
<meta charset="utf-8" />
6-
<link rel="shortcut icon" href="favicon.ico" />
7-
<title>IDOM</title>
8-
</head>
13+
const uri = document.location.hostname + ":" + document.location.port;
14+
const url = (uri + document.location.pathname).split("/").slice(0, -1);
15+
url[url.length - 1] = "stream";
16+
const secure = document.location.protocol === "https:";
917

10-
<body>
11-
<div id="app"></div>
12-
<script type="module">
13-
import { renderLayout } from "./core_modules/layout.js";
14-
15-
const uri = document.location.hostname + ":" + document.location.port;
16-
const url = (uri + document.location.pathname).split("/").slice(0, -1);
17-
url[url.length - 1] = "stream";
18-
const secure = document.location.protocol === "https:";
19-
20-
let protocol;
21-
if (secure) {
22-
protocol = "wss:";
23-
} else {
24-
protocol = "ws:";
25-
}
26-
let endpoint = protocol + "//" + url.join("/");
27-
28-
renderLayout(document.getElementById("app"), endpoint);
29-
</script>
30-
</body>
18+
let protocol;
19+
if (secure) {
20+
protocol = "wss:";
21+
} else {
22+
protocol = "ws:";
23+
}
24+
let endpoint = protocol + "//" + url.join("/");
3125

26+
renderLayout(document.getElementById("app"), endpoint);
27+
</script>
28+
</body>
3229
</html>

idom/client/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
"scripts": {
1212
"it": "./node_modules/.bin/npx",
1313
"snowpack": "npm run it -- snowpack",
14-
"lint": "npm run it -- prettier --write ."
14+
"lint": "npm run it -- prettier --write ./core_modules"
1515
},
1616
"devDependencies": {
1717
"npx": "^10.2.2",

0 commit comments

Comments
 (0)