diff --git a/travo/dashboards.py b/travo/dashboards.py
index 399e40bb64857c40a27b302fdf9500c7cd8cb114..59784f17dd456952bc8c424c230a25c21ada34f8 100644
--- a/travo/dashboards.py
+++ b/travo/dashboards.py
@@ -20,6 +20,7 @@ import logging
import os
import subprocess
import requests
+import textwrap
from threading import Thread
from typing import Any, Callable, Dict, List, Optional, Tuple, Union
@@ -48,6 +49,7 @@ except ImportError:
)
from IPython.display import display, Javascript # type: ignore
+import traitlets # type: ignore
from .assignment import Assignment
@@ -507,6 +509,62 @@ class StatusBar(VBox):
).start()
+class LinkAsButton(ipywidgets.HTML):
+ """
+ A widget displaying an html link with a button-like style and interface
+ """
+
+ disabled = traitlets.Bool(False)
+
+ def __init__(
+ self,
+ description: str,
+ target: Optional[str] = None,
+ button_style: str = "",
+ icon: Optional[str] = None,
+ tooltip: Optional[str] = None,
+ layout: Optional[Layout] = None,
+ disabled: bool = False,
+ ):
+ super().__init__("")
+
+ self.target = target
+
+ if icon:
+ description = f'' + description
+ self.button_content = description
+
+ classes = "lm-Widget jupyter-widgets jupyter-button widget-button"
+ if button_style:
+ classes += f" mod-{button_style}"
+ self.classes = classes
+
+ self.title = tooltip
+
+ if disabled:
+ self.disabled = disabled # will trigger an update
+ else:
+ self.update()
+
+ def update(self) -> None:
+ opacity = 0.5 if self.disabled else 1
+ content = textwrap.dedent(
+ f"""
+
+ {self.button_content}
+
+ """
+ )
+ if self.target is not None:
+ content = f'{content}'
+
+ self.value = content
+
+ @traitlets.observe("disabled")
+ def _disabled_changed(self, change: dict) -> None:
+ self.update()
+
+
class AssignmentStudentDashboard(HBox):
def __init__(
self,
@@ -526,10 +584,6 @@ class AssignmentStudentDashboard(HBox):
)
self.status_bar = status_bar
- from ipylab import JupyterFrontEnd # type: ignore
-
- self.jupyter_front_end = JupyterFrontEnd()
-
layout = Layout(width="initial")
self.nameUI = HTML(self.name, layout=layout)
self.fetchUI = Button(
@@ -551,7 +605,7 @@ class AssignmentStudentDashboard(HBox):
)
self.submitUI.on_click(lambda event: self.submit())
- self.work_dir_UI = Button(
+ self.work_dir_UI = LinkAsButton(
description=_("open"),
button_style="primary",
icon="edit",
@@ -559,7 +613,6 @@ class AssignmentStudentDashboard(HBox):
layout=layout,
disabled=True,
)
- self.work_dir_UI.on_click(self.open_work_dir_callback)
self.scoreUI = HTML("", tooltip=_("browse feedback"))
self.updateUI = Button(
description="",
@@ -602,6 +655,23 @@ class AssignmentStudentDashboard(HBox):
Thread(target=self.update).start()
def update(self) -> None:
+ # First take care of the UI for the local work dir for the assignment
+ # as it does not require authentication
+ self.work_dir_UI.disabled = True
+ has_assignment_dir = (
+ self.assignment.assignment_dir is not None
+ and os.path.exists(self.assignment.assignment_dir)
+ )
+ if has_assignment_dir:
+ assert self.assignment.assignment_dir is not None
+ index_files = ["index.md", "index.ipynb", "README.md", "README.ipynb"]
+ for x in index_files:
+ file = os.path.join(self.assignment.assignment_dir, x)
+ if os.path.isfile(file):
+ self.work_dir_UI.target = file
+ self.work_dir_UI.disabled = False
+ break
+
# For now, fetching the assignment status requires the user to
# be logged in. Fails gracefuly if this is not yet the case.
try:
@@ -647,13 +717,7 @@ class AssignmentStudentDashboard(HBox):
self.submissionUI.value = ""
self.submitUI.tooltip = _("submit assignment", assignment_name=self.name)
- self.submitUI.disabled = True
- self.work_dir_UI.disabled = True
- if self.assignment.assignment_dir is not None and os.path.exists(
- self.assignment.assignment_dir
- ):
- self.work_dir_UI.disabled = False
- self.submitUI.disabled = False
+ self.submitUI.disabled = not has_assignment_dir
if status.autograde_status == "success":
# these two s/could be provided by autograde_status
@@ -698,52 +762,6 @@ class AssignmentStudentDashboard(HBox):
on_success=self.update,
)
- def open_work_dir_callback(self, event: Any) -> None:
- self.work_dir_UI.disabled = True
- self.status_bar.run( # run_in_subthread(
- action=_("open work dir", assignment_name=self.name),
- command=self.open_work_dir,
- on_finished=lambda: setattr(self.work_dir_UI, "disabled", False),
- )
-
- def open_work_dir(self) -> None:
- if self.assignment.assignment_dir is None:
- raise ValueError(_("cannot open work dir unset"))
- if os.path.isabs(self.assignment.assignment_dir):
- raise NotImplementedError(
- _(
- _("cannot open work dir absolute"),
- work_dir=self.assignment.assignment_dir,
- )
- )
- path = os.path.dirname(self.jupyter_front_end.sessions.current_session["path"])
- # TODO : if README.md does not exist neither, try to open gitlab file browser
- index_files = ["index.md", "index.ipynb", "README.md", "README.ipynb"]
- for x in index_files:
- file = os.path.join(self.assignment.assignment_dir, x)
- if os.path.isfile(file):
- self.jupyter_front_end.commands.execute(
- "docmanager:open",
- {
- "path": path + "/" + file, # Generalize if there is no index.md
- "factory": "Notebook",
- # 'options': {
- # 'mode': 'split-right'
- # },
- "kernelPreference": {
- "shutdownOnClose": True,
- },
- },
- )
- return
- raise FileNotFoundError(
- _(
- "no index file",
- assignment_dir=self.assignment.assignment_dir,
- index_files=" ".join(index_files),
- )
- )
-
def other_action(self) -> None:
action = self.other_actionsUI.value
if not action: