diff --git a/pyproject.toml b/pyproject.toml index e47a579..77bc520 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -52,20 +52,26 @@ docs = [ "sphinx-book-theme", ] -# XBlock entry points will be added here as xblocks are migrated -# Example: -# [project.entry-points."xblock.v1"] -# foo = "foo_xblock:FooXBlock" +[project.entry-points."xblock.v1"] +audio = "audio:AudioXBlock" # Packages live in src/ but are installed without the src prefix # e.g., src/foo_xblock/ is installed as foo_xblock [tool.setuptools] package-dir = {"" = "src"} +include-package-data = true [tool.setuptools.packages.find] where = ["src"] exclude = ["tests*"] +[tool.setuptools.package-data] +"*" = [ + "static/**/*", + "templates/**/*", + "translations/**/*", +] + # Ruff configuration # https://docs.astral.sh/ruff/configuration/ [tool.ruff] diff --git a/src/audio/README.md b/src/audio/README.md new file mode 100644 index 0000000..91b79cb --- /dev/null +++ b/src/audio/README.md @@ -0,0 +1,9 @@ +AudioXBlock +=========== + +This is a simple XBlock which will play audio files as an HTML5 audio +element. If unavailable, it will fall back to an embed element. + +Usage: + + diff --git a/src/audio/__init__.py b/src/audio/__init__.py new file mode 100644 index 0000000..50d2e33 --- /dev/null +++ b/src/audio/__init__.py @@ -0,0 +1 @@ +from .audio import AudioXBlock as AudioXBlock diff --git a/src/audio/audio.py b/src/audio/audio.py new file mode 100644 index 0000000..02c2a45 --- /dev/null +++ b/src/audio/audio.py @@ -0,0 +1,98 @@ +"""TO-DO: This XBlock will play an MP3 file as an HTML5 audio element.""" + +from xblock.core import XBlock +from xblock.fields import Scope, String +from xblock.fragment import Fragment +from xblock.utils.resources import ResourceLoader + +resource_loader = ResourceLoader(__name__) + + +def _(text): + """ + Make `_` a no-op, so we can scrape strings + """ + return text + + +class AudioXBlock(XBlock): + """ + This XBlock will play an MP3 file as an HTML5 audio element. + """ + + # Fields are defined on the class. You can access them in your code as + # self.. + src = String( + scope=Scope.settings, + help=_("URL for MP3 file to play"), + ) + + @staticmethod + def resource_string(path): + """Handy helper for getting resources from our kit.""" + return resource_loader.load_unicode(path) + + # TO-DO: change this view to display your data your own way. + def student_view(self, context=None): + """ + The primary view of the AudioXBlock, shown to students + when viewing courses. + """ + frag = Fragment() + frag.add_content( + resource_loader.render_django_template( + "templates/html/audio.html", + context={ + "src": self.src, + }, + i18n_service=self.runtime.service(self, "i18n"), + ) + ) + frag.add_css(self.resource_string("static/css/audio.css")) + return frag + + def studio_view(self, context): + """ + The view for editing the AudioXBlock parameters inside Studio. + """ + frag = Fragment() + frag.add_content( + resource_loader.render_django_template( + "templates/html/audio_edit.html", + context={ + "src": self.src, + }, + i18n_service=self.runtime.service(self, "i18n"), + ) + ) + js = self.resource_string("static/js/src/audio_edit.js") + frag.add_javascript(js) + frag.initialize_js("AudioEditBlock") + + return frag + + @XBlock.json_handler + def studio_submit(self, data, suffix=""): + """ + Called when submitting the form in Studio. + """ + self.src = data.get("src") + + return {"result": "success"} + + # TO-DO: change this to create the scenarios you'd like to see in the + # workbench while developing your XBlock. + @staticmethod + def workbench_scenarios(): + """A canned scenario for display in the workbench.""" + return [ + ( + "AudioXBlock", + """ + + + + + """, + ), + ] diff --git a/src/audio/conf/locale/config.yaml b/src/audio/conf/locale/config.yaml new file mode 100644 index 0000000..4c2a7d7 --- /dev/null +++ b/src/audio/conf/locale/config.yaml @@ -0,0 +1,4 @@ +# Configuration for i18n workflow. + +locales: + - en # English - Source Language diff --git a/src/audio/static/css/audio.css b/src/audio/static/css/audio.css new file mode 100644 index 0000000..960256d --- /dev/null +++ b/src/audio/static/css/audio.css @@ -0,0 +1,9 @@ +/* CSS for AudioXBlock */ + +.audio_block .count { + font-weight: bold; +} + +.audio_block p { + cursor: pointer; +} diff --git a/src/audio/static/js/src/audio_edit.js b/src/audio/static/js/src/audio_edit.js new file mode 100644 index 0000000..e4eff18 --- /dev/null +++ b/src/audio/static/js/src/audio_edit.js @@ -0,0 +1,16 @@ +function AudioEditBlock(runtime, element) { + $(element).find('.save-button').bind('click', function () { + var handlerUrl = runtime.handlerUrl(element, 'studio_submit'); + var data = { + src: $(element).find('input[name=audio_src]').val() + }; + $.post(handlerUrl, JSON.stringify(data)).done(function (response) { + runtime.notify('save', { state: 'end' }) + window.location.reload(false); + }); + }); + + $(element).find('.cancel-button').bind('click', function () { + runtime.notify('cancel', {}); + }); +} diff --git a/src/audio/templates/html/audio.html b/src/audio/templates/html/audio.html new file mode 100644 index 0000000..009c6a1 --- /dev/null +++ b/src/audio/templates/html/audio.html @@ -0,0 +1,7 @@ + + + + + + + diff --git a/src/audio/templates/html/audio_edit.html b/src/audio/templates/html/audio_edit.html new file mode 100644 index 0000000..088c4bc --- /dev/null +++ b/src/audio/templates/html/audio_edit.html @@ -0,0 +1,21 @@ +{% load i18n %} + + + + + + {% trans "Audio Source Location" %} + + + + + + {% trans "Save" %} + {% trans "Cancel" %} + + diff --git a/src/audio/translations b/src/audio/translations new file mode 120000 index 0000000..618b7e2 --- /dev/null +++ b/src/audio/translations @@ -0,0 +1 @@ +conf/locale \ No newline at end of file