Skip to content

Commit

Permalink
feat: Add an option to launch a lab in a new window
Browse files Browse the repository at this point in the history
Add a new view to the XBlock for launching a lab in a new window.
Consolidate the use of the guacamole-common-js library between
the inline LMS lab and the lab launched in a new window.
  • Loading branch information
Maari Tamm committed Dec 12, 2023
1 parent 6564749 commit d9b0be7
Show file tree
Hide file tree
Showing 26 changed files with 837 additions and 585 deletions.
4 changes: 4 additions & 0 deletions Changelog.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
Unreleased
-------------------------
* [Enhancement] Add an option to launch the lab in a new window.

Version 7.7.3 (2023-12-04)
-------------------------
* [Bug fix] Fix private key getting lost after a stack resume failure.
Expand Down
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ two steps:
delete_interval: 3600
delete_task_timeout: 900
guacamole_js_version: 1.5.2
enable_fullscreen: false
instructions_layout: above
js_timeouts:
check: 5000
Expand Down Expand Up @@ -192,6 +193,9 @@ This is a brief explanation of each:
from the terminal window. (Default: `above`; this is currently an
experimental feature)

* `enable_fullscreen`: An option to allow learners to launch a lab in fullsceen mode,
in a separate browser window. (Default: `false`)

* `lab_usage_limit`: Allocate limited time per user for labs across the platform,
in seconds. (Default is `None`, meaning there is no time limit).

Expand Down Expand Up @@ -513,6 +517,10 @@ The following are optional:
* `hidden`: An option to hide the lab itself in the browser while spinning up
the lab environment in the background. Default is `False`.

* `enable_fullscreen`: An option to allow learners to launch a lab in fullsceen mode,
in a separate browser window. Overrides the globally defined setting.
Default is `False`.

* `progress_check_label`: Set a label for the progress check button.
For example: `Submit Answer` or `Check Progress` (Default).

Expand Down
1 change: 1 addition & 0 deletions hastexo/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,7 @@
"terminal_font_name": "monospace",
"terminal_font_size": "10",
"instructions_layout": "above",
"enable_fullscreen": False,
"launch_timeout": 900,
"remote_exec_timeout": 300,
"suspend_timeout": 120,
Expand Down
154 changes: 109 additions & 45 deletions hastexo/hastexo.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import re
import string
import textwrap
from webob import Response

from xblock.core import XBlock, XML_NAMESPACES
from xblock.fields import Scope, Float, String, Dict, List, Integer, Boolean
Expand Down Expand Up @@ -161,6 +162,12 @@ class HastexoXBlock(XBlock,
"not display hints and feedback. Default is \"Progress check "
"result\"."
)
enable_fullscreen = Boolean(
default=False,
scope=Scope.settings,
help="Enable the learners to launch a lab in fulscreen mode on a "
"separate browser window. Overrides the globally defined setting."
)

# Set via XML
hook_events = Dict(
Expand Down Expand Up @@ -241,7 +248,8 @@ class HastexoXBlock(XBlock,
'providers',
'tests',
'read_only',
'hidden')
'hidden',
'enable_fullscreen')

has_author_view = True
has_score = True
Expand Down Expand Up @@ -565,20 +573,93 @@ def get_stack_name(self):

return stack_name

@staticmethod
def _get_text_js_url():
def get_js_urls(self):
"""
Returns the Javascript translation file
for the currently selected language, if supported.
Returns a dict of urls for the Javascript files.
"""
settings = get_xblock_settings()
plugins_url = self.runtime.local_resource_url(
self, 'public/js/plugins.js')

# guacamole common library url
guac_js_version = settings.get("guacamole_js_version", "1.5.2")
guac_common_url = (
self.runtime.local_resource_url(
self,
f'public/js/guacamole-common-js/{guac_js_version}-all.min.js'))

# HastexoGuacamoleClient url
guac_client_url = self.runtime.local_resource_url(
self, 'public/js/client.js')

# HastexoXBlock url
main_js_url = self.runtime.local_resource_url(
self, 'public/js/main.js')

js_urls = {
"plugins_url": plugins_url,
"guac_common_url": guac_common_url,
"guac_client_url": guac_client_url,
"main_js_url": main_js_url,
}

# Translation file url (if supported)
lang_code = translation.get_language()
if lang_code and lang_code in SUPPORTED_LANGUAGES:
text_js = f'public/js/translations/{lang_code}/text.js'
if pkg_resources.resource_exists(loader.module_name, text_js):
return text_js
logger.warning(
"Javascript translation file missing or language is not supported")
return None
text_js_url = f'public/js/translations/{lang_code}/text.js'
if pkg_resources.resource_exists(loader.module_name, text_js_url):
js_urls["text_js_url"] = self.runtime.local_resource_url(
self, text_js_url)
else:
logger.warning("Javascript translation file missing or "
"language is not supported")
return js_urls

def get_context(self, stack=None):
settings = get_xblock_settings()
stack = stack or self.get_stack()

context = {
"stack_name": stack.name,
"terminal_url": settings.get("terminal_url"),
"keepalive_url": self.runtime.handler_url(self, 'keepalive'),
"timeouts": settings.get("js_timeouts"),
"has_tests": len(self.tests) > 0,
"protocol": self.stack_protocol,
"ports": self.ports,
"port": stack.port,
"instructions_layout": settings.get("instructions_layout"),
"read_only": self.read_only or self.hidden,
"hidden": self.hidden,
"progress_check_label": self.progress_check_label,
"show_hints_on_error": self.show_hints_on_error,
"show_feedback": self.show_feedback,
"progress_check_result_heading": self.progress_check_result_heading
}

return context

@XBlock.handler
def launch_new_window(self, request, suffix=''):
"""
The fullscreen lab view, opened in a new browser window.
"""
# Get context
context = self.get_context()

# Pass the token (for keepalives)
context["csrftoken"] = request.cookies.get('csrftoken')

# Get the JavaScript urls
js_urls = self.get_js_urls().items()
for key, value in js_urls:
context[key] = value

# Render the lab template
template = loader.render_django_template(
"static/html/lab.html", context)

return Response(template)

def student_view(self, context=None):
"""
Expand All @@ -594,6 +675,12 @@ def student_view(self, context=None):
self.stack_run = "%s_%s" % (course_id.course, course_id.run)
self.stack_name = self.get_stack_name()

# Fullscreen mode: if the XBlock attribute does not match the global
# setting, allow it override the value per instance
enable_fullscreen = settings.get("enable_fullscreen")
if enable_fullscreen != self.enable_fullscreen:
enable_fullscreen = self.enable_fullscreen

frag = Fragment()

# Render children
Expand All @@ -608,51 +695,28 @@ def student_view(self, context=None):
i18n_service = self.runtime.service(self, "i18n")

frag.add_content(loader.render_django_template(
"static/html/main.html", {"child_content": child_content},
"static/html/main.html",
{"child_content": child_content,
"enable_fullscreen": enable_fullscreen},
i18n_service=i18n_service
))

# Add the public CSS and JS
# Add the public CSS and JS files
frag.add_css_url(
self.runtime.local_resource_url(self, 'public/css/main.css')
)
frag.add_javascript_url(
self.runtime.local_resource_url(self, 'public/js/plugins.js')
)
frag.add_javascript_url(
self.runtime.local_resource_url(self, 'public/js/main.js')
)
text_js_url = self._get_text_js_url()
if text_js_url:
frag.add_javascript_url(
self.runtime.local_resource_url(self, text_js_url)
)
guac_js_version = settings.get("guacamole_js_version", "1.5.2")
frag.add_javascript_url(
self.runtime.local_resource_url(
self,
f'public/js/guacamole-common-js/{guac_js_version}-all.min.js')
)
js_urls = list(self.get_js_urls().values())
for url in js_urls:
frag.add_javascript_url(url)

# Create the stack in the database
stack = self.create_stack(settings, course_id, student_id)

# Get context
context = self.get_context(stack)

# Call the JS initialization function
frag.initialize_js('HastexoXBlock', {
"terminal_url": settings.get("terminal_url"),
"timeouts": settings.get("js_timeouts"),
"has_tests": len(self.tests) > 0,
"protocol": self.stack_protocol,
"ports": self.ports,
"port": stack.port,
"instructions_layout": settings.get("instructions_layout"),
"read_only": self.read_only or self.hidden,
"hidden": self.hidden,
"progress_check_label": self.progress_check_label,
"show_hints_on_error": self.show_hints_on_error,
"show_feedback": self.show_feedback,
"progress_check_result_heading": self.progress_check_result_heading
})
frag.initialize_js('HastexoXBlock', context)

return frag

Expand Down
Loading

0 comments on commit d9b0be7

Please sign in to comment.