Skip to content

Commit 3ec2541

Browse files
author
lindakladivova
committed
not working version - but already refactored hugely
1 parent a205a5e commit 3ec2541

File tree

7 files changed

+503
-174
lines changed

7 files changed

+503
-174
lines changed

gui/wxpython/jupyter_notebook/panel.py

Lines changed: 62 additions & 174 deletions
Original file line numberDiff line numberDiff line change
@@ -14,23 +14,18 @@
1414
@author Linda Karlovska <linda.karlovska seznam.cz>
1515
"""
1616

17-
import json
18-
import subprocess
19-
import threading
20-
from pathlib import Path
21-
2217
import wx
2318

2419
try:
2520
import wx.html2 as html # wx.html2 is available in wxPython 4.0 and later
2621
except ImportError as e:
2722
raise RuntimeError(_("wx.html2 is required for Jupyter integration.")) from e
2823

29-
import grass.script as gs
30-
import grass.jupyter as gj
31-
3224
from main_window.page import MainPageBase
3325

26+
from grass.notebooks.launcher import NotebookServerManager
27+
from grass.notebooks.directory import NotebookDirectoryManager
28+
3429

3530
class JupyterPanel(wx.Panel, MainPageBase):
3631
def __init__(
@@ -60,177 +55,70 @@ def __init__(
6055

6156
self.SetName("Jupyter")
6257

63-
def start_jupyter_server(self, notebooks_dir):
64-
"""Spustí Jupyter notebook server v daném adresáři na volném portu."""
65-
import socket
66-
import time
67-
68-
# Najdi volný port
69-
sock = socket.socket()
70-
sock.bind(("", 0))
71-
port = sock.getsockname()[1]
72-
sock.close()
73-
74-
# Spusť server v samostatném vlákně
75-
def run_server():
76-
subprocess.Popen(
77-
[
78-
"jupyter",
79-
"notebook",
80-
"--no-browser",
81-
"--NotebookApp.token=''",
82-
"--NotebookApp.password=''",
83-
"--port",
84-
str(port),
85-
"--notebook-dir",
86-
notebooks_dir,
87-
]
88-
)
89-
90-
threading.Thread(target=run_server, daemon=True).start()
91-
92-
output = subprocess.check_output(["jupyter", "notebook", "list"]).decode()
93-
print(output)
94-
95-
# Počkej, až server naběhne (lepší by bylo kontrolovat výstup, zde jen krátké čekání)
96-
time.sleep(3)
97-
print(port)
98-
return f"http://localhost:{port}"
58+
def _hide_file_menu(self, event):
59+
"""Inject JavaScript to hide Jupyter's File menu
60+
and header after load.
61+
:param event: wx.EVT_WEBVIEW_LOADED event
62+
"""
63+
# Hide File menu and header
64+
webview = event.GetEventObject()
65+
js = """
66+
var interval = setInterval(function() {
67+
// Hide File menu
68+
var fileMenu = document.querySelector('li#file_menu, a#filelink, a[aria-controls="file_menu"]');
69+
if (fileMenu) {
70+
if (fileMenu.tagName === "LI") {
71+
fileMenu.style.display = 'none';
72+
} else if (fileMenu.parentElement && fileMenu.parentElement.tagName === "LI") {
73+
fileMenu.parentElement.style.display = 'none';
74+
}
75+
}
76+
// Hide top header
77+
var header = document.getElementById('header-container');
78+
if (header) {
79+
header.style.display = 'none';
80+
}
81+
// Stop checking once both are hidden
82+
if (fileMenu && header) {
83+
clearInterval(interval);
84+
}
85+
}, 500);
86+
"""
87+
webview.RunScript(js)
88+
89+
def _add_notebook_tab(self, url, title):
90+
"""Add a new tab with a Jupyter notebook loaded in a WebView.
91+
92+
This method creates a new browser tab inside the notebook panel
93+
and loads the given URL. After the page is loaded, it injects
94+
JavaScript to hide certain UI elements from the Jupyter interface.
95+
96+
:param url: URL to the Jupyter notebook
97+
:param title: Title of the new tab
98+
"""
99+
webview = html.WebView.New(self.notebook)
100+
wx.CallAfter(webview.LoadURL, url)
101+
wx.CallAfter(webview.Bind, html.EVT_WEBVIEW_LOADED, self._hide_file_menu)
102+
self.notebook.AddPage(webview, title)
99103

100104
def SetUpPage(self):
101105
"""Set up the Jupyter Notebook interface."""
102-
gisenv = gs.gisenv()
103-
gisdbase = gisenv["GISDBASE"]
104-
location = gisenv["LOCATION_NAME"]
105-
mapset = gisenv["MAPSET"]
106-
mapset_path = f"{gisdbase}/{location}/{mapset}"
107-
notebooks_dir = Path(mapset_path) / "notebooks"
108-
notebooks_dir.mkdir(parents=True, exist_ok=True)
109-
self.session = gj.init(mapset_path)
110-
111-
# Spusť Jupyter server v adresáři notebooks
112-
url_base = self.start_jupyter_server(notebooks_dir)
113-
114-
# Najdi všechny .ipynb soubory v notebooks/
115-
ipynb_files = [f for f in Path.iterdir(notebooks_dir) if f.endswith(".ipynb")]
116-
print(ipynb_files)
117-
118-
if not ipynb_files:
119-
print("No notebooks found in the directory.")
120-
# Pokud nejsou žádné soubory, vytvoř template
121-
new_notebook_name = "template.ipynb"
122-
print(new_notebook_name)
123-
new_notebook_path = Path(notebooks_dir) / (new_notebook_name)
124-
template = {
125-
"cells": [
126-
{
127-
"cell_type": "markdown",
128-
"metadata": {},
129-
"source": [
130-
"# Template file\n",
131-
],
132-
},
133-
{
134-
"cell_type": "markdown",
135-
"metadata": {},
136-
"source": [
137-
"You can add your own code here\n",
138-
"or create new notebooks in the GRASS GUI\n",
139-
"and they will be automatically saved in the directory: `{}`\n".format(
140-
notebooks_dir.replace("\\", "/")
141-
),
142-
"and opened in the Jupyter Notebook interface.\n",
143-
"\n",
144-
],
145-
},
146-
{
147-
"cell_type": "code",
148-
"execution_count": None,
149-
"metadata": {},
150-
"outputs": [],
151-
"source": [
152-
"import grass.script as gs",
153-
],
154-
},
155-
{
156-
"cell_type": "code",
157-
"execution_count": None,
158-
"metadata": {},
159-
"outputs": [],
160-
"source": [
161-
"print('Raster maps in the current mapset:')\n",
162-
"for rast in gs.list_strings(type='raster'):\n",
163-
" print(' ', rast)\n",
164-
],
165-
},
166-
{
167-
"cell_type": "code",
168-
"execution_count": None,
169-
"metadata": {},
170-
"outputs": [],
171-
"source": [
172-
"print('\\nVector maps in the current mapset:')\n",
173-
"for vect in gs.list_strings(type='vector'):\n",
174-
" print(' ', vect)\n",
175-
],
176-
},
177-
],
178-
"metadata": {
179-
"kernelspec": {
180-
"display_name": "Python 3",
181-
"language": "python",
182-
"name": "python3",
183-
},
184-
"language_info": {"name": "python", "version": "3.x"},
185-
},
186-
"nbformat": 4,
187-
"nbformat_minor": 2,
188-
}
189-
print(new_notebook_path)
190-
print("template")
191-
with open(new_notebook_path, "w", encoding="utf-8") as f:
192-
json.dump(template, f, ensure_ascii=False, indent=2)
193-
ipynb_files.append(new_notebook_name)
194-
195-
notebook = wx.Notebook(self)
196-
197-
# Po načtení stránky injektujte JS pro skrytí File menu
198-
def hide_file_menu(event):
199-
browser = event.GetEventObject()
200-
print(browser)
201-
js = """
202-
var interval = setInterval(function() {
203-
// Skrytí File menu
204-
var fileMenu = document.querySelector('li#file_menu, a#filelink, a[aria-controls="file_menu"]');
205-
if (fileMenu) {
206-
if (fileMenu.tagName === "LI") {
207-
fileMenu.style.display = 'none';
208-
} else if (fileMenu.parentElement && fileMenu.parentElement.tagName === "LI") {
209-
fileMenu.parentElement.style.display = 'none';
210-
}
211-
}
212-
// Skrytí horního panelu
213-
var header = document.getElementById('header-container');
214-
if (header) {
215-
header.style.display = 'none';
216-
}
217-
// Ukonči interval, pokud jsou oba prvky nalezeny
218-
if (fileMenu && header) {
219-
clearInterval(interval);
220-
}
221-
}, 500);
222-
"""
106+
# Create a directory manager to handle notebook files
107+
# and a server manager to run the Jupyter server
108+
dir_manager = NotebookDirectoryManager()
109+
dir_manager.prepare_notebook_files()
110+
111+
server_manager = NotebookServerManager(dir_manager.notebook_workdir)
112+
server_manager.start_server()
223113

224-
browser.RunScript(js)
114+
self.notebook = wx.Notebook(self)
225115

226-
for fname in ipynb_files:
227-
url_base = url_base.rstrip("/")
228-
url = f"{url_base}/notebooks/{fname}"
229-
browser = html.WebView.New(notebook)
230-
wx.CallAfter(browser.LoadURL, url)
231-
wx.CallAfter(browser.Bind, html.EVT_WEBVIEW_LOADED, hide_file_menu)
232-
notebook.AddPage(browser, fname)
116+
# Create a new tab for each notebook file
117+
for fname in dir_manager.notebook_files:
118+
print(fname)
119+
url = server_manager.get_notebook_url(fname)
120+
self._add_notebook_tab(url, fname)
233121

234122
sizer = wx.BoxSizer(wx.VERTICAL)
235-
sizer.Add(notebook, 1, wx.EXPAND)
123+
sizer.Add(self.notebook, 1, wx.EXPAND)
236124
self.SetSizer(sizer)

python/grass/Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ SUBDIRS = \
1414
gunittest \
1515
imaging \
1616
jupyter \
17+
notebooks \
1718
pydispatch \
1819
pygrass \
1920
script \

python/grass/notebooks/Makefile

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
MODULE_TOPDIR = ../../..
2+
3+
include $(MODULE_TOPDIR)/include/Make/Other.make
4+
include $(MODULE_TOPDIR)/include/Make/Python.make
5+
6+
DSTDIR = $(ETC)/python/grass/notebooks
7+
8+
MODULES = launcher directory
9+
10+
PYFILES := $(patsubst %,$(DSTDIR)/%.py,$(MODULES) __init__)
11+
PYCFILES := $(patsubst %,$(DSTDIR)/%.pyc,$(MODULES) __init__)
12+
13+
default: $(PYFILES) $(PYCFILES)
14+
15+
$(DSTDIR):
16+
$(MKDIR) $@
17+
18+
$(DSTDIR)/%: % | $(DSTDIR)
19+
$(INSTALL_DATA) $< $@

python/grass/notebooks/__init__.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
# MODULE: grass.notebooks
2+
#
3+
# AUTHOR(S): Linda Karlovska <linda.karlovska seznam cz>
4+
#
5+
# PURPOSE: Tools for managing Jupyter Notebooks within GRASS
6+
#
7+
# COPYRIGHT: (C) 2025 Linda Karlovska, and by the GRASS Development Team
8+
#
9+
# This program is free software under the GNU General Public
10+
# License (>=v2). Read the file COPYING that comes with GRASS
11+
# for details.
12+
13+
"""
14+
Tools for managing Jupyter Notebooks within GRASS
15+
16+
This module provides functionality for:
17+
- Starting and stopping local Jupyter Notebook servers inside a GRASS GIS session
18+
- Managing notebook directories linked to specific GRASS mapsets
19+
- Creating default notebook templates for users
20+
- Supporting integration with the GUI (e.g., wxGUI) and other tools
21+
22+
Unlike `grass.jupyter`, which allows Jupyter to access GRASS environments,
23+
this module is focused on running Jupyter from within GRASS.
24+
25+
Example use case:
26+
- A user opens a panel in the GRASS that launches a Jupyter server
27+
and opens the associated notebook directory for the current mapset.
28+
29+
.. versionadded:: 8.5
30+
31+
"""
32+
33+
from .launcher import NotebookServerManager
34+
from .directory import NotebookDirectoryManager
35+
36+
__all__ = [
37+
"Directory",
38+
"Launcher",
39+
"NotebookDirectoryManager",
40+
"NotebookServerManager",
41+
]

0 commit comments

Comments
 (0)