Skip to content
Closed
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
24 changes: 24 additions & 0 deletions .github/workflows/test_and_release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,15 @@ jobs:
npm ci
npm run build

- name: Build React App
run: |
cd react-app
npm ci
npm run lint
npm run typecheck
npm run test
npm run build

- name: Install and Run Tests
run: |
pip install .[test]
Expand Down Expand Up @@ -115,6 +124,15 @@ jobs:
npm ci
npm run build

- name: Build React App
run: |
cd react-app
npm ci
npm run lint
npm run typecheck
npm run test
npm run build

release:
needs: [pre-commit, pytest, test-npm-build]
runs-on: ubuntu-latest
Expand Down Expand Up @@ -149,6 +167,12 @@ jobs:
npm ci
npm run build

- name: Build React App
run: |
cd react-app
npm ci
npm run build

- name: Python Semantic Release
id: release
uses: relekang/python-semantic-release@v9.15.2
Expand Down
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -129,3 +129,6 @@ npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*

# image-comparison test output
tests/refs/diff_*.png
4 changes: 4 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ trame-client is the generic single page application that come with `trame <https
trame-client provides the infrastructure on the client-side (browser) to connect to a trame server, synchronize
its state with the server, make method call, load dynamically components and feed a dynamic template provided by the server.

Three clients are provided, selected through ``client_type`` on the server:
``vue2`` and ``vue3`` (Vue.js based) and ``react`` (native React, rendering a
serialized component tree — see ``react-app/README.md``).

This package is not supposed to be used by itself but rather should come as a dependency of **trame**.
For any specificity, please refer to `the trame documentation <https://kitware.github.io/trame/>`_.

Expand Down
5 changes: 5 additions & 0 deletions examples/react/r_on.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
from trame_client.widgets import html
from trame.app import get_server

server = get_server(client_type="react")
print(html.Div(trame_server=server, r_on_click_left_stop_self="hello").react_node)
30 changes: 30 additions & 0 deletions examples/react/reactive_state.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
from trame.app import get_server
from trame.widgets import html
from trame.ui.html import DivLayout

server = get_server()
server.client_type = "react"
state, ctrl = server.state, server.controller

state.count = 2
state.double = 4


@state.change("count")
def update_count(count, **kwargs):
state.double = 2 * int(count)


with DivLayout(server) as layout:
html.Div("count = {{ count }}")
html.Div("2 x count = {{ double }}")
html.Input(
type="range",
min=0,
max=10,
step=1,
r_model_number="count",
__properties=[("r_model_number", "v-model.number")],
)

server.start()
21 changes: 21 additions & 0 deletions examples/react/size_observer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
from trame.app import get_server
from trame.widgets import client, html
from trame.ui.html import DivLayout

server = get_server(client_type="react")
state = server.state


@state.change("main_size")
def size_changed(main_size, **kwargs):
print(f"main_size={main_size}")


with DivLayout(server) as layout:
layout.root.style = "height: 100vh; margin: 0; padding: 0;"
with client.SizeObserver("main_size"):
html.Div("{{ JSON.stringify(main_size?.size) }}")


if __name__ == "__main__":
server.start()
19 changes: 19 additions & 0 deletions examples/react/state_change.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
from trame.app import get_server
from trame.widgets import client, html
from trame.ui.html import DivLayout

server = get_server(client_type="react")


def a_changed():
print(f"a={server.state.a}")


with DivLayout(server) as layout:
html.Div("{{ a }}")
html.Input(type="range", r_model=("a", 1))
client.ClientStateChange(value="a", change=a_changed)


if __name__ == "__main__":
server.start()
82 changes: 82 additions & 0 deletions examples/react/trigger.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
from trame.app import get_server
from trame.widgets import html, client
from trame.ui.html import DivLayout

# -----------------------------------------------------------------------------
# Trame app
# -----------------------------------------------------------------------------

server = get_server(client_type="react")

state, ctrl = server.state, server.controller

# -----------------------------------------------------------------------------
# State setup
# -----------------------------------------------------------------------------

state.a = 1

# -----------------------------------------------------------------------------
# Methods call
# -----------------------------------------------------------------------------


def monitor_life_cycles(life_cycle):
print(f"Life cycle: {life_cycle}")


def call_method_1():
print("Server: call_method_1")
server.js_call("ref_name", "emit", "method1", "a")


def call_method_2():
print("Server: call_method_2")
ctrl.call("method2", "b")


def call_method_3():
print("Server: call_method_3")
ctrl.call("method3")


def client_exited():
print("client exited")


# -----------------------------------------------------------------------------
# UI setup
# -----------------------------------------------------------------------------

layout = DivLayout(server)

with layout:
html.Div("State a={{ a }}", style="padding: 20px;")
client_triggers = client.ClientTriggers(
ref="ref_name",
created=(monitor_life_cycles, "['created']"),
mounted=(monitor_life_cycles, "['mounted']"),
beforeDestroy=(monitor_life_cycles, "['beforeDestroy']"),
method1="window.console.log('method 1', $event)",
method2="window.console.log('method 2', $event)",
method3="window.console.log('method 3', $event)",
exit=client_exited,
)
ctrl.call = client_triggers.call

with html.Div(style="padding: 10px;") as div:
html.Button("Method 1", click=call_method_1)
html.Button("Method 2", click=call_method_2)
html.Button("Method 3", click=call_method_3)
html.Button("a+", click="a+=1")
div.add_child("")

client.LifeCycleMonitor(type="info", events=("['created', 'destroyed']",))


# -----------------------------------------------------------------------------
# start server
# -----------------------------------------------------------------------------

if __name__ == "__main__":
server.start()
31 changes: 31 additions & 0 deletions examples/test/model_react.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
from trame.app import get_server
from trame.widgets import html
from trame.ui.html import DivLayout
from trame_client.utils.testing import enable_testing

server = get_server(client_type="react")
state = server.state

state.count = 2
state.double = 4


@state.change("count")
def update_count(count, **kwargs):
state.double = 2 * int(count)


with DivLayout(server):
html.Div("count = {{ count }}", classes="countValue")
html.Div("2 x count = {{ double }}", classes="doubleValue")
html.Input(
type="range",
min=0,
max=10,
step=1,
r_model_number="count",
__properties=[("r_model_number", "v-model.number")],
)

enable_testing(server, "count", "double")
server.start()
18 changes: 18 additions & 0 deletions examples/test/reactivity_react.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
from trame.app import get_server
from trame.widgets import html
from trame.ui.html import DivLayout
from trame_client.utils.testing import enable_testing

server = get_server(client_type="react")
server.state.count = 1

with DivLayout(server):
html.Div(
"{{ count }}",
classes="countValue",
style="padding: 20px; background: red;",
)
html.Button("Add to count", classes="plusButton", click="count++")

enable_testing(server, "count")
server.start()
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ include = [
"/src/**/*.py",
"/src/trame_client/module/vue2-www/**",
"/src/trame_client/module/vue3-www/**",
"/src/trame_client/module/react-www/**",
"src/trame_client/module/user_provided_scripts/**",
]

Expand Down
43 changes: 43 additions & 0 deletions react-app/.eslintrc.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/* eslint-env node */
require("@rushstack/eslint-patch/modern-module-resolution");

module.exports = {
root: true,
extends: [
"eslint:recommended",
"plugin:react/recommended",
"plugin:react/jsx-runtime",
"plugin:react-hooks/recommended",
"prettier",
],
overrides: [
{
files: ["*.ts", "*.tsx"],
parser: "@typescript-eslint/parser",
plugins: ["@typescript-eslint"],
extends: ["plugin:@typescript-eslint/recommended"],
rules: {
"@typescript-eslint/no-explicit-any": "off",
},
},
],
settings: {
react: {
version: "detect",
},
},
parserOptions: {
ecmaVersion: "latest",
sourceType: "module",
ecmaFeatures: {
jsx: true,
},
},
env: {
browser: true,
es2022: true,
},
rules: {
"react/prop-types": "off",
},
};
29 changes: 29 additions & 0 deletions react-app/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*

node_modules
.DS_Store
dist
dist-ssr
coverage
*.local

/cypress/videos/
/cypress/screenshots/

# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
.venv
1 change: 1 addition & 0 deletions react-app/.prettierrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{}
35 changes: 35 additions & 0 deletions react-app/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# trame react client

Native React client for trame (`client_type="react"`). Renders the JSON
component tree produced by `trame_client/utils/react.py` with
`React.createElement` — no Vue, no iframe.

## Build the web client

```bash
npm install
npm run build # outputs to ../src/trame_client/module/react-www
```

## Develop

```bash
npm run test # vitest renderer/unit tests
npm run lint # eslint + prettier
npm run debug # build with sourcemaps
```

## Architecture

- `src/renderer/` - JSON tree -> React elements (expression evaluation against
the trame shared state, `r_*` structural directives, scoped slots)
- `src/registry.js` - tag -> React component map; widget libraries register
through `plugin.install(registry)` (declared via the `react_use` module key)
- `src/components/` - built-in trame components (TrameApp, TrameTemplate,
TrameGetter, ...)
- `src/core/` - framework-agnostic wslink client, shared state mirror and
attachment decorators (shared lineage with vue2-app/vue3-app)

Widget library bundles must build with `react`/`react-dom` as externals; the
client exposes the page's single React instance as `window.React` /
`window.ReactDOM`.
Loading
Loading