Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions docs/frontend_architecture/single_page_apps.rst
Original file line number Diff line number Diff line change
Expand Up @@ -92,13 +92,13 @@ A special kind of Kolibri Module is dedicated to rendering particular content ty
:members:
:noindex:

The ``ContentViewer`` class has one required property ``viewerComponent`` which should return a Vue component that wraps the content rendering code. This component will be passed ``files``, ``file``, ``itemData``, ``preset``, ``itemId``, ``answerState``, ``allowHints``, ``extraFields``, ``interactive``, ``lang``, ``showCorrectAnswer``, ``defaultItemPreset``, ``availableFiles``, ``defaultFile``, ``supplementaryFiles``, ``thumbnailFiles``, ``contentDirection``, and ``contentIsRtl`` props, defining the files associated with the piece of content, and other required data for rendering.
The ``ContentViewer`` class has one required property ``viewerComponent`` which should return a Vue component that wraps the content rendering code. The ``ContentViewer`` wrapper accepts a ``contentNode`` prop containing the content metadata (including ``files``, ``lang``, ``options``, and ``duration``), and provides all necessary data to descendant viewer components via the ``useContentViewer`` composable.

The component should use the `useContentViewer` composable and the `contentViewerProps`, importable as follows:
The component should use the `useContentViewer` composable, importable as follows:

.. code-block:: javascript

import useContentViewer, { contentViewerProps } from 'kolibri/composables/useContentViewer';
import useContentViewer from 'kolibri/composables/useContentViewer';

In order to log data about users viewing content, the component should emit ``startTracking``, ``updateProgress``, and ``stopTracking`` events, using the Vue ``$emit`` method. ``startTracking`` and ``stopTracking`` are emitted without any arguments, whereas ``updateProgress`` should be emitted with a single value between 0 and 1 representing the current proportion of progress on the content.

Expand Down
32 changes: 31 additions & 1 deletion kolibri/core/content/hooks.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@

from django.core.serializers.json import DjangoJSONEncoder
from django.utils.safestring import mark_safe
from le_utils.constants import file_formats
from le_utils.constants import format_presets

from kolibri.core.webpack.hooks import WebpackBundleHook
from kolibri.core.webpack.hooks import WebpackInclusionMixin
Expand All @@ -29,6 +31,30 @@ class ContentRendererHook(WebpackBundleHook, WebpackInclusionMixin):
def presets(self):
pass

#: Optional tuple of CSS selectors that this content renderer can handle
css_selectors = ()

#: Whether to allow object tag handling (defaults to False for sandboxing compatibility)
allow_object_tag = False

@classmethod
def all_css_selectors(cls):
"""Get all CSS selectors (auto-generated from presets + custom), cached."""
if not hasattr(cls, "_cached_css_selectors"):
selectors = list(cls.css_selectors)

if cls.allow_object_tag:
for preset in cls.presets:
preset_obj = next(
x for x in format_presets.PRESETLIST if x.id == preset
)
for fmt in preset_obj.allowed_formats:
fmt_obj = file_formats.getformat(fmt)
selectors.append(f'object[type="{fmt_obj.mimetype}"]')

cls._cached_css_selectors = tuple(sorted(set(selectors)))
return cls._cached_css_selectors

@classmethod
def html(cls):
tags = []
Expand All @@ -53,7 +79,11 @@ def template_html(self):
'<template data-viewer="{bundle}">{data}</template>'.format(
bundle=self.unique_id,
data=json.dumps(
{"urls": urls, "presets": self.presets},
{
"urls": urls,
"presets": self.presets,
"css_selectors": self.all_css_selectors(),
},
separators=(",", ":"),
ensure_ascii=False,
cls=DjangoJSONEncoder,
Expand Down
3 changes: 2 additions & 1 deletion kolibri/core/content/zip_wsgi.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
from le_utils.constants.file_formats import BLOOMPUB
from le_utils.constants.file_formats import H5P
from le_utils.constants.file_formats import HTML5
from le_utils.constants.file_formats import HTML5_ARTICLE
from le_utils.constants.file_formats import PERSEUS
from whitenoise.responders import StaticFile

Expand Down Expand Up @@ -271,7 +272,7 @@ def get_embedded_file(
return response


archive_file_types = (HTML5, H5P, BLOOMPUB, BLOOMD, PERSEUS)
archive_file_types = (HTML5, H5P, BLOOMPUB, BLOOMD, PERSEUS, HTML5_ARTICLE)
archive_file_extension_match = "|".join(archive_file_types)

# Allows a base url to be passed in the main
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,28 +6,11 @@
:style="{ width: iframeWidth }"
@changeFullscreen="isInFullscreen = $event"
>
<div
class="fullscreen-header"
:style="{ backgroundColor: $themePalette.grey.v_200 }"
>
<KButton
:primary="false"
appearance="flat-button"
@click="$refs.bloompubViewer.toggleFullscreen()"
>
<KIcon
v-if="isInFullscreen"
icon="fullscreen_exit"
class="fs-icon"
/>
<KIcon
v-else
icon="fullscreen"
class="fs-icon"
/>
{{ fullscreenText }}
</KButton>
</div>
<ViewerToolbar
:isInFullscreen="isInFullscreen"
:embedded="embedded"
@toggleFullscreen="$refs.bloompubViewer.toggleFullscreen()"
/>
<div
class="iframe-container"
:style="containerStyle"
Expand Down Expand Up @@ -58,29 +41,47 @@
import urls from 'kolibri/urls';
import { now } from 'kolibri/utils/serverClock';
import CoreFullscreen from 'kolibri-common/components/CoreFullscreen';
import ViewerToolbar from 'kolibri-common/components/ViewerToolbar';
import Sandbox from 'kolibri-sandbox';
import useContentViewer, { contentViewerProps } from 'kolibri/composables/useContentViewer';

import useContentViewer from 'kolibri/composables/useContentViewer';

const defaultContentHeight = '500px';
const frameTopbarHeight = '37px';
const frameTopbarHeight = '48px';
export default {
name: 'BloomPubRendererIndex',
components: {
CoreFullscreen,
ViewerToolbar,
},
setup(props, context) {
const { defaultFile, forceDurationBasedProgress, reportError } = useContentViewer(
props,
context,
{ defaultDuration: 300 },
);
const {
defaultFile,
options,
lang,
forceDurationBasedProgress,
reportError,
embedded,
extraFields,
timeSpent,
userId,
userFullName,
progress,
} = useContentViewer(context, { defaultDuration: 300 });
return {
defaultFile,
options,
lang,
forceDurationBasedProgress,
reportError,
embedded,
extraFields,
timeSpent,
userId,
userFullName,
progress,
};
},
props: contentViewerProps,
data() {
return {
iframeHeight: (this.options && this.options.height) || defaultContentHeight,
Expand All @@ -95,9 +96,6 @@
iframeWidth() {
return (this.options && this.options.width) || 'auto';
},
fullscreenText() {
return this.isInFullscreen ? this.$tr('exitFullscreen') : this.$tr('enterFullscreen');
},
userData() {
return {
userId: this.userId,
Expand Down Expand Up @@ -162,18 +160,6 @@
}
this.$emit('stopTracking');
},
$trs: {
exitFullscreen: {
message: 'Exit fullscreen',
context:
"Learners can use the Esc key or the 'exit fullscreen' button to close the fullscreen view on an html5 app.",
},
enterFullscreen: {
message: 'Enter fullscreen',
context:
'Learners can use the full screen button in the upper right corner to open an html5 app in fullscreen view.\n',
},
},
};

</script>
Expand All @@ -182,18 +168,7 @@
<style lang="scss" scoped>

@import '~kolibri-design-system/lib/styles/definitions';
$frame-topbar-height: 37px;

.fullscreen-header {
text-align: right;
}

.fs-icon {
position: relative;
top: 8px;
width: 24px;
height: 24px;
}
$frame-topbar-height: 48px;

.bloompub-viewer {
position: relative;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,10 +99,8 @@
<ContentViewer
v-if="questionContentExists(question)"
:ref="`contentRenderer-${question.item}`"
:lang="getQuestionContent(question).lang"
:files="getQuestionContent(question).files"
:contentNode="getQuestionContent(question)"
:itemId="question.question_id"
:assessment="true"
:allowHints="false"
:interactive="false"
:showCorrectAnswer="true"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,14 +59,12 @@
/>
<ContentViewer
v-if="currentInteraction"
:contentNode="exercise"
:itemId="currentLearner.item"
:assessment="true"
:allowHints="false"
:files="exercise.files"
:answerState="answerState"
:showCorrectAnswer="showCorrectAnswer"
:interactive="false"
:extraFields="exercise.extra_fields"
/>
</div>
</template>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,10 @@
<ContentViewer
v-if="content.available"
class="content-viewer"
:contentNode="content"
:showCorrectAnswer="true"
:itemId="selectedQuestion"
:allowHints="false"
:files="content.files"
:extraFields="content.extra_fields"
:interactive="false"
/>
<MissingResourceAlert
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -122,10 +122,8 @@
<ContentViewer
v-if="content && content.available && currentQuestion.question_id"
ref="contentViewer"
:files="content.files"
:extraFields="content.extra_fields"
:contentNode="content"
:itemId="currentQuestion.question_id"
:assessment="true"
:allowHints="false"
:showCorrectAnswer="true"
:interactive="false"
Expand Down
Loading
Loading