Commit Graph
4 Commits
Author SHA1 Message Date
Qiusheng Wuandgithub-actions[bot] 5ecb05760e docs: correct the user guide against the running app and add screenshots (#2112)
* docs: correct the user guide against the running app and add screenshots

Several pages had drifted from the shipped UI: the toolbar was documented as
seven menus with Edit and View missing entirely, Add Data omitted ten entries,
the plugin and style-type lists were from an older release, and the terrain
tutorial claimed raster tools need the desktop app when eight of them have a
browser engine. Every claim here was rechecked by driving the app, and the
Browser panel, Model Builder, Dashboard, and the attribute table's own tools
are now documented rather than only mentioned in the feature list.

* fix(mkdocs): update GeoLibre Web link in documentation

* Address Claude review feedback

- Correct the `C` shortcut row: it toggles the review-comment tool and the
  pin lands where you click next, not at the map center. Also mention the
  shortcut in the Review comments section, which only documented the panel.
- Give Managing Layers its own Layers-panel screenshot instead of reusing
  the full-workspace shot from Interface Overview, which did not match the
  alt text.

* Address CodeRabbit review feedback

- Name both projection-aware engines in the buffer tip. Python (Pyodide)
  runs the same GeoPandas path as the sidecar and is the one available on
  the web build, so pointing only at the sidecar sends web users nowhere.
- Stop claiming every on-map-control plugin has a positioning submenu.
  Flight Simulator and SamGeo toggle directly, and Web Services and DGGS
  open a list of sub-plugins instead.

* Update mkdocs.yml

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-08-26 02:48:06 +00:00
Qiusheng Wu ccaf754005 Follow-up: notebook style review fixes (#1620)
* Address follow-up review feedback on #1619

Second round of bot review comments on #1619, which merged before they could
be addressed on that branch.

- docs/notebook.md: drop the `layer_id`-taking calls from the snippet instead
  of annotating them. The fire-and-forget client can never produce an id, so
  the example was self-contradictory; the calls are now described in prose
  below it, pointing at the widget client that does return ids.
- console_api.py: give `load_geojson` the same `**style` kwargs as
  `add_geojson`, so both console entry points style in one call.
- tests/scripting-style-params.test.ts: set the history coalesce window to 0
  so the empty-style guard is actually under test (at the app's default window
  the two writes merge and the assertion passed either way — verified by
  mutating the guard), and add a case pinning the app-window behavior: a
  styled add coalesces into one undo entry, so one Ctrl+Z removes the layer
  rather than leaving an unstyled one behind.

* Address review feedback

- Capture the app's history coalesce window with `getHistoryCoalesceMs()` and
  restore that, instead of hard-coding 400 in the cleanup path and in the
  coalescing case, so the test follows the source default if it moves.

* Address Claude review feedback

- Document the `**style` kwargs on `load_geojson` in the Python console API
  table; the signature gained them in 6260a02a but the row still read
  `load_geojson(url, name=)`, inconsistent with the `add_geojson` row above it.
2026-08-01 11:57:14 -04:00
Qiusheng Wu 4ad82800a2 Support inline GeoJSON styles in notebook bridge (#1619)
* fix: support inline GeoJSON styles in notebooks

* fix: skip empty notebook style updates

* Address review feedback

- Extract the empty-style guard in `addGeoJsonLayer` into a documented
  `styleParamPatch` helper (`scripting/style-params.ts`), so the rule that a
  `{}` payload writes nothing is testable on its own. The scripting module
  itself pulls in the plugin/map stack (and its CSS) and cannot be imported
  under `node --test`.
- Add `tests/scripting-style-params.test.ts`: covers the patch helper and
  replays what the handler does against the real store — the style merges onto
  the new layer, and a style-less call still costs exactly one undo step.
- docs/notebook.md: flag that the fire-and-forget client returns no layer id,
  so the `layer_id`-taking calls in the snippet need one from the blocking
  widget client.

* Address Claude review feedback

- Give the in-app Pyodide console's `add_geojson` the same `**style` kwargs as
  the notebook client, so one-call styling behaves identically across both
  Python entry points (flagged in the review summary as an inconsistency).

* Document the console's inline add_geojson styles

- Update the Python console user guide for the `**style` kwargs added to
  `console_api.add_geojson` in c3c5b22b.
2026-08-01 11:34:40 -04:00
Qiusheng Wu 6f3f4ade7a feat(scripting): Python automation API + in-app Python Console (#327)
* feat(scripting): Python automation API + in-app Python Console

Add two PyQGIS-style ways to script GeoLibre programmatically.

Notebook API (geolibre.Map anywidget):
- Request/reply command channel + event channel over the anywidget custom
  message bus, relayed by _frontend.js into the embedded app.
- App-side dispatcher (useCommandBridge) backed by a shared scriptingApi
  factory; MapController gains flyTo() and identifyFeatures().
- Python request()/on() primitives (blocking via jupyter-ui-poll) plus
  get_center, get_bounds, fly_to, fit_bounds, identify, get_features,
  run_algorithm, list_algorithms, to_image, and a Layer/Feature model.

In-app Python Console:
- Dockable, resizable bottom panel (Processing -> Python Console), not a modal.
- Main-thread Pyodide runtime (minimal/fast startup, on-demand packages,
  async execution) exposing a `geolibre` facade that drives the live store and
  map, sharing the same scriptingApi handlers as the notebook bridge.

Extracts the embed-host handshake into embedHost.ts (shared by both bridges).

* Address Claude review feedback

- pyodide-console: destroy the result JsProxy in a finally so a throwing
  toString() can't leak it.
- map-controller (featureIdForLayer): return null when the rendered feature has
  no properties, instead of vacuously matching the first source feature.
- _frontend.js: cap the pre-ready pendingCommands queue at 500 as a defensive
  bound.
- console_api to_image: return PNG bytes to match the notebook Map.to_image()
  return type (host-path writing isn't available in-browser).

* Address CodeRabbit review feedback

- test_scripting: inject a fake jupyter_ui_poll via sys.modules instead of
  patching by string path, fixing the package-publish CI job which lacks the
  optional dependency (ModuleNotFoundError).
- map-controller (featureIdForLayer): only return a property-matched id for an
  unambiguous single hit (compare full key sets), else null — supersedes the
  empty-properties guard and avoids wrong ids for non-unique features.
- geolibre.request: move send() inside the try so a failing send() still pops
  the pending slot.
- console_api Layer: sync self._info in the visible/opacity setters so later
  reads on the same handle aren't stale; add -> None annotations.
- PythonConsolePanel: show runtime-init failures as an error (no spinner),
  separate from loading progress.
- embedHost: document the ?embed=1 trust model / deployment constraint.
- scriptingApi.toImage: note the synchronous PNG encode blocks the main thread.

* Address review feedback (round 2)

- embedHost.isEmbedded: auto-trust only a SAME-ORIGIN framing parent by probing
  window.parent.location.href (which throws cross-origin); `window.parent !==
  window` does not throw cross-origin, so the old check would auto-activate the
  bridge for a third-party framer. Doc corrected.
- useCommandBridge: dispatch only own-property handlers (Object.hasOwn), so an
  inherited member like "constructor"/"toString" can't be invoked as a command.
- scriptingApi.setBasemap: validate the url is an http(s)/root-relative string
  (rejects undefined -> "undefined" and javascript:/data: schemes).
- console_api.load_geojson: raise on non-ok HTTP responses instead of parsing an
  error body as GeoJSON.
- to_image (both notebook geolibre.py and console_api): raise on a data URL with
  no comma instead of silently returning empty bytes.
- PythonConsolePanel: tear down in-flight drag listeners on unmount.
- pyodide-console: drop the redundant scriptPromise reset in script.onerror.
- geolibre.request: register the pending slot inside the try.

* docs: add Python Console guide and interactive scripting API reference

- New user-guide page (user-guide/python-console.md) covering the in-app Python
  Console: opening/docking, first-run runtime load, driving the map, async
  await, on-demand packages, and a full geolibre/Layer/Feature API reference.
- python.md: document the interactive scripting surface (live queries, events,
  processing, to_image, Layer/Feature) that the notebook widget shares with the
  console, with a new API-reference subsection.
- mkdocs.yml: add the Python Console page to the User Guide nav.

* feat(python-console): command history, autocomplete, eraser icon

- Up/Down arrows recall previous/next submitted commands (only when the caret is
  on the first/last line, so multi-line editing still works); a draft is saved
  when history navigation starts.
- Tab / Ctrl+Space autocomplete the name or attribute at the caret, backed by a
  new _geolibre_complete() that introspects the live Pyodide namespace (real
  geolibre methods, user variables, builtins, keywords; private names hidden). A
  single match inserts directly; multiple show a dropdown (Up/Down to choose,
  Enter/Tab to accept, Esc to dismiss).
- Swap the Clear-output icon from Trash2 to Eraser.
- Docs: document the editing shortcuts in the Python Console guide.

* feat(python-console): QGIS-style script editor alongside the console

- "Show editor" toggle splits a script editor into the Python Console panel
  (left), with a draggable vertical divider; hidden by default.
- The editor runs in the SAME Pyodide interpreter as the console (shared
  globals), so scripts and the REPL share state. Run executes the whole buffer
  or the current selection; output goes to the console scrollback.
- Open/Save/Save As/New for .py files via the app's cross-platform file I/O
  (openLocalDataFileWithFallback / saveTextFileWithFallback); desktop Save writes
  in place via new tauri-io writeTextFileToPath, web downloads. Dirty marker +
  discard confirmation.
- Editor keys: Ctrl/Cmd+Enter run, Ctrl/Cmd+S save, Tab indent, Ctrl+Space
  autocomplete.
- Factor the console autocomplete into a shared usePyCompletion hook (with a
  tabTriggers option) reused by both the console input and the editor.
- Add i18n strings and document the script editor in the user guide.

* Address review feedback (round 3)

- featureIdForLayer: return null for zero-property features (no fake index id),
  and compare object/array property values by JSON (MapLibre re-parses them to
  fresh refs, so === always failed).
- scriptingApi.setOpacity: reject non-finite and clamp to [0,1].
- notebook Map: add set_basemap alias (name parity with the console facade).
- PythonConsolePanel: dispatch PANEL_RESIZE_END on unmount mid-drag so MapCanvas
  clears panelResizeActive.
- pyodide-console: serialize runs through a queue so overlapping calls can't
  clobber the instance-global stdout/stderr capture; document the merged
  stdout/stderr stream.
- usePyCompletion: clamp completion splice offsets against current code.
- geolibre._wait_for_result: check the deadline before and after poll() so a slow
  pump can't overrun a small timeout.

* feat(python-editor): clear button; address review feedback (round 4)

- Add a "Clear editor" eraser button to the script editor toolbar (empties the
  buffer, keeping the filename; confirms when there are unsaved changes).
- PythonConsolePanel: throttle the editor splitter drag through
  requestAnimationFrame with direct DOM writes (commit to state on mouseup), and
  give the vertical and horizontal drags separate cleanup refs so one can't
  overwrite the other's teardown.
- _frontend.js: warn when the pre-ready command queue is full instead of
  silently dropping, so a full queue is diagnosable.
- geolibre._wait_for_result: poll at 20 Hz (sleep 0.05) instead of 100 Hz to cut
  idle CPU; deadline is still checked each iteration.

* Address review feedback (round 5)

- PythonEditorPane: Tab now block-indents every selected line with 4 spaces
  (PEP 8) instead of replacing a multi-line selection with two spaces; the
  selection is preserved.
- scriptingApi: validate layerId is a non-empty string across the layer handlers
  (getLayerFeatures/removeLayer/setVisibility/setOpacity/setStyle/zoomToLayer)
  via a shared requireLayerId, matching identify.
- _frontend.js: post commands straight through once ready instead of a
  push→flush cycle; only buffer before ready.
- pyodide-console: remove the failed <script> element on load error so retries
  don't orphan dead tags in <head>.
2026-06-13 22:42:27 -04:00