syd 1.0.2__tar.gz → 1.2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {syd-1.0.2 → syd-1.2.0}/PKG-INFO +24 -10
- {syd-1.0.2 → syd-1.2.0}/README.md +23 -9
- syd-1.2.0/syd/__init__.py +4 -0
- {syd-1.0.2 → syd-1.2.0}/syd/flask_deployment/deployer.py +119 -26
- {syd-1.0.2 → syd-1.2.0}/syd/flask_deployment/static/css/styles.css +100 -67
- syd-1.2.0/syd/flask_deployment/static/css/viewer.css +48 -0
- syd-1.2.0/syd/flask_deployment/static/js/modules/api.js +89 -0
- syd-1.2.0/syd/flask_deployment/static/js/modules/config.js +22 -0
- syd-1.2.0/syd/flask_deployment/static/js/modules/plot.js +75 -0
- syd-1.2.0/syd/flask_deployment/static/js/modules/state.js +89 -0
- syd-1.2.0/syd/flask_deployment/static/js/modules/system_controls.js +191 -0
- syd-1.2.0/syd/flask_deployment/static/js/modules/ui_controls.js +812 -0
- syd-1.2.0/syd/flask_deployment/static/js/modules/utils.js +49 -0
- syd-1.0.2/syd/flask_deployment/static/js/viewer.js → syd-1.2.0/syd/flask_deployment/static/js/old_viewer.js +394 -30
- syd-1.2.0/syd/flask_deployment/static/js/viewer.js +58 -0
- {syd-1.0.2 → syd-1.2.0}/syd/flask_deployment/templates/index.html +1 -1
- {syd-1.0.2 → syd-1.2.0}/syd/notebook_deployment/deployer.py +1 -3
- {syd-1.0.2 → syd-1.2.0}/syd/notebook_deployment/widgets.py +45 -27
- {syd-1.0.2 → syd-1.2.0}/syd/support.py +25 -0
- {syd-1.0.2 → syd-1.2.0}/syd/viewer.py +35 -4
- syd-1.0.2/syd/__init__.py +0 -11
- {syd-1.0.2 → syd-1.2.0}/.gitignore +0 -0
- {syd-1.0.2 → syd-1.2.0}/LICENSE +0 -0
- {syd-1.0.2 → syd-1.2.0}/pyproject.toml +0 -0
- {syd-1.0.2 → syd-1.2.0}/syd/flask_deployment/__init__.py +0 -0
- {syd-1.0.2 → syd-1.2.0}/syd/flask_deployment/static/__init__.py +0 -0
- {syd-1.0.2 → syd-1.2.0}/syd/flask_deployment/templates/__init__.py +0 -0
- {syd-1.0.2 → syd-1.2.0}/syd/flask_deployment/testing_principles.md +0 -0
- {syd-1.0.2 → syd-1.2.0}/syd/notebook_deployment/__init__.py +0 -0
- {syd-1.0.2 → syd-1.2.0}/syd/parameters.py +0 -0
{syd-1.0.2 → syd-1.2.0}/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: syd
|
|
3
|
-
Version: 1.0
|
|
3
|
+
Version: 1.2.0
|
|
4
4
|
Summary: A Python package for making GUIs for data science easy.
|
|
5
5
|
Project-URL: Homepage, https://github.com/landoskape/syd
|
|
6
6
|
Author-email: Andrew Landau <andrew+tyler+landau+getridofthisanddtheplusses@gmail.com>
|
|
@@ -47,7 +47,7 @@ Have you ever wanted to look through all your data really quickly interactively?
|
|
|
47
47
|
|
|
48
48
|
Syd is a system for creating a data viewing GUI that you can view in a jupyter notebook or in a web browser. And guess what? Since it can open in a web browser, you can even open it on any other computer on your local network! For example, your PI's computer. Gone are the days of single random examples that they make infinitely stubborn conclusions about. Now, you can look at all the examples, quickly and easily, on their computer. And that's why Syd stands for share your data!
|
|
49
49
|
|
|
50
|
-
Okay, so what is it? Syd is an automated system to convert some basic python plotting code into an interactive GUI. This means you only have to think about what you want to
|
|
50
|
+
Okay, so what is it? Syd is an automated system to convert some basic python plotting code into an interactive GUI. This means you only have to think about _**what you want to plot**_ and _**which parameters**_ you want to be interactive. Syd handles all the behind-the-scenes action required to make an interface. And guess what? That means you get to _**spend your time thinking**_ about your data, rather than writing code to look at it. And that's why Syd stands for Science, Yes! Dayummmm!
|
|
51
51
|
|
|
52
52
|
## Installation
|
|
53
53
|
It's easy, just use pip install. The dependencies are light so it should work in most environments.
|
|
@@ -56,7 +56,7 @@ pip install syd
|
|
|
56
56
|
```
|
|
57
57
|
|
|
58
58
|
## Documentation
|
|
59
|
-
The full documentation is available
|
|
59
|
+
The full documentation is available [here](https://shareyourdata.readthedocs.io/). It includes a quick start guide, a comprehensive tutorial, and an API reference for the different elements of Syd. If you have any questions or want to suggest improvements to the docs, please let us know on the [github issues page](https://github.com/landoskape/syd/issues)!
|
|
60
60
|
|
|
61
61
|
## Quick Start
|
|
62
62
|
This is an example of a sine wave viewer which is about as simple as it gets. You can choose which env to use - if you use ``env="notebook"`` then the GUI will deploy as the output of a jupyter cell (this only works in jupyter!). If you use ``env="browser"`` then the GUI will open a page in your default web browser and you can interact with the data there (works in jupyter notebooks and also from python scripts!).
|
|
@@ -78,9 +78,8 @@ viewer.add_float("amplitude", value=1.0, min=0.1, max=2.0)
|
|
|
78
78
|
viewer.add_float("frequency", value=1.0, min=0.1, max=5.0)
|
|
79
79
|
viewer.add_selection("color", value="red", options=["red", "blue", "green", "black"])
|
|
80
80
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
viewer.show()
|
|
81
|
+
viewer.show() # for viewing in a jupyter notebook
|
|
82
|
+
# viewer.share() # for viewing in a web browser
|
|
84
83
|
```
|
|
85
84
|
|
|
86
85
|

|
|
@@ -202,7 +201,21 @@ This project is licensed under the GNU General Public License v3.0 - see the [LI
|
|
|
202
201
|
|
|
203
202
|
## Contributing
|
|
204
203
|
|
|
205
|
-
Contributions are welcome!
|
|
204
|
+
Contributions are welcome! If you have an idea for an improvement or a bug report, please let us know by opening an
|
|
205
|
+
issue on the [github issues page](https://github.com/landoskape/syd/issues). You can also contribute code by submitting
|
|
206
|
+
a pull request. Here are some guidelines for contributing:
|
|
207
|
+
|
|
208
|
+
### 1. Reporting Bugs
|
|
209
|
+
If you find a bug, (e.g. any error or strange behavior that is not expected), please let us know by opening an issue on the [github issues page](https://github.com/landoskape/syd/issues).
|
|
210
|
+
|
|
211
|
+
### 2. Suggesting Features
|
|
212
|
+
If you have an idea for a feature or improvement, please let tell us. Opening an issue on the [github issues page](https://github.com/landoskape/syd/issues).
|
|
213
|
+
|
|
214
|
+
### 3. Improvements to the Documentation
|
|
215
|
+
A package is only as good as its documentation. If you think the documentation is missing something, confusing, or could be improved in any way, please let us know by opening an issue on the [github issues page](https://github.com/landoskape/syd/issues).
|
|
216
|
+
|
|
217
|
+
### 4. Contributing Code
|
|
218
|
+
If you want to contribute code (good for you!), here's how you can do it:
|
|
206
219
|
|
|
207
220
|
1. Fork the repository
|
|
208
221
|
2. Create a new branch (`git checkout -b feature/amazing-feature`)
|
|
@@ -212,7 +225,7 @@ Contributions are welcome! Here's how you can help:
|
|
|
212
225
|
6. Push to the branch (`git push origin feature/amazing-feature`)
|
|
213
226
|
7. Open a Pull Request online
|
|
214
227
|
|
|
215
|
-
Please make sure to update tests as appropriate and adhere to the existing coding style (black, line-length=88, other style guidelines not capture by black, generally following pep8 guidelines). Try to make the code coverage report improve or stay the same rather than decrease (right now the deployment system isn't covered by tests).
|
|
228
|
+
Please make sure to update tests as appropriate and adhere to the existing coding style (black, line-length=88, other style guidelines not capture by black, generally following pep8 guidelines). Try to make the code coverage report improve or stay the same rather than decrease (right now the deployment system isn't covered by tests). There aren't any precommit hooks or anything so you're responsible for checking this yourself. You can process the code with black as follows:
|
|
216
229
|
```bash
|
|
217
230
|
pip install black
|
|
218
231
|
black . # from the root directory of the repo
|
|
@@ -220,7 +233,6 @@ black . # from the root directory of the repo
|
|
|
220
233
|
|
|
221
234
|
## To-Do List
|
|
222
235
|
- Layout controls
|
|
223
|
-
- [ ] Improve the display and make it look better
|
|
224
236
|
- [ ] Add a "save" button that saves the current state of the viewer to a json file
|
|
225
237
|
- [ ] Add a "load" button that loads the viewer state from a json file
|
|
226
238
|
- [ ] Add a "freeze" button that allows the user to update state variables without updating the plot until unfreezing
|
|
@@ -228,4 +240,6 @@ black . # from the root directory of the repo
|
|
|
228
240
|
- [ ] Consider "app_deployed" context for each deployer...
|
|
229
241
|
- Export options:
|
|
230
242
|
- [ ] Export lite: export the viewer as a HTML/Java package that contains an incomplete set of renderings of figures -- using a certain set of parameters.
|
|
231
|
-
- [ ] Export full: export the viewer in a way that contains the data to give full functionality.
|
|
243
|
+
- [ ] Export full: export the viewer in a way that contains the data to give full functionality.
|
|
244
|
+
- [ ] Idea for sharing: https://github.com/analyticalmonk/awesome-neuroscience, https://github.com/fasouto/awesome-dataviz
|
|
245
|
+
- [ ] The handling of value in Selection parameters is kind of weird.... I think we need to think more about what to do for fails!!!!
|
|
@@ -17,7 +17,7 @@ Have you ever wanted to look through all your data really quickly interactively?
|
|
|
17
17
|
|
|
18
18
|
Syd is a system for creating a data viewing GUI that you can view in a jupyter notebook or in a web browser. And guess what? Since it can open in a web browser, you can even open it on any other computer on your local network! For example, your PI's computer. Gone are the days of single random examples that they make infinitely stubborn conclusions about. Now, you can look at all the examples, quickly and easily, on their computer. And that's why Syd stands for share your data!
|
|
19
19
|
|
|
20
|
-
Okay, so what is it? Syd is an automated system to convert some basic python plotting code into an interactive GUI. This means you only have to think about what you want to
|
|
20
|
+
Okay, so what is it? Syd is an automated system to convert some basic python plotting code into an interactive GUI. This means you only have to think about _**what you want to plot**_ and _**which parameters**_ you want to be interactive. Syd handles all the behind-the-scenes action required to make an interface. And guess what? That means you get to _**spend your time thinking**_ about your data, rather than writing code to look at it. And that's why Syd stands for Science, Yes! Dayummmm!
|
|
21
21
|
|
|
22
22
|
## Installation
|
|
23
23
|
It's easy, just use pip install. The dependencies are light so it should work in most environments.
|
|
@@ -26,7 +26,7 @@ pip install syd
|
|
|
26
26
|
```
|
|
27
27
|
|
|
28
28
|
## Documentation
|
|
29
|
-
The full documentation is available
|
|
29
|
+
The full documentation is available [here](https://shareyourdata.readthedocs.io/). It includes a quick start guide, a comprehensive tutorial, and an API reference for the different elements of Syd. If you have any questions or want to suggest improvements to the docs, please let us know on the [github issues page](https://github.com/landoskape/syd/issues)!
|
|
30
30
|
|
|
31
31
|
## Quick Start
|
|
32
32
|
This is an example of a sine wave viewer which is about as simple as it gets. You can choose which env to use - if you use ``env="notebook"`` then the GUI will deploy as the output of a jupyter cell (this only works in jupyter!). If you use ``env="browser"`` then the GUI will open a page in your default web browser and you can interact with the data there (works in jupyter notebooks and also from python scripts!).
|
|
@@ -48,9 +48,8 @@ viewer.add_float("amplitude", value=1.0, min=0.1, max=2.0)
|
|
|
48
48
|
viewer.add_float("frequency", value=1.0, min=0.1, max=5.0)
|
|
49
49
|
viewer.add_selection("color", value="red", options=["red", "blue", "green", "black"])
|
|
50
50
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
viewer.show()
|
|
51
|
+
viewer.show() # for viewing in a jupyter notebook
|
|
52
|
+
# viewer.share() # for viewing in a web browser
|
|
54
53
|
```
|
|
55
54
|
|
|
56
55
|

|
|
@@ -172,7 +171,21 @@ This project is licensed under the GNU General Public License v3.0 - see the [LI
|
|
|
172
171
|
|
|
173
172
|
## Contributing
|
|
174
173
|
|
|
175
|
-
Contributions are welcome!
|
|
174
|
+
Contributions are welcome! If you have an idea for an improvement or a bug report, please let us know by opening an
|
|
175
|
+
issue on the [github issues page](https://github.com/landoskape/syd/issues). You can also contribute code by submitting
|
|
176
|
+
a pull request. Here are some guidelines for contributing:
|
|
177
|
+
|
|
178
|
+
### 1. Reporting Bugs
|
|
179
|
+
If you find a bug, (e.g. any error or strange behavior that is not expected), please let us know by opening an issue on the [github issues page](https://github.com/landoskape/syd/issues).
|
|
180
|
+
|
|
181
|
+
### 2. Suggesting Features
|
|
182
|
+
If you have an idea for a feature or improvement, please let tell us. Opening an issue on the [github issues page](https://github.com/landoskape/syd/issues).
|
|
183
|
+
|
|
184
|
+
### 3. Improvements to the Documentation
|
|
185
|
+
A package is only as good as its documentation. If you think the documentation is missing something, confusing, or could be improved in any way, please let us know by opening an issue on the [github issues page](https://github.com/landoskape/syd/issues).
|
|
186
|
+
|
|
187
|
+
### 4. Contributing Code
|
|
188
|
+
If you want to contribute code (good for you!), here's how you can do it:
|
|
176
189
|
|
|
177
190
|
1. Fork the repository
|
|
178
191
|
2. Create a new branch (`git checkout -b feature/amazing-feature`)
|
|
@@ -182,7 +195,7 @@ Contributions are welcome! Here's how you can help:
|
|
|
182
195
|
6. Push to the branch (`git push origin feature/amazing-feature`)
|
|
183
196
|
7. Open a Pull Request online
|
|
184
197
|
|
|
185
|
-
Please make sure to update tests as appropriate and adhere to the existing coding style (black, line-length=88, other style guidelines not capture by black, generally following pep8 guidelines). Try to make the code coverage report improve or stay the same rather than decrease (right now the deployment system isn't covered by tests).
|
|
198
|
+
Please make sure to update tests as appropriate and adhere to the existing coding style (black, line-length=88, other style guidelines not capture by black, generally following pep8 guidelines). Try to make the code coverage report improve or stay the same rather than decrease (right now the deployment system isn't covered by tests). There aren't any precommit hooks or anything so you're responsible for checking this yourself. You can process the code with black as follows:
|
|
186
199
|
```bash
|
|
187
200
|
pip install black
|
|
188
201
|
black . # from the root directory of the repo
|
|
@@ -190,7 +203,6 @@ black . # from the root directory of the repo
|
|
|
190
203
|
|
|
191
204
|
## To-Do List
|
|
192
205
|
- Layout controls
|
|
193
|
-
- [ ] Improve the display and make it look better
|
|
194
206
|
- [ ] Add a "save" button that saves the current state of the viewer to a json file
|
|
195
207
|
- [ ] Add a "load" button that loads the viewer state from a json file
|
|
196
208
|
- [ ] Add a "freeze" button that allows the user to update state variables without updating the plot until unfreezing
|
|
@@ -198,4 +210,6 @@ black . # from the root directory of the repo
|
|
|
198
210
|
- [ ] Consider "app_deployed" context for each deployer...
|
|
199
211
|
- Export options:
|
|
200
212
|
- [ ] Export lite: export the viewer as a HTML/Java package that contains an incomplete set of renderings of figures -- using a certain set of parameters.
|
|
201
|
-
- [ ] Export full: export the viewer in a way that contains the data to give full functionality.
|
|
213
|
+
- [ ] Export full: export the viewer in a way that contains the data to give full functionality.
|
|
214
|
+
- [ ] Idea for sharing: https://github.com/analyticalmonk/awesome-neuroscience, https://github.com/fasouto/awesome-dataviz
|
|
215
|
+
- [ ] The handling of value in Selection parameters is kind of weird.... I think we need to think more about what to do for fails!!!!
|
|
@@ -6,7 +6,6 @@ from dataclasses import dataclass
|
|
|
6
6
|
import matplotlib as mpl
|
|
7
7
|
import matplotlib.pyplot as plt
|
|
8
8
|
import io
|
|
9
|
-
import time
|
|
10
9
|
import webbrowser
|
|
11
10
|
import threading
|
|
12
11
|
import socket
|
|
@@ -20,7 +19,7 @@ from flask import (
|
|
|
20
19
|
jsonify,
|
|
21
20
|
render_template,
|
|
22
21
|
)
|
|
23
|
-
from werkzeug.serving import
|
|
22
|
+
from werkzeug.serving import make_server
|
|
24
23
|
|
|
25
24
|
# Use Deployer base class
|
|
26
25
|
from ..viewer import Viewer
|
|
@@ -43,12 +42,61 @@ from ..support import ParameterUpdateWarning, plot_context
|
|
|
43
42
|
mpl.use("Agg")
|
|
44
43
|
|
|
45
44
|
|
|
45
|
+
class ServerManager:
|
|
46
|
+
def __init__(self):
|
|
47
|
+
self.servers: dict[int, "ServerThread"] = {}
|
|
48
|
+
|
|
49
|
+
def register_server(self, server: "ServerThread", port: int):
|
|
50
|
+
self.servers[port] = server
|
|
51
|
+
|
|
52
|
+
def close_app(self, port: int | None = None):
|
|
53
|
+
if port is None:
|
|
54
|
+
for server in self.servers.values():
|
|
55
|
+
server.shutdown()
|
|
56
|
+
self.servers.clear()
|
|
57
|
+
else:
|
|
58
|
+
if port in self.servers:
|
|
59
|
+
self.servers[port].shutdown()
|
|
60
|
+
del self.servers[port]
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
server_manager = ServerManager()
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
class ServerThread(threading.Thread):
|
|
67
|
+
def __init__(self, host: str, port: int, app, debug: bool):
|
|
68
|
+
super().__init__(daemon=True)
|
|
69
|
+
self.server = make_server(host, port, app, threaded=True)
|
|
70
|
+
self.port = port
|
|
71
|
+
self.debug = debug
|
|
72
|
+
self.ready = threading.Event()
|
|
73
|
+
|
|
74
|
+
num_open_servers = len(server_manager.servers)
|
|
75
|
+
if num_open_servers >= 10:
|
|
76
|
+
open_servers = "\n".join([f"{port}" for port in server_manager.servers])
|
|
77
|
+
print(
|
|
78
|
+
f"\nYou have {num_open_servers} open servers!\n"
|
|
79
|
+
f"Open servers:\n{open_servers}\n"
|
|
80
|
+
"You can close them with syd.close_servers() or a particular one with syd.close_servers(port).\n"
|
|
81
|
+
"To see a list, use: syd.show_open_servers()."
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
def run(self):
|
|
85
|
+
server_manager.register_server(self, self.port)
|
|
86
|
+
self.ready.set()
|
|
87
|
+
self.server.serve_forever()
|
|
88
|
+
|
|
89
|
+
def shutdown(self):
|
|
90
|
+
# Call this to stop the server cleanly
|
|
91
|
+
self.server.shutdown()
|
|
92
|
+
|
|
93
|
+
|
|
46
94
|
@dataclass
|
|
47
95
|
class FlaskLayoutConfig:
|
|
48
96
|
"""Configuration for the Flask viewer layout."""
|
|
49
97
|
|
|
50
98
|
controls_position: str = "left" # Options are: 'left', 'top', 'right', 'bottom'
|
|
51
|
-
controls_width_percent: int =
|
|
99
|
+
controls_width_percent: int = 15
|
|
52
100
|
|
|
53
101
|
def __post_init__(self):
|
|
54
102
|
valid_positions = ["left", "top", "right", "bottom"]
|
|
@@ -73,12 +121,14 @@ class FlaskDeployer:
|
|
|
73
121
|
viewer: Viewer,
|
|
74
122
|
controls_position: str = "left",
|
|
75
123
|
fig_dpi: int = 300,
|
|
76
|
-
controls_width_percent: int =
|
|
124
|
+
controls_width_percent: int = 15,
|
|
77
125
|
suppress_warnings: bool = True,
|
|
78
126
|
debug: bool = False,
|
|
79
127
|
host: str = "127.0.0.1",
|
|
80
128
|
port: Optional[int] = None,
|
|
81
129
|
open_browser: bool = True,
|
|
130
|
+
update_threshold: float = 1.0,
|
|
131
|
+
timeout_threshold: float = 10.0,
|
|
82
132
|
):
|
|
83
133
|
"""
|
|
84
134
|
Initialize the Flask deployer.
|
|
@@ -107,10 +157,16 @@ class FlaskDeployer:
|
|
|
107
157
|
Port for the server. If None, finds an available port (default: None).
|
|
108
158
|
open_browser : bool, optional
|
|
109
159
|
Whether to open the web application in a browser tab (default: True).
|
|
160
|
+
update_threshold : float, optional
|
|
161
|
+
Time in seconds to wait before showing the loading indicator (default: 1.0)
|
|
162
|
+
timeout_threshold : float, optional
|
|
163
|
+
Time in seconds to wait for the browser to open (default: 10.0).
|
|
110
164
|
"""
|
|
111
165
|
self.viewer = viewer
|
|
112
166
|
self.suppress_warnings = suppress_warnings
|
|
113
167
|
self._updating = False # Flag to check circular updates
|
|
168
|
+
self.update_threshold = update_threshold # Store update threshold
|
|
169
|
+
self.timeout_threshold = timeout_threshold # Store timeout threshold
|
|
114
170
|
|
|
115
171
|
# Flask specific configurations
|
|
116
172
|
self.config = FlaskLayoutConfig(
|
|
@@ -167,12 +223,17 @@ class FlaskDeployer:
|
|
|
167
223
|
}
|
|
168
224
|
# Get the order of parameters
|
|
169
225
|
param_order = list(self.viewer.parameters.keys())
|
|
170
|
-
# Also include the initial state
|
|
226
|
+
# Also include the initial state and configuration
|
|
171
227
|
return jsonify(
|
|
172
228
|
{
|
|
173
229
|
"params": param_info,
|
|
174
230
|
"param_order": param_order,
|
|
175
231
|
"state": self.viewer.state,
|
|
232
|
+
"config": {
|
|
233
|
+
"controls_position": self.config.controls_position,
|
|
234
|
+
"controls_width_percent": self.config.controls_width_percent,
|
|
235
|
+
"update_threshold": self.update_threshold,
|
|
236
|
+
},
|
|
176
237
|
}
|
|
177
238
|
)
|
|
178
239
|
|
|
@@ -315,7 +376,6 @@ class FlaskDeployer:
|
|
|
315
376
|
host: str = "127.0.0.1",
|
|
316
377
|
port: Optional[int] = None,
|
|
317
378
|
open_browser: bool = True,
|
|
318
|
-
**kwargs,
|
|
319
379
|
) -> None:
|
|
320
380
|
"""Starts the Flask development server."""
|
|
321
381
|
if not self.app:
|
|
@@ -329,26 +389,59 @@ class FlaskDeployer:
|
|
|
329
389
|
self.url = f"http://{self.host}:{self.port}"
|
|
330
390
|
print(f" * Syd Flask server running on {self.url}")
|
|
331
391
|
|
|
332
|
-
if open_browser:
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
#
|
|
341
|
-
#
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
392
|
+
# if open_browser:
|
|
393
|
+
|
|
394
|
+
# def wait_until_responsive(url, timeout=self.timeout_threshold):
|
|
395
|
+
# start_time = time.time()
|
|
396
|
+
# while time.time() - start_time < timeout:
|
|
397
|
+
# try:
|
|
398
|
+
# r = requests.get(url, timeout=0.5)
|
|
399
|
+
# if r.status_code == 200:
|
|
400
|
+
# return True
|
|
401
|
+
# except requests.exceptions.RequestException:
|
|
402
|
+
# pass
|
|
403
|
+
# time.sleep(0.1)
|
|
404
|
+
# return False
|
|
405
|
+
|
|
406
|
+
# def open_browser_tab_when_ready():
|
|
407
|
+
# if wait_until_responsive(self.url):
|
|
408
|
+
# out = webbrowser.open(self.url, new=1, autoraise=True)
|
|
409
|
+
# else:
|
|
410
|
+
# print(
|
|
411
|
+
# f"Could not open browser: server at {self.url} not responding."
|
|
412
|
+
# f"Increase the timeout_threshold to fix this! It's set to {self.timeout_threshold} seconds."
|
|
413
|
+
# "You can do this from viewer.show(timeout_threshold=...) or in the FlaskDeployer constructor."
|
|
414
|
+
# "Also, this is unexpected so please report this issue on GitHub."
|
|
415
|
+
# )
|
|
416
|
+
|
|
417
|
+
# threading.Thread(target=open_browser_tab_when_ready, daemon=True).start()
|
|
418
|
+
|
|
419
|
+
# # Run the Flask server using Werkzeug's run_simple
|
|
420
|
+
# # Pass debug status to run_simple for auto-reloading
|
|
421
|
+
# run_simple(
|
|
422
|
+
# self.host,
|
|
423
|
+
# self.port,
|
|
424
|
+
# self.app,
|
|
425
|
+
# use_reloader=False,
|
|
426
|
+
# use_debugger=self.debug,
|
|
427
|
+
# )
|
|
428
|
+
|
|
429
|
+
# 1) Spin up the server thread
|
|
430
|
+
srv_thread = ServerThread(self.host, self.port, self.app, debug=self.debug)
|
|
431
|
+
srv_thread.start()
|
|
432
|
+
|
|
433
|
+
# 2) Wait for the socket‐bind event (not for an HTTP 200)
|
|
434
|
+
if not srv_thread.ready.wait(timeout=self.timeout_threshold):
|
|
435
|
+
print(
|
|
436
|
+
f"[!] Server did not bind within {self.timeout_threshold:.1f}s; it may already be in use."
|
|
437
|
+
)
|
|
438
|
+
else:
|
|
439
|
+
# 3) Now we know the app is truly listening; open a focused window
|
|
440
|
+
if open_browser:
|
|
441
|
+
webbrowser.open(self.url, new=1, autoraise=True)
|
|
350
442
|
|
|
351
|
-
|
|
443
|
+
# 4) Keep the thread handle around so you can call srv_thread.shutdown()
|
|
444
|
+
self._server_thread = srv_thread
|
|
352
445
|
|
|
353
446
|
def deploy(self) -> None:
|
|
354
447
|
"""
|
|
@@ -598,7 +691,7 @@ class FlaskDeployer:
|
|
|
598
691
|
)
|
|
599
692
|
|
|
600
693
|
|
|
601
|
-
def _find_available_port(start_port=5000, max_attempts=
|
|
694
|
+
def _find_available_port(start_port=5000, max_attempts=1000):
|
|
602
695
|
"""
|
|
603
696
|
Find an available port starting from start_port.
|
|
604
697
|
(Identical to original)
|
|
@@ -60,14 +60,14 @@ body {
|
|
|
60
60
|
#controls-container {
|
|
61
61
|
display: grid;
|
|
62
62
|
grid-template-columns: 1fr;
|
|
63
|
-
gap:
|
|
63
|
+
gap: 5px;
|
|
64
64
|
}
|
|
65
65
|
|
|
66
66
|
/* Control groups */
|
|
67
67
|
.control-group {
|
|
68
68
|
display: flex;
|
|
69
69
|
flex-direction: column;
|
|
70
|
-
padding:
|
|
70
|
+
padding: 7px;
|
|
71
71
|
border: 1px solid #eee;
|
|
72
72
|
border-radius: 4px;
|
|
73
73
|
background-color: white;
|
|
@@ -76,7 +76,7 @@ body {
|
|
|
76
76
|
|
|
77
77
|
.control-label {
|
|
78
78
|
font-weight: 600;
|
|
79
|
-
margin-bottom:
|
|
79
|
+
margin-bottom: 0px;
|
|
80
80
|
color: #333;
|
|
81
81
|
text-transform: capitalize;
|
|
82
82
|
}
|
|
@@ -87,38 +87,13 @@ input[type="number"] {
|
|
|
87
87
|
padding: 8px 12px;
|
|
88
88
|
border: 1px solid #ddd;
|
|
89
89
|
border-radius: 4px;
|
|
90
|
-
font-size:
|
|
90
|
+
font-size: 12px;
|
|
91
91
|
width: 100%;
|
|
92
92
|
box-sizing: border-box;
|
|
93
93
|
}
|
|
94
94
|
|
|
95
|
-
/* Range inputs */
|
|
96
95
|
input[type="range"] {
|
|
97
96
|
width: 100%;
|
|
98
|
-
height: 6px;
|
|
99
|
-
background: #ddd;
|
|
100
|
-
border-radius: 3px;
|
|
101
|
-
outline: none;
|
|
102
|
-
margin: 10px 0;
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
input[type="range"]::-webkit-slider-thumb {
|
|
106
|
-
-webkit-appearance: none;
|
|
107
|
-
width: 18px;
|
|
108
|
-
height: 18px;
|
|
109
|
-
border-radius: 50%;
|
|
110
|
-
background: #3f51b5;
|
|
111
|
-
cursor: pointer;
|
|
112
|
-
border: 1px solid #2c3e90;
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
input[type="range"]::-moz-range-thumb {
|
|
116
|
-
width: 18px;
|
|
117
|
-
height: 18px;
|
|
118
|
-
border-radius: 50%;
|
|
119
|
-
background: #3f51b5;
|
|
120
|
-
cursor: pointer;
|
|
121
|
-
border: 1px solid #2c3e90;
|
|
122
97
|
}
|
|
123
98
|
|
|
124
99
|
/* Checkbox styling */
|
|
@@ -184,6 +159,49 @@ button.active {
|
|
|
184
159
|
font-style: italic;
|
|
185
160
|
}
|
|
186
161
|
|
|
162
|
+
/* Style all numeric controls consistently */
|
|
163
|
+
.numeric-control {
|
|
164
|
+
display: flex;
|
|
165
|
+
align-items: center;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
.numeric-control input[type="range"] {
|
|
169
|
+
flex: 1;
|
|
170
|
+
-webkit-appearance: none;
|
|
171
|
+
appearance: none;
|
|
172
|
+
height: 6px;
|
|
173
|
+
background: #ddd;
|
|
174
|
+
outline: none;
|
|
175
|
+
border-radius: 3px;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
.numeric-control input[type="range"]::-webkit-slider-thumb {
|
|
179
|
+
-webkit-appearance: none;
|
|
180
|
+
appearance: none;
|
|
181
|
+
width: 16px;
|
|
182
|
+
height: 16px;
|
|
183
|
+
background: #4a90e2;
|
|
184
|
+
cursor: pointer;
|
|
185
|
+
border-radius: 50%;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
.numeric-control input[type="range"]::-moz-range-thumb {
|
|
189
|
+
width: 16px;
|
|
190
|
+
height: 16px;
|
|
191
|
+
background: #4a90e2;
|
|
192
|
+
cursor: pointer;
|
|
193
|
+
border-radius: 50%;
|
|
194
|
+
border: none;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
.numeric-control input[type="number"] {
|
|
198
|
+
width: 60px;
|
|
199
|
+
padding: 4px 1px;
|
|
200
|
+
border: 1px solid #ddd;
|
|
201
|
+
border-radius: 1px;
|
|
202
|
+
margin-left: 6px;
|
|
203
|
+
}
|
|
204
|
+
|
|
187
205
|
/* Range slider styles */
|
|
188
206
|
.range-container {
|
|
189
207
|
display: flex;
|
|
@@ -194,7 +212,7 @@ button.active {
|
|
|
194
212
|
.range-inputs {
|
|
195
213
|
display: flex;
|
|
196
214
|
justify-content: space-between;
|
|
197
|
-
margin-bottom:
|
|
215
|
+
margin-bottom: 5px;
|
|
198
216
|
}
|
|
199
217
|
|
|
200
218
|
.range-input {
|
|
@@ -204,77 +222,92 @@ button.active {
|
|
|
204
222
|
|
|
205
223
|
.range-slider-container {
|
|
206
224
|
position: relative;
|
|
207
|
-
margin: 10px 0;
|
|
208
|
-
background: linear-gradient(
|
|
225
|
+
margin: 10px 0 15px 0;
|
|
226
|
+
background: linear-gradient(
|
|
227
|
+
to right,
|
|
209
228
|
#ddd 0%,
|
|
210
229
|
#ddd var(--min-pos, 0%),
|
|
211
|
-
#
|
|
212
|
-
#
|
|
230
|
+
#4a90e2 var(--min-pos, 0%),
|
|
231
|
+
#4a90e2 var(--max-pos, 100%),
|
|
213
232
|
#ddd var(--max-pos, 100%),
|
|
214
233
|
#ddd 100%);
|
|
215
|
-
border-radius:
|
|
216
|
-
height:
|
|
234
|
+
border-radius: 4px;
|
|
235
|
+
height: 6px;
|
|
236
|
+
width: 100%;
|
|
217
237
|
}
|
|
218
238
|
|
|
219
239
|
.range-slider {
|
|
220
240
|
position: absolute;
|
|
221
|
-
top:
|
|
222
|
-
transform: translateY(-50%);
|
|
241
|
+
top: 0;
|
|
223
242
|
left: 0;
|
|
224
243
|
width: 100%;
|
|
225
|
-
|
|
226
|
-
-webkit-appearance: none;
|
|
244
|
+
height: 100%;
|
|
227
245
|
appearance: none;
|
|
228
|
-
|
|
246
|
+
-webkit-appearance: none;
|
|
229
247
|
cursor: pointer;
|
|
248
|
+
background: none;
|
|
230
249
|
margin: 0;
|
|
231
|
-
|
|
250
|
+
padding: 0;
|
|
251
|
+
pointer-events: none;
|
|
232
252
|
}
|
|
233
253
|
|
|
234
254
|
/* Transparent Track for Webkit */
|
|
235
|
-
.range-slider::-webkit-slider-runnable-track {
|
|
255
|
+
.range-slider-container .range-slider::-webkit-slider-runnable-track {
|
|
236
256
|
background: transparent;
|
|
237
|
-
border:
|
|
238
|
-
|
|
257
|
+
border-radius: 2px;
|
|
258
|
+
height: 8px;
|
|
239
259
|
}
|
|
240
260
|
|
|
241
261
|
/* Transparent Track for Firefox */
|
|
242
|
-
.range-slider::-moz-range-track {
|
|
262
|
+
.range-slider-container .range-slider::-moz-range-track {
|
|
243
263
|
background: transparent;
|
|
244
|
-
border:
|
|
245
|
-
|
|
264
|
+
border-radius: 2px;
|
|
265
|
+
height: 8px;
|
|
246
266
|
}
|
|
247
267
|
|
|
248
|
-
.range-slider.
|
|
249
|
-
z-index: 2;
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
.range-slider::-webkit-slider-thumb {
|
|
268
|
+
.range-slider-container .range-slider::-webkit-slider-thumb {
|
|
253
269
|
pointer-events: auto;
|
|
254
270
|
-webkit-appearance: none;
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
border-radius: 50%;
|
|
259
|
-
background: #3f51b5;
|
|
271
|
+
width: 16px;
|
|
272
|
+
height: 16px;
|
|
273
|
+
background: #4a90e2;
|
|
260
274
|
cursor: pointer;
|
|
261
|
-
border:
|
|
275
|
+
border-radius: 50%;
|
|
276
|
+
margin-top: -4px; /* center on track */
|
|
262
277
|
}
|
|
263
278
|
|
|
264
|
-
.range-slider::-moz-range-thumb {
|
|
279
|
+
.range-slider-container .range-slider::-moz-range-thumb {
|
|
265
280
|
pointer-events: auto;
|
|
266
|
-
width:
|
|
267
|
-
height:
|
|
268
|
-
|
|
269
|
-
background: #3f51b5;
|
|
281
|
+
width: 16px;
|
|
282
|
+
height: 16px;
|
|
283
|
+
background: #4a90e2;
|
|
270
284
|
cursor: pointer;
|
|
271
|
-
border:
|
|
285
|
+
border-radius: 50%;
|
|
286
|
+
margin-top: -4px; /* center on track */
|
|
272
287
|
}
|
|
273
288
|
|
|
274
289
|
.min-slider {
|
|
275
|
-
z-index:
|
|
290
|
+
z-index: 5;
|
|
276
291
|
}
|
|
277
292
|
|
|
278
293
|
.max-slider {
|
|
279
|
-
z-index:
|
|
294
|
+
z-index: 5;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
#status-display {
|
|
298
|
+
margin-top: 10px;
|
|
299
|
+
margin-bottom: 3px;
|
|
300
|
+
padding: 8px;
|
|
301
|
+
border-radius: 4px;
|
|
302
|
+
background-color: #ffffff;
|
|
303
|
+
border: 1px solid #e5e7eb;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
.status-message {
|
|
307
|
+
background-color: #e0e0e0;
|
|
308
|
+
color: #000;
|
|
309
|
+
padding: 2px 6px;
|
|
310
|
+
border-radius: 4px;
|
|
311
|
+
font-size: 90%;
|
|
312
|
+
margin-left: 8px;
|
|
280
313
|
}
|