Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix 502 #503

Merged
merged 6 commits into from
Sep 30, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 10 additions & 12 deletions docs/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,18 +46,16 @@ def make_component():
# Modify the run function so when we exec the file
# instead of running a server we mount the view.
idom.run = partial(mount.add, file.stem)

with file.open() as f:
try:
exec(
f.read(),
{
"__file__": str(file),
"__name__": f"__main__.examples.{file.stem}",
},
)
except Exception as error:
raise RuntimeError(f"Failed to execute {file}") from error
try:
exec(
file.read_text(),
{
"__file__": str(file.absolute()),
"__name__": f"__main__.examples.{file.stem}",
},
)
except Exception as error:
raise RuntimeError(f"Failed to execute {file}") from error
finally:
idom.run = original_run

Expand Down
9 changes: 3 additions & 6 deletions docs/source/_static/custom.js
Original file line number Diff line number Diff line change
Expand Up @@ -1398,7 +1398,9 @@ function useJsonPatchCallback(initial) {
// We CANNOT mutate the part of the document because React checks some
// attributes of the model (e.g. model.attributes.style is checked for
// identity).
doc.current = applyNonMutativePatch(doc.current, patch);
doc.current = applyNonMutativePatch(
doc.current,
patch);
} else {
// We CAN mutate the document here though because we know that nothing above
// The patch `path` is changing. Thus, maintaining the identity for that section
Expand Down Expand Up @@ -1965,11 +1967,6 @@ function _nextReconnectTimeout(maxReconnectTimeout, mountState) {
Math.floor(Math.random() * mountState.reconnectTimeoutRange) || 1;
mountState.reconnectTimeoutRange =
(mountState.reconnectTimeoutRange + 5) % maxReconnectTimeout;
if (mountState.reconnectAttempts === 4) {
window.alert(
"Server connection was lost. Attempts to reconnect are being made in the background."
);
}
return timeout;
}

Expand Down
14 changes: 11 additions & 3 deletions docs/source/examples.rst
Original file line number Diff line number Diff line change
Expand Up @@ -62,15 +62,15 @@ you're experimenting:
Define Javascript Modules
-------------------------

Click the bars to trigger an event 👇
Shows a very simple chart implemented in vanilla Javascript:

.. example:: super_simple_chart


Material UI Slider
Material UI Button
------------------

Move the slider and see the event information update 👇
Click the button to change the indicator 👇

.. example:: material_ui_switch

Expand All @@ -83,6 +83,14 @@ Click the map to create pinned location 📍:
.. example:: pigeon_maps


Cytoscape Notework Graph
------------------------

You can move the nodes in the graph 🕸️:

.. example:: network_graph


.. Links
.. =====

Expand Down
43 changes: 43 additions & 0 deletions docs/source/examples/network_graph.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import random

import idom


react_cytoscapejs = idom.web.module_from_template(
# we need to use this template because react-cytoscapejs uses a default export
"react",
"react-cytoscapejs",
exports_default=True,
fallback="⌛",
)
Cytoscape = idom.web.export(react_cytoscapejs, "default")


@idom.component
def RandomNetworkGraph():
return Cytoscape(
{
"style": {"width": "100%", "height": "200px"},
"elements": random_network(20),
"layout": {"name": "cose"},
}
)


def random_network(number_of_nodes):
conns = []
nodes = [{"data": {"id": 0, "label": 0}}]

for src_node_id in range(1, number_of_nodes + 1):
tgt_node = random.choice(nodes)
src_node = {"data": {"id": src_node_id, "label": src_node_id}}

new_conn = {"data": {"source": src_node_id, "target": tgt_node["data"]["id"]}}

nodes.append(src_node)
conns.append(new_conn)

return nodes + conns


idom.run(RandomNetworkGraph)
15 changes: 9 additions & 6 deletions docs/source/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -70,22 +70,22 @@ A package for building responsive user interfaces in pure Python.

.. grid-item-card::

.. interactive-widget:: snake_game
.. interactive-widget:: network_graph
:no-activate-button:

.. grid-item-card::

.. interactive-widget:: slideshow
.. interactive-widget:: snake_game
:no-activate-button:

.. grid-item-card::

.. interactive-widget:: audio_player
.. interactive-widget:: slideshow
:no-activate-button:

.. grid-item-card::

.. interactive-widget:: todo
.. interactive-widget:: audio_player
:no-activate-button:

.. grid-item::
Expand All @@ -98,8 +98,6 @@ A package for building responsive user interfaces in pure Python.
.. interactive-widget:: simple_dashboard
:no-activate-button:



.. grid-item-card::

.. interactive-widget:: matplotlib_plot
Expand All @@ -109,3 +107,8 @@ A package for building responsive user interfaces in pure Python.

.. interactive-widget:: material_ui_button_on_click
:no-activate-button:

.. grid-item-card::

.. interactive-widget:: todo
:no-activate-button:
8 changes: 7 additions & 1 deletion src/client/packages/idom-client-react/src/json-patch.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,13 @@ export function useJsonPatchCallback(initial) {
// We CANNOT mutate the part of the document because React checks some
// attributes of the model (e.g. model.attributes.style is checked for
// identity).
doc.current = applyNonMutativePatch(doc.current, patch, false, false, true);
doc.current = applyNonMutativePatch(
doc.current,
patch,
false,
false,
true
);
} else {
// We CAN mutate the document here though because we know that nothing above
// The patch `path` is changing. Thus, maintaining the identity for that section
Expand Down
24 changes: 19 additions & 5 deletions src/idom/web/module.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from dataclasses import dataclass
from functools import partial
from pathlib import Path
from string import Template
from typing import Any, List, NewType, Optional, Set, Tuple, Union, overload
from urllib.parse import urlparse

Expand Down Expand Up @@ -83,6 +84,7 @@ def module_from_template(
package: str,
cdn: str = "https://esm.sh",
fallback: Optional[Any] = None,
exports_default: bool = False,
resolve_exports: bool = IDOM_DEBUG_MODE.current,
resolve_exports_depth: int = 5,
unmount_before_update: bool = False,
Expand All @@ -99,17 +101,22 @@ def module_from_template(
warning.cIt's best to author a module adhering to the
:ref:`Custom Javascript Component` interface instead.

**Templates**

- ``react``: for modules exporting React components

Parameters:
template:
The name of the framework template to use with the given ``package``
(only ``react`` is supported at the moment).
The name of the framework template to use with the given ``package``.
package:
The name of a package to load. May include a file extension (defaults to
``.js`` if not given)
cdn:
Where the package should be loaded from. The CDN must distribute ESM modules
fallback:
What to temporarilly display while the module is being loaded.
exports_default:
Whether the module has a default export.
resolve_imports:
Whether to try and find all the named exports of this module.
resolve_exports_depth:
Expand All @@ -128,7 +135,12 @@ def module_from_template(
# downstream code assumes no trailing slash
cdn = cdn.rstrip("/")

template_file_name = f"{template}{module_name_suffix(package_name)}"
template_file_name = (
template
+ (".default" if exports_default else "")
+ module_name_suffix(package_name)
)

template_file = Path(__file__).parent / "templates" / template_file_name
if not template_file.exists():
raise ValueError(f"No template for {template_file_name!r} exists")
Expand All @@ -137,7 +149,9 @@ def module_from_template(
if not target_file.exists():
target_file.parent.mkdir(parents=True, exist_ok=True)
target_file.write_text(
template_file.read_text().replace("$PACKAGE", package).replace("$CDN", cdn)
Template(template_file.read_text()).substitute(
{"PACKAGE": package, "CDN": cdn}
)
)

return WebModule(
Expand All @@ -146,7 +160,7 @@ def module_from_template(
default_fallback=fallback,
file=target_file,
export_names=(
resolve_module_exports_from_url(f"{cdn}/{package}", resolve_exports_depth)
resolve_module_exports_from_file(target_file, resolve_exports_depth)
if resolve_exports
else None
),
Expand Down
48 changes: 48 additions & 0 deletions src/idom/web/templates/react.default.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
export default from "$CDN/$PACKAGE";
export * from "$CDN/$PACKAGE";

import * as React from "$CDN/react";
import * as ReactDOM from "$CDN/react-dom";

export function bind(node, config) {
return {
create: (component, props, children) =>
React.createElement(component, wrapEventHandlers(props), ...children),
render: (element) => ReactDOM.render(element, node),
unmount: () => ReactDOM.unmountComponentAtNode(node),
};
}

function wrapEventHandlers(props) {
const newProps = Object.assign({}, props);
for (const [key, value] of Object.entries(props)) {
if (typeof value === "function") {
newProps[key] = makeJsonSafeEventHandler(value);
}
}
return newProps;
}

function makeJsonSafeEventHandler(oldHandler) {
// Since we can't really know what the event handlers get passed we have to check if
// they are JSON serializable or not. We can allow normal synthetic events to pass
// through since the original handler already knows how to serialize those for us.
return function safeEventHandler() {
oldHandler(
...Array.from(arguments).filter((value) => {
if (typeof value === "object" && value.nativeEvent) {
// this is probably a standard React synthetic event
return true;
} else {
try {
JSON.stringify(value);
} catch (err) {
console.error("Failed to serialize some event data");
return false;
}
return true;
}
})
);
};
}
Loading