diff --git a/js-lib/README.md b/js-lib/README.md index 2ebe83f..469dbe7 100644 --- a/js-lib/README.md +++ b/js-lib/README.md @@ -1,18 +1,46 @@ -// const trame = new Trame({ iframe }); -// await trame.connect({ application: 'trame' }); +# Trame iframe client library for plain JS +This library aims to simplify interaction between a trame application living inside an iframe and its iframe parent. +This work is inspired by the [official trame-client js lib](https://github.com/Kitware/trame-client/tree/master/js-lib) -// State handing -trame.state.set("a", 5); -console.log(trame.state.get("b")); -trame.state.update({ - a: 1, - b: 2, -}); +## Examples +- [Vite](./examples/vite/) + +## Usage +First you need to grab the iframe that contains your trame application. +```js +import ClientCommunicator from "@kitware/trame-iframe-client"; + +const iframe = document.getElementById("trame_app"); +const iframe_url = "http://localhost:3000"; -// Method call on Python -const result = await trame.trigger("name", [arg_0, arg_1], { kwarg_0: 1, kwarg_1: 2 }); +const trame = new ClientCommunicator(iframe, iframe_url); -// TODO - state watching -trame.state.watch(["a"], (a) => { - console.log(`a changed to ${a}`); +// set +trame.state.set("a", 2); +trame.state.set('b', 3); +trame.state.update({ + a: 2.5, + b: 3.5, + c: 4.5, }) + +// get +console.log(trame.state.get("c")); +console.log(trame.state.get('a')); + + +// simple api for state change +trame.state.watch( + ["a", "b", "c"], + (a, b, c) => { + console.log(`a(${a}) or b(${b}) or c(${c}) have changed`); + } +); + +// ----------------------------------- +// Method execution API +// ----------------------------------- + +// method execution on Python side +trame.trigger("name", ['arg_0', 'arg_1'], { kwarg_0: 1, kwarg_1: 2 }); +``` diff --git a/js-lib/examples/vite/README.md b/js-lib/examples/vite/README.md new file mode 100644 index 0000000..e5787f0 --- /dev/null +++ b/js-lib/examples/vite/README.md @@ -0,0 +1,25 @@ +# Vite project + +This example use npm package to illustrate how to use the trame iframe client. + +## Trame setup + +```bash +python3 -m venv .venv +source .venv/bin/activate +pip install trame trame-iframe +``` + +## Build the client + +```bash +cd client +npm i +npm run build +``` + +## Running example + +```bash +python ./server.py --port 3000 --server +``` diff --git a/js-lib/examples/vite/client/.gitignore b/js-lib/examples/vite/client/.gitignore new file mode 100644 index 0000000..a547bf3 --- /dev/null +++ b/js-lib/examples/vite/client/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/js-lib/examples/vite/client/counter.js b/js-lib/examples/vite/client/counter.js new file mode 100644 index 0000000..f94ba09 --- /dev/null +++ b/js-lib/examples/vite/client/counter.js @@ -0,0 +1,9 @@ +export function setupCounter(element, trame) { + trame.state.onReady(() => { + trame.state.watch(["count"], (count) => { + console.log(`count is ${count}`); + element.innerHTML = `count is ${count}`; + }); + }); + element.addEventListener("click", () => trame.trigger("add")); +} diff --git a/js-lib/examples/vite/client/index.html b/js-lib/examples/vite/client/index.html new file mode 100644 index 0000000..ddc7e32 --- /dev/null +++ b/js-lib/examples/vite/client/index.html @@ -0,0 +1,12 @@ + + + + + + Vite App + + +
+ + + diff --git a/js-lib/examples/vite/client/main.js b/js-lib/examples/vite/client/main.js new file mode 100644 index 0000000..dc27295 --- /dev/null +++ b/js-lib/examples/vite/client/main.js @@ -0,0 +1,29 @@ +import ClientCommunicator from "@kitware/trame-iframe-client"; +import "./style.css"; +import { setupCounter } from "./counter.js"; + +document.querySelector("#app").innerHTML = ` +
+

Hello Trame !

+
+ + + + +
+
+`; + +const url = "http://localhost:3000"; +const iframe = document.getElementById("trame_app"); + +iframe.addEventListener("load", () => { + const trame = new ClientCommunicator(iframe, url); + setupCounter(document.querySelector("#counter"), trame); + document + .querySelector("#play") + .addEventListener("click", () => trame.trigger("toggle_play")); + document + .querySelector("#subtract") + .addEventListener("click", () => trame.trigger("subtract")); +}); diff --git a/js-lib/examples/vite/client/package.json b/js-lib/examples/vite/client/package.json new file mode 100644 index 0000000..892060d --- /dev/null +++ b/js-lib/examples/vite/client/package.json @@ -0,0 +1,17 @@ +{ + "name": "trame", + "version": "0.0.0", + "type": "module", + "main": "main.js", + "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "vite preview" + }, + "devDependencies": { + "vite": "^5.2.0" + }, + "dependencies": { + "@kitware/trame-iframe-client": "/home/jules/projects/kitware/trame/repos/trame-iframe/js-lib/dist/trame-iframe.mjs" + } +} diff --git a/js-lib/examples/vite/client/style.css b/js-lib/examples/vite/client/style.css new file mode 100644 index 0000000..30aa814 --- /dev/null +++ b/js-lib/examples/vite/client/style.css @@ -0,0 +1,96 @@ +:root { + font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif; + line-height: 1.5; + font-weight: 400; + + color-scheme: light dark; + color: rgba(255, 255, 255, 0.87); + background-color: #242424; + + font-synthesis: none; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +a { + font-weight: 500; + color: #646cff; + text-decoration: inherit; +} +a:hover { + color: #535bf2; +} + +body { + margin: 0; + display: flex; + place-items: center; + min-width: 320px; + min-height: 100vh; +} + +h1 { + font-size: 3.2em; + line-height: 1.1; +} + +#app { + max-width: 1280px; + margin: 0 auto; + padding: 2rem; + text-align: center; +} + +.logo { + height: 6em; + padding: 1.5em; + will-change: filter; + transition: filter 300ms; +} +.logo:hover { + filter: drop-shadow(0 0 2em #646cffaa); +} +.logo.vanilla:hover { + filter: drop-shadow(0 0 2em #f7df1eaa); +} + +.card { + padding: 2em; +} + +.read-the-docs { + color: #888; +} + +button { + border-radius: 8px; + border: 1px solid transparent; + padding: 0.6em 1.2em; + font-size: 1em; + font-weight: 500; + font-family: inherit; + background-color: #1a1a1a; + cursor: pointer; + transition: border-color 0.25s; +} +button:hover { + border-color: #646cff; +} +button:focus, +button:focus-visible { + outline: 4px auto -webkit-focus-ring-color; +} + +@media (prefers-color-scheme: light) { + :root { + color: #213547; + background-color: #ffffff; + } + a:hover { + color: #747bff; + } + button { + background-color: #f9f9f9; + } +} diff --git a/js-lib/examples/vite/server.py b/js-lib/examples/vite/server.py new file mode 100644 index 0000000..701f017 --- /dev/null +++ b/js-lib/examples/vite/server.py @@ -0,0 +1,48 @@ +import asyncio +from trame.app import get_server +from trame.widgets import iframe +from trame.ui.html import DivLayout + +server = get_server() +state, ctrl = server.state, server.controller + +state.count = 1 +state.play = False + + +@state.change("count") +def count_change(count, **_): + print(f"count={count}") + + +@ctrl.trigger("add") +def add_to_count(): + state.count += 1 + + +@ctrl.trigger("subtract") +def subtract_to_count(): + state.count -= 1 + + +@ctrl.trigger("toggle_play") +def toggle_play(): + state.play = not state.play + + +async def animate(**kwargs): + while True: + await asyncio.sleep(0.5) + if state.play: + with state: + state.count += 1 + + +ctrl.on_server_ready.add_task(animate) + +with DivLayout(server) as layout: + comm = iframe.Communicator( + target_origin="http://localhost:2222", enable_rpc=True + ) + +server.start()