Skip to content

Commit

Permalink
Merge pull request #12586 from nikkuAg/bloom-player
Browse files Browse the repository at this point in the history
Bloom player
  • Loading branch information
rtibbles authored Oct 18, 2024
2 parents 1f24cc8 + 6d45b10 commit 39f41b8
Show file tree
Hide file tree
Showing 24 changed files with 778 additions and 5 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -97,8 +97,9 @@ js-dist
storage/*
kolibri/content/content_db/*.sqlite3
kolibri/core/content/contentschema/migrations/*
# Check in h5p specific files
# Check in h5p & bloom specific files
!kolibri/core/content/static/h5p/
!kolibri/core/content/static/bloom/

# virtual environment
venv/
Expand Down
1 change: 1 addition & 0 deletions kolibri/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,4 +39,5 @@
"kolibri.plugins.user_auth",
"kolibri.plugins.user_profile",
"kolibri.plugins.policies",
"kolibri.plugins.bloompub_viewer",
]
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ const filePresetStrings = {
zim: 'ZIM Document ({fileSize})',
slideshow_manifest: 'Slideshow ({fileSize})',
slideshow_image: 'Slideshow image ({fileSize})',
bloompub: 'Bloom Pub Document ({fileSize})',
};

const filePresetTranslator = createTranslator('FilePresetStrings', filePresetStrings);
Expand Down
42 changes: 42 additions & 0 deletions kolibri/core/content/migrations/0038_alter_localfile_extension.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# Generated by Django 3.2.25 on 2024-09-25 09:03
from django.db import migrations
from django.db import models


class Migration(migrations.Migration):

dependencies = [
("content", "0037_add_bloompub_preset"),
]

operations = [
migrations.AlterField(
model_name="localfile",
name="extension",
field=models.CharField(
blank=True,
choices=[
("mp4", "MP4 Video"),
("webm", "WEBM Video"),
("vtt", "VTT Subtitle"),
("mp3", "MP3 Audio"),
("pdf", "PDF Document"),
("jpg", "JPG Image"),
("jpeg", "JPEG Image"),
("png", "PNG Image"),
("gif", "GIF Image"),
("json", "JSON"),
("svg", "SVG Image"),
("perseus", "Perseus Exercise"),
("graphie", "Graphie Exercise"),
("zip", "HTML5 Zip"),
("h5p", "H5P"),
("zim", "ZIM"),
("epub", "ePub Document"),
("bloompub", "Bloom Document"),
("bloomd", "Bloom Document"),
],
max_length=40,
),
),
]

Large diffs are not rendered by default.

13 changes: 13 additions & 0 deletions kolibri/core/content/static/bloom/bloomplayer.htm
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<!doctype html>
<html>
<head>
<meta charset="UTF-8" />
</head>
<body style="background-color: #2e2e2e">
<div id="root"><span style="color: #d65649">Loading Bloom Player...</span></div>
<script
type="text/javascript"
src="bloomPlayer-d1be6dab31acb4a956a6.min.js"
></script>
</body>
</html>
Binary file not shown.
Binary file not shown.
Empty file.
12 changes: 12 additions & 0 deletions kolibri/plugins/bloompub_viewer/assets/src/module.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import BloomPubComponent from './views/BloomPubRendererIndex.vue';
import ContentRendererModule from 'content_renderer_module';

class BloomPubModule extends ContentRendererModule {
get rendererComponent() {
return BloomPubComponent;
}
}

const bloomPubModule = new BloomPubModule();

export { bloomPubModule as default };
Original file line number Diff line number Diff line change
@@ -0,0 +1,275 @@
<template>

<CoreFullscreen
ref="bloompubRenderer"
class="bloompub-renderer"
:style="{ width: iframeWidth }"
@changeFullscreen="isInFullscreen = $event"
>
<div
class="fullscreen-header"
:style="{ backgroundColor: $themePalette.grey.v_100 }"
>
<KButton
:primary="false"
appearance="flat-button"
@click="$refs.bloompubRenderer.toggleFullscreen()"
>
<KIcon
v-if="isInFullscreen"
icon="fullscreen_exit"
class="fs-icon"
/>
<KIcon
v-else
icon="fullscreen"
class="fs-icon"
/>
{{ fullscreenText }}
</KButton>
</div>
<div
class="iframe-container"
:style="containerStyle"
>
<iframe
ref="iframe"
class="iframe"
sandbox="allow-scripts allow-same-origin"
:style="{ backgroundColor: $themePalette.grey.v_100 }"
frameBorder="0"
:src="rooturl"
allow="fullscreen"
>
</iframe>
<KCircularLoader
v-if="loading"
:delay="false"
class="loader"
/>
</div>
</CoreFullscreen>

</template>


<script>
import urls from 'kolibri.urls';
import { now } from 'kolibri.utils.serverClock';
import CoreFullscreen from 'kolibri.coreVue.components.CoreFullscreen';
import Hashi from 'hashi';
const defaultContentHeight = '500px';
const frameTopbarHeight = '37px';
export default {
name: 'BloomPubRendererIndex',
components: {
CoreFullscreen,
},
props: {
userId: {
type: String,
default: '',
},
userFullName: {
type: String,
default: '',
},
progress: {
type: Number,
default: 0,
},
},
data() {
return {
iframeHeight: (this.options && this.options.height) || defaultContentHeight,
isInFullscreen: false,
loading: false,
};
},
computed: {
rooturl() {
return urls.hashi();
},
iframeWidth() {
return (this.options && this.options.width) || 'auto';
},
fullscreenText() {
return this.isInFullscreen ? this.$tr('exitFullscreen') : this.$tr('enterFullscreen');
},
userData() {
return {
userId: this.userId,
userFullName: this.userFullName,
progress: this.progress,
complete: this.progress >= 1,
language: this.lang.id,
timeSpent: this.timeSpent,
};
},
containerStyle() {
if (this.isInFullscreen) {
return {
position: 'absolute',
top: frameTopbarHeight,
bottom: 0,
};
}
return {};
},
/* eslint-disable kolibri/vue-no-unused-properties */
/**
* @public
* Note: the default duration historically for HTML5 Apps has been 5 min
*/
defaultDuration() {
return 300;
},
/* eslint-enable kolibri/vue-no-unused-properties */
entry() {
return (this.options && this.options.entry) || 'index.htm';
},
isBloom() {
return this.defaultFile.extension === 'bloompub';
},
},
watch: {
userData(newValue) {
if (newValue && this.hashi) {
this.hashi.updateData({ userData: newValue });
}
},
},
mounted() {
this.hashi = new Hashi({ iframe: this.$refs.iframe, now });
this.hashi.onUserDataUpdate(data => {
const hashiProgress = data.progress;
if (hashiProgress !== null && !this.forceDurationBasedProgress) {
this.$emit('updateProgress', hashiProgress);
}
});
this.hashi.on('navigateTo', message => {
this.$emit('navigateTo', message);
});
this.hashi.on(this.hashi.events.RESIZE, scrollHeight => {
this.iframeHeight = scrollHeight;
});
this.hashi.on(this.hashi.events.LOADING, loading => {
this.loading = loading;
});
this.hashi.on(this.hashi.events.ERROR, err => {
this.loading = false;
this.$emit('error', err);
});
let storageUrl = this.defaultFile.storage_url;
if (!this.isBloom) {
// In the case that this is being routed via a remote URL
// ensure we preserve that for the zip endpoint.
const url = new URL(this.defaultFile.storage_url, window.location.href);
const baseurl = url.searchParams.get('baseurl');
storageUrl = urls.zipContentUrl(
this.defaultFile.checksum,
this.defaultFile.extension,
this.entry,
baseurl ? encodeURIComponent(baseurl) : undefined,
);
}
this.hashi.initialize(
(this.extraFields && this.extraFields.contentState) || {},
this.userData,
storageUrl,
this.defaultFile.checksum,
);
this.$emit('startTracking');
if (!this.isBloom) {
this.pollProgress();
}
},
beforeDestroy() {
if (this.timeout) {
clearTimeout(this.timeout);
}
this.$emit('stopTracking');
},
methods: {
recordProgress() {
let progress;
if (this.forceDurationBasedProgress) {
progress = this.durationBasedProgress;
} else {
const hashiProgress = this.hashi ? this.hashi.getProgress() : null;
progress = hashiProgress === null ? this.durationBasedProgress : hashiProgress;
}
this.$emit('updateProgress', progress);
if (progress >= 1) {
this.$emit('finished');
}
this.pollProgress();
},
pollProgress() {
this.timeout = setTimeout(() => {
this.recordProgress();
}, 5000);
},
},
$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>


<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;
}
.bloompub-renderer {
position: relative;
text-align: center;
}
.iframe {
width: 100%;
height: 100%;
}
.iframe-container {
@extend %momentum-scroll;
width: 100%;
height: calc(100% - #{$frame-topbar-height});
margin-bottom: -8px;
overflow: hidden;
}
.loader {
position: absolute;
top: calc(50% - 16px);
left: calc(50% - 16px);
}
</style>
6 changes: 6 additions & 0 deletions kolibri/plugins/bloompub_viewer/buildConfig.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
module.exports = {
bundle_id: 'main',
webpack_config: {
entry: './assets/src/module.js',
},
};
15 changes: 15 additions & 0 deletions kolibri/plugins/bloompub_viewer/kolibri_plugin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
from le_utils.constants import format_presets

from kolibri.core.content import hooks as content_hooks
from kolibri.plugins import KolibriPluginBase
from kolibri.plugins.hooks import register_hook


class BloomPubRenderPlugin(KolibriPluginBase):
pass


@register_hook
class BloomPubRenderAsset(content_hooks.ContentRendererHook):
bundle_id = "main"
presets = (format_presets.BLOOMPUB,)
1 change: 1 addition & 0 deletions kolibri/utils/build_config/default_plugins.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,5 @@
"kolibri.plugins.slideshow_viewer",
"kolibri.plugins.user_auth",
"kolibri.plugins.user_profile",
"kolibri.plugins.bloompub_viewer",
]
Loading

0 comments on commit 39f41b8

Please sign in to comment.