-
-
Notifications
You must be signed in to change notification settings - Fork 138
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge branch 'main' into share-magic-wormhole
- Loading branch information
Showing
23 changed files
with
830 additions
and
40 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
name: Python CI | ||
|
||
on: | ||
push: | ||
branches: | ||
- '**' | ||
|
||
jobs: | ||
run-ci: | ||
runs-on: windows-latest | ||
|
||
steps: | ||
- name: Checkout code | ||
uses: actions/checkout@v3 | ||
|
||
- name: Set up Python | ||
uses: actions/setup-python@v3 | ||
with: | ||
python-version: '3.10' | ||
|
||
- name: Install dependencies | ||
run: | | ||
pip install wheel | ||
pip install -r requirements.txt | ||
pip install -e . | ||
- name: Check formatting with Black | ||
run: | | ||
black --check --exclude "(src|alembic)/" . | ||
- name: Run headless tests | ||
uses: coactions/setup-xvfb@v1 | ||
with: | ||
run: python -m pytest | ||
working-directory: ./ # Optional: Specify the working directory if needed | ||
options: # Optional: Add any additional options or arguments for pytest | ||
|
||
- name: flake8 Lint | ||
uses: py-actions/flake8@v2 | ||
with: | ||
plugins: "flake8-docstrings" | ||
extra-args: "--docstring-convention=google --exclude=alembic/versions" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -19,6 +19,9 @@ cache | |
.VSCode | ||
.vsCode | ||
|
||
#Idea | ||
.idea | ||
|
||
# Generated performance charts | ||
performance | ||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
from openadapt.app.main import run_app | ||
|
||
run_app() |
Binary file not shown.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
import os | ||
import subprocess | ||
from pathlib import Path | ||
|
||
import nicegui | ||
|
||
spec = [ | ||
"pyi-makespec", | ||
f"{Path(__file__).parent}/main.py", | ||
f"--icon={Path(__file__).parent}/assets/logo.ico", | ||
"--name", | ||
"OpenAdapt", # name | ||
# "--onefile", # trade startup speed for smaller file size | ||
"--onedir", | ||
"--windowed", # prevent console appearing, only use with ui.run(native=True, ...) | ||
"--add-data", | ||
f"{Path(nicegui.__file__).parent}{os.pathsep}nicegui", | ||
] | ||
|
||
subprocess.call(spec) | ||
|
||
# add import sys ; sys.setrecursionlimit(sys.getrecursionlimit() * 5) to line 2 of OpenAdapt.spec | ||
with open("OpenAdapt.spec", "r+") as f: | ||
lines = f.readlines() | ||
lines[1] = "import sys ; sys.setrecursionlimit(sys.getrecursionlimit() * 5)\n" | ||
f.seek(0) | ||
f.truncate() | ||
f.writelines(lines) | ||
|
||
subprocess.call(["pyinstaller", "OpenAdapt.spec"]) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,88 @@ | ||
import signal | ||
from nicegui import ui | ||
from subprocess import Popen | ||
from openadapt.app.objects.local_file_picker import LocalFilePicker | ||
from openadapt.app.util import set_dark, sync_switch | ||
|
||
PROC = None | ||
|
||
|
||
def settings(dark_mode): | ||
with ui.dialog() as settings, ui.card(): | ||
s = ui.switch("Dark mode", on_change=lambda: set_dark(dark_mode, s.value)) | ||
sync_switch(s, dark_mode) | ||
ui.button("Close", on_click=lambda: settings.close()) | ||
|
||
settings.open() | ||
|
||
|
||
def select_import(f): | ||
async def pick_file(): | ||
result = await LocalFilePicker(".") | ||
ui.notify(f"Selected {result[0]}" if result else "No file selected.") | ||
selected_file.text = result[0] if result else "" | ||
import_button.enabled = True if result else False | ||
|
||
with ui.dialog() as import_dialog, ui.card(): | ||
with ui.column(): | ||
ui.button("Select File", on_click=pick_file).props("icon=folder") | ||
selected_file = ui.label("") | ||
selected_file.visible = False | ||
import_button = ui.button( | ||
"Import", on_click=lambda: f(selected_file.text, delete.value) | ||
) | ||
import_button.enabled = False | ||
delete = ui.checkbox("Delete file after import") | ||
|
||
import_dialog.open() | ||
|
||
|
||
def recording_prompt(options, record_button): | ||
if PROC is None: | ||
with ui.dialog() as dialog, ui.card(): | ||
ui.label("Enter a name for the recording: ") | ||
ui.input( | ||
label="Name", | ||
placeholder="test", | ||
autocomplete=options, | ||
on_change=lambda e: result.set_text(e), | ||
) | ||
result = ui.label() | ||
|
||
with ui.row(): | ||
ui.button("Close", on_click=dialog.close) | ||
ui.button("Enter", on_click=lambda: on_record()) | ||
|
||
dialog.open() | ||
|
||
def terminate(): | ||
global PROC | ||
PROC.send_signal(signal.SIGINT) | ||
|
||
# wait for process to terminate | ||
PROC.wait() | ||
ui.notify("Stopped recording") | ||
record_button._props["name"] = "radio_button_checked" | ||
record_button.on("click", lambda: recording_prompt(options, record_button)) | ||
|
||
PROC = None | ||
|
||
def begin(): | ||
name = result.text.__getattribute__("value") | ||
|
||
ui.notify( | ||
f"Recording {name}... Press CTRL + C in terminal window to cancel", | ||
) | ||
PROC = Popen( | ||
"python3 -m openadapt.record " + name, | ||
shell=True, | ||
) | ||
record_button._props["name"] = "stop" | ||
record_button.on("click", lambda: terminate()) | ||
record_button.update() | ||
return PROC | ||
|
||
def on_record(): | ||
global PROC | ||
dialog.close() | ||
PROC = begin() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,82 @@ | ||
import threading | ||
import base64 | ||
import os | ||
|
||
from nicegui import app, ui | ||
|
||
from openadapt import replay, visualize | ||
from openadapt.app.cards import recording_prompt, select_import, settings | ||
from openadapt.app.util import clear_db, on_export, on_import | ||
from openadapt.app.objects.console import Console | ||
|
||
SERVER = "127.0.0.1:8000/upload" | ||
|
||
|
||
def run_app(): | ||
file = os.path.dirname(__file__) | ||
app.native.window_args["resizable"] = False # too many issues with resizing | ||
app.native.start_args["debug"] = False | ||
|
||
dark = ui.dark_mode() | ||
logger = None | ||
|
||
# Add logo | ||
# right align icon | ||
with ui.row().classes("w-full justify-right"): | ||
# settings | ||
|
||
# alignment trick | ||
with ui.avatar(color="white" if dark else "black", size=128): | ||
logo_base64 = base64.b64encode(open(f"{file}/assets/logo.png", "rb").read()) | ||
img = bytes( | ||
f"data:image/png;base64,{(logo_base64.decode('utf-8'))}", | ||
encoding="utf-8", | ||
) | ||
ui.image(img.decode("utf-8")) | ||
ui.icon("settings").tooltip("Settings").on("click", lambda: settings(dark)) | ||
ui.icon("delete").on("click", lambda: clear_db(log=logger)).tooltip( | ||
"Clear all recorded data" | ||
) | ||
ui.icon("upload").tooltip("Export Data").on("click", lambda: on_export(SERVER)) | ||
ui.icon("download").tooltip("Import Data").on( | ||
"click", lambda: select_import(on_import) | ||
) | ||
ui.icon("share").tooltip("Share").on( | ||
"click", lambda: (_ for _ in ()).throw(Exception(NotImplementedError)) | ||
) | ||
|
||
# Recording description autocomplete | ||
options = ["test"] | ||
|
||
with ui.splitter(value=20) as splitter: | ||
splitter.classes("w-full h-full") | ||
with splitter.before: | ||
with ui.column().classes("w-full h-full"): | ||
record_button = ( | ||
ui.icon("radio_button_checked", size="64px") | ||
.on("click", lambda: recording_prompt(options, record_button)) | ||
.tooltip("Record a new replay / Stop recording") | ||
) | ||
ui.icon("visibility", size="64px").on( | ||
"click", lambda: threading.Thread(target=visualize.main).start() | ||
).tooltip("Visualize the latest replay") | ||
|
||
ui.icon("play_arrow", size="64px").on( | ||
"click", lambda: replay.replay("NaiveReplayStrategy") | ||
).tooltip("Play the latest replay") | ||
with splitter.after: | ||
logger = Console() | ||
logger.log.style("height: 250px;, width: 300px;") | ||
splitter.enabled = False | ||
|
||
ui.run( | ||
title="OpenAdapt Client", | ||
native=True, | ||
window_size=(400, 400), | ||
fullscreen=False, | ||
reload=False, | ||
) | ||
|
||
|
||
if __name__ == "__main__": | ||
run_app() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
import sys | ||
|
||
from nicegui import ui | ||
|
||
|
||
class Console(object): | ||
def __init__(self): | ||
self.log = ui.log().classes("w-full h-20") | ||
self.old_stderr = sys.stderr | ||
sys.stderr = self | ||
|
||
def write(self, data): | ||
self.log.push(data[:-1]) | ||
self.log.update() | ||
|
||
def flush(self): | ||
self.log.update() | ||
|
||
def reset(self): | ||
self.log.clear() | ||
sys.stderr = self.old_stderr |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,108 @@ | ||
# retrieved from https://github.com/zauberzeug/nicegui/tree/main/examples/local_file_picker | ||
|
||
from pathlib import Path | ||
from typing import Dict, Optional | ||
|
||
from nicegui import ui | ||
|
||
|
||
class LocalFilePicker(ui.dialog): | ||
def __init__( | ||
self, | ||
directory: str, | ||
*, | ||
upper_limit: Optional[str] = ..., | ||
multiple: bool = False, | ||
show_hidden_files: bool = False, | ||
dark_mode: bool = False, | ||
) -> None: | ||
"""Local File Picker | ||
This is a simple file picker that allows you to select a file from the local filesystem where NiceGUI is running. | ||
:param directory: The directory to start in. | ||
:param upper_limit: The directory to stop at (None: no limit, default: same as the starting directory). | ||
:param multiple: Whether to allow multiple files to be selected. | ||
:param show_hidden_files: Whether to show hidden files. | ||
""" | ||
super().__init__() | ||
|
||
self.path = Path(directory).expanduser() | ||
if upper_limit is None: | ||
self.upper_limit = None | ||
else: | ||
self.upper_limit = Path( | ||
directory if upper_limit == ... else upper_limit | ||
).expanduser() | ||
self.show_hidden_files = show_hidden_files | ||
|
||
with self, ui.card(): | ||
self.grid = ( | ||
ui.aggrid( | ||
{ | ||
"columnDefs": [{"field": "name", "headerName": "File"}], | ||
"rowSelection": "multiple" if multiple else "single", | ||
}, | ||
html_columns=[0], | ||
) | ||
.classes("w-96") | ||
.on("cellDoubleClicked", self.handle_double_click) | ||
) | ||
with ui.row().classes("w-full justify-end"): | ||
ui.button("Cancel", on_click=self.close).props("outline") | ||
ui.button("Ok", on_click=self._handle_ok) | ||
self.update_grid() | ||
|
||
def update_grid(self) -> None: | ||
paths = list(self.path.glob("*")) | ||
if not self.show_hidden_files: | ||
paths = [p for p in paths if not p.name.startswith(".")] | ||
paths.sort(key=lambda p: p.name.lower()) | ||
paths.sort(key=lambda p: not p.is_dir()) | ||
|
||
self.grid.options["rowData"] = [ | ||
{ | ||
"name": f"📁 <strong>{p.name}</strong>" if p.is_dir() else p.name, | ||
"path": str(p), | ||
} | ||
for p in paths | ||
] | ||
if ( | ||
self.upper_limit is None | ||
and self.path != self.path.parent | ||
or self.upper_limit is not None | ||
and self.path != self.upper_limit | ||
): | ||
self.grid.options["rowData"].insert( | ||
0, | ||
{ | ||
"name": "📁 <strong>..</strong>", | ||
"path": str(self.path.parent), | ||
}, | ||
) | ||
|
||
self.grid.update() | ||
|
||
async def handle_double_click(self, msg: Dict) -> None: | ||
self.path = Path(msg["args"]["data"]["path"]) | ||
if self.path.is_dir(): | ||
self.update_grid() | ||
else: | ||
self.submit([str(self.path)]) | ||
|
||
async def _handle_ok(self): | ||
rows = await ui.run_javascript( | ||
f"getElement({self.grid.id}).gridOptions.api.getSelectedRows()" | ||
) | ||
self.submit([r["path"] for r in rows]) | ||
|
||
|
||
async def pick_file(): | ||
result = await LocalFilePicker("~", multiple=True) | ||
ui.notify(f"You chose {result}") | ||
|
||
|
||
if __name__ in {"__main__", "__mp_main__"}: | ||
ui.button("Choose file", on_click=pick_file).props("icon=folder") | ||
|
||
ui.run(native=True) |
Oops, something went wrong.