Files
wehub-resource-sync 7df9ebf22c
Sync labels / sync (push) Failing after 1m9s
Publish Container Image / Build and publish container image (push) Has been cancelled
Deploy Website / Deploy to GitHub Pages (push) Has been cancelled
Deploy Website / Build website and demo (push) Has been cancelled
Deploy viewer / Deploy viewer proxy to Cloudflare Workers (push) Has been cancelled
Deploy tiles / Deploy planetary tile proxy to Cloudflare Workers (push) Has been cancelled
Deploy collab / Deploy collaboration relay to Cloudflare Workers (push) Has been cancelled
CI / Dependency audit (push) Has been cancelled
CI / E2E smoke (Playwright) (push) Has been cancelled
CI / Validate CITATION.cff (push) Has been cancelled
CI / Build and test (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:33:00 +08:00

102 lines
4.8 KiB
Markdown

# Notebook panel
GeoLibre can run a Jupyter notebook **beside the map**, in a resizable,
collapsible panel (open it from **Processing → Jupyter Notebook**). It works in
both the web and desktop builds, with a different Python runtime behind the same
`<iframe>` panel:
- **Web build → [JupyterLite](https://jupyterlite.readthedocs.io).** A full
Jupyter UI running entirely in the browser on a [Pyodide](https://pyodide.org)
(WebAssembly) kernel — no server. Install extra packages on demand from a cell
with `%pip install geopandas`.
- **Desktop build → a real JupyterLab server.** The app launches `jupyter lab`
from a uv-managed Python environment (the same mechanism as the FastAPI
sidecar) on `127.0.0.1:8766`, token-authenticated, and embeds it. This gives
full CPython with the native geospatial stack (geopandas, rasterio, GDAL, …).
This is distinct from the [`geolibre` Python package](python.md), which does the
inverse — embedding the *whole GeoLibre app* inside a notebook cell.
## Scripting the map from a cell
A lightweight `geolibre` client is preloaded into the notebook kernel — on web
it is bundled into JupyterLite's filesystem, so **no `pip install` is needed**;
on desktop it is placed on the kernel's import path. Just import it:
```python
import geolibre
m = geolibre.connect() # or geolibre.Map()
m.fly_to(-122.4, 37.8, zoom=11) # animate the live map in the left pane
m.add_geojson(gdf, name="My layer") # GeoDataFrame, dict, or JSON string
m.fit_bounds([-123, 37, -122, 38])
m.set_basemap("https://…/style.json")
m.set_visibility(layer_id, False)
m.remove_layer(layer_id)
```
Calls are **fire-and-forget**: each posts a command to the host app over the
shared scripting protocol (the same `createScriptingHandlers` surface used by the
in-app Python console and the Jupyter widget) and returns immediately, so the
client behaves identically on the in-browser kernel and a real server. The
app-side endpoint is `useNotebookBridge` in
`apps/geolibre-desktop/src/hooks/`. Canonical client source:
`backend/geolibre_server/notebook_client.py`.
> Read-back queries (e.g. `get_center`) are not exposed by this fire-and-forget
> client; they need the blocking request/reply path the `geolibre` widget uses.
## Theme
On the **web build** the notebook follows the app's light/dark theme live (no
reload). JupyterLite is built with `exposeAppInBrowser` (see
`apps/geolibre-desktop/jupyterlite/jupyter-lite.json`), which puts its app object
on `window.jupyterapp`; since the JupyterLite iframe is same-origin,
`useNotebookThemeSync` reaches in and runs the built-in `apputils:change-theme`
command whenever the app theme changes. On the **desktop build** the JupyterLab
server is a different origin, so this cross-origin call is a no-op and the
notebook keeps its own theme (syncing it there would need a small JupyterLab
extension — a future enhancement).
## Building the web JupyterLite site
The web build embeds a self-hosted JupyterLite site under
`apps/geolibre-desktop/public/jupyterlite/`. It is **not** committed (≈70 MB) and
is generated by a best-effort prebuild step that needs the `jupyter lite` CLI:
```bash
pip install -r apps/geolibre-desktop/jupyterlite/requirements.txt
npm run build:jupyterlite
```
Both `npm run dev` and `npm run build` run this automatically:
- `npm run dev` builds it **once** on first run via a `predev` hook
(`--if-missing`), then is instant on subsequent runs.
- `npm run build` always rebuilds it (via `prebuild`) so a changed client/config
is picked up.
Both **skip gracefully** when `jupyter lite` is not installed — a Node-only build
still succeeds and the web Notebook panel shows a "not built" message until the
site is generated (install the deps above and re-run). The desktop (Tauri) dev
and build paths skip it entirely (they use the real JupyterLab server), so the
static site never bloats the installer.
Build config lives in `apps/geolibre-desktop/jupyterlite/` (a
`jupyter_lite_config.json`, the build `requirements.txt`, and a starter
`files/Welcome.ipynb`). The generated directory is excluded from the PWA
precache (see `pwaPlugin` in `apps/geolibre-desktop/vite.config.ts`).
## Desktop server
- Backend extra: `notebook` in `backend/geolibre_server/pyproject.toml`
(`jupyterlab`, `jupyter-server`), synced into its own uv project environment so
it never disturbs the sidecar's env.
- Launcher / lifecycle: `start_jupyter_server` / `stop_jupyter_server` Tauri
commands in `apps/geolibre-desktop/src-tauri/src/lib.rs` (mirrors the sidecar
launcher), with the TS wrapper in `apps/geolibre-desktop/src/lib/jupyter.ts`.
- Framing: `backend/geolibre_server/jupyter_server_config.py` relaxes
`Content-Security-Policy: frame-ancestors` to the Tauri webview / loopback
origins so the app can embed the server; the Tauri CSP (`tauri.conf.json`)
adds the loopback origins to `frame-src`/`child-src`.