From 690544afbf61106e3d9be63299da651c68f218f3 Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Tue, 29 Oct 2019 16:07:22 +0100 Subject: [PATCH] MOBILE-2235 h5p: Show placeholder instead of H5P directly --- .../filter/displayh5p/displayh5p.module.ts | 34 +++++++ .../filter/displayh5p/providers/handler.ts | 93 +++++++++++++++++++ src/addon/filter/filter.module.ts | 2 + .../filter/mathjaxloader/providers/handler.ts | 7 +- .../filter/mediaplugin/providers/handler.ts | 9 +- src/app/app.module.ts | 2 + src/assets/img/icons/h5p.svg | 14 +++ src/assets/lang/en.json | 1 + src/core/filter/providers/default-filter.ts | 7 +- src/core/filter/providers/delegate.ts | 14 +-- src/core/h5p/components/components.module.ts | 43 +++++++++ .../h5p-player/core-h5p-player.html | 9 ++ .../h5p/components/h5p-player/h5p-player.scss | 24 +++++ .../h5p/components/h5p-player/h5p-player.ts | 56 +++++++++++ src/core/h5p/h5p.module.ts | 27 ++++++ src/core/h5p/lang/en.json | 3 + src/directives/format-text.ts | 10 +- src/providers/utils/text.ts | 5 +- src/theme/variables.scss | 3 + 19 files changed, 341 insertions(+), 22 deletions(-) create mode 100644 src/addon/filter/displayh5p/displayh5p.module.ts create mode 100644 src/addon/filter/displayh5p/providers/handler.ts create mode 100644 src/assets/img/icons/h5p.svg create mode 100644 src/core/h5p/components/components.module.ts create mode 100644 src/core/h5p/components/h5p-player/core-h5p-player.html create mode 100644 src/core/h5p/components/h5p-player/h5p-player.scss create mode 100644 src/core/h5p/components/h5p-player/h5p-player.ts create mode 100644 src/core/h5p/h5p.module.ts create mode 100644 src/core/h5p/lang/en.json diff --git a/src/addon/filter/displayh5p/displayh5p.module.ts b/src/addon/filter/displayh5p/displayh5p.module.ts new file mode 100644 index 000000000..8a7197c71 --- /dev/null +++ b/src/addon/filter/displayh5p/displayh5p.module.ts @@ -0,0 +1,34 @@ +// (C) Copyright 2015 Moodle Pty Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { NgModule } from '@angular/core'; +import { IonicModule } from 'ionic-angular'; +import { CoreFilterDelegate } from '@core/filter/providers/delegate'; +import { AddonFilterDisplayH5PHandler } from './providers/handler'; + +@NgModule({ + declarations: [ + ], + imports: [ + IonicModule + ], + providers: [ + AddonFilterDisplayH5PHandler + ] +}) +export class AddonFilterDisplayH5PModule { + constructor(filterDelegate: CoreFilterDelegate, handler: AddonFilterDisplayH5PHandler) { + filterDelegate.registerHandler(handler); + } +} diff --git a/src/addon/filter/displayh5p/providers/handler.ts b/src/addon/filter/displayh5p/providers/handler.ts new file mode 100644 index 000000000..3956d3299 --- /dev/null +++ b/src/addon/filter/displayh5p/providers/handler.ts @@ -0,0 +1,93 @@ + +// (C) Copyright 2015 Moodle Pty Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { Injectable, ViewContainerRef, ComponentFactoryResolver } from '@angular/core'; +import { CoreFilterDefaultHandler } from '@core/filter/providers/default-filter'; +import { CoreFilterFilter, CoreFilterFormatTextOptions } from '@core/filter/providers/filter'; +import { CoreH5PPlayerComponent } from '@core/h5p/components/h5p-player/h5p-player'; + +/** + * Handler to support the Display H5P filter. + */ +@Injectable() +export class AddonFilterDisplayH5PHandler extends CoreFilterDefaultHandler { + name = 'AddonFilterDisplayH5PHandler'; + filterName = 'displayh5p'; + + protected template = document.createElement('template'); // A template element to convert HTML to element. + + constructor(protected factoryResolver: ComponentFactoryResolver) { + super(); + } + + /** + * Filter some text. + * + * @param text The text to filter. + * @param filter The filter. + * @param options Options passed to the filters. + * @param siteId Site ID. If not defined, current site. + * @return Filtered text (or promise resolved with the filtered text). + */ + filter(text: string, filter: CoreFilterFilter, options: CoreFilterFormatTextOptions, siteId?: string) + : string | Promise { + + this.template.innerHTML = text; + + const h5pIframes = Array.from(this.template.content.querySelectorAll('iframe.h5p-iframe')); + + // Replace all iframes with an empty div that will be treated in handleHtml. + h5pIframes.forEach((iframe) => { + const placeholder = document.createElement('div'); + + placeholder.classList.add('core-h5p-tmp-placeholder'); + placeholder.setAttribute('data-player-src', iframe.src); + + iframe.parentElement.replaceChild(placeholder, iframe); + }); + + return this.template.innerHTML; + } + + /** + * Handle HTML. This function is called after "filter", and it will receive an HTMLElement containing the text that was + * filtered. + * + * @param container The HTML container to handle. + * @param filter The filter. + * @param options Options passed to the filters. + * @param viewContainerRef The ViewContainerRef where the container is. + * @param siteId Site ID. If not defined, current site. + * @return If async, promise resolved when done. + */ + handleHtml(container: HTMLElement, filter: CoreFilterFilter, options: CoreFilterFormatTextOptions, + viewContainerRef: ViewContainerRef, siteId?: string): void | Promise { + + const placeholders = Array.from(container.querySelectorAll('div.core-h5p-tmp-placeholder')); + + placeholders.forEach((placeholder) => { + const url = placeholder.getAttribute('data-player-src'); + + // Create the component to display the player. + const factory = this.factoryResolver.resolveComponentFactory(CoreH5PPlayerComponent), + componentRef = viewContainerRef.createComponent(factory); + + componentRef.instance.src = url; + + // Move the component to its right position. + placeholder.parentElement.replaceChild(componentRef.instance.elementRef.nativeElement, placeholder); + }); + } +} diff --git a/src/addon/filter/filter.module.ts b/src/addon/filter/filter.module.ts index 57e177598..29ab01243 100644 --- a/src/addon/filter/filter.module.ts +++ b/src/addon/filter/filter.module.ts @@ -17,6 +17,7 @@ import { AddonFilterActivityNamesModule } from './activitynames/activitynames.mo import { AddonFilterAlgebraModule } from './algebra/algebra.module'; import { AddonFilterCensorModule } from './censor/censor.module'; import { AddonFilterDataModule } from './data/data.module'; +import { AddonFilterDisplayH5PModule } from './displayh5p/displayh5p.module'; import { AddonFilterEmailProtectModule } from './emailprotect/emailprotect.module'; import { AddonFilterEmoticonModule } from './emoticon/emoticon.module'; import { AddonFilterGlossaryModule } from './glossary/glossary.module'; @@ -34,6 +35,7 @@ import { AddonFilterUrlToLinkModule } from './urltolink/urltolink.module'; AddonFilterAlgebraModule, AddonFilterCensorModule, AddonFilterDataModule, + AddonFilterDisplayH5PModule, AddonFilterEmailProtectModule, AddonFilterEmoticonModule, AddonFilterGlossaryModule, diff --git a/src/addon/filter/mathjaxloader/providers/handler.ts b/src/addon/filter/mathjaxloader/providers/handler.ts index 51c9d8d0c..e1d514e05 100644 --- a/src/addon/filter/mathjaxloader/providers/handler.ts +++ b/src/addon/filter/mathjaxloader/providers/handler.ts @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { Injectable } from '@angular/core'; +import { Injectable, ViewContainerRef } from '@angular/core'; import { CoreFilterDefaultHandler } from '@core/filter/providers/default-filter'; import { CoreFilterFilter, CoreFilterFormatTextOptions } from '@core/filter/providers/filter'; import { CoreEventsProvider } from '@providers/events'; @@ -161,11 +161,12 @@ export class AddonFilterMathJaxLoaderHandler extends CoreFilterDefaultHandler { * @param container The HTML container to handle. * @param filter The filter. * @param options Options passed to the filters. + * @param viewContainerRef The ViewContainerRef where the container is. * @param siteId Site ID. If not defined, current site. * @return If async, promise resolved when done. */ - handleHtml(container: HTMLElement, filter: CoreFilterFilter, options: CoreFilterFormatTextOptions, siteId?: string) - : void | Promise { + handleHtml(container: HTMLElement, filter: CoreFilterFilter, options: CoreFilterFormatTextOptions, + viewContainerRef: ViewContainerRef, siteId?: string): void | Promise { return this.waitForReady().then(() => { this.window.M.filter_mathjaxloader.typeset(container); diff --git a/src/addon/filter/mediaplugin/providers/handler.ts b/src/addon/filter/mediaplugin/providers/handler.ts index bc8b5ce03..e1c5a93fc 100644 --- a/src/addon/filter/mediaplugin/providers/handler.ts +++ b/src/addon/filter/mediaplugin/providers/handler.ts @@ -27,6 +27,8 @@ export class AddonFilterMediaPluginHandler extends CoreFilterDefaultHandler { name = 'AddonFilterMediaPluginHandler'; filterName = 'mediaplugin'; + protected template = document.createElement('template'); // A template element to convert HTML to element. + constructor(private textUtils: CoreTextUtilsProvider, private urlUtils: CoreUrlUtilsProvider) { super(); @@ -44,16 +46,15 @@ export class AddonFilterMediaPluginHandler extends CoreFilterDefaultHandler { filter(text: string, filter: CoreFilterFilter, options: CoreFilterFormatTextOptions, siteId?: string) : string | Promise { - const div = document.createElement('div'); - div.innerHTML = text; + this.template.innerHTML = text; - const videos = Array.from(div.querySelectorAll('video')); + const videos = Array.from(this.template.content.querySelectorAll('video')); videos.forEach((video) => { this.treatVideoFilters(video); }); - return div.innerHTML; + return this.template.innerHTML; } /** diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 6f9966fa8..f28974676 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -83,6 +83,7 @@ import { CoreBlockModule } from '@core/block/block.module'; import { CoreRatingModule } from '@core/rating/rating.module'; import { CoreTagModule } from '@core/tag/tag.module'; import { CoreFilterModule } from '@core/filter/filter.module'; +import { CoreH5PModule } from '@core/h5p/h5p.module'; // Addon modules. import { AddonBadgesModule } from '@addon/badges/badges.module'; @@ -230,6 +231,7 @@ export const WP_PROVIDER: any = null; CorePushNotificationsModule, CoreTagModule, CoreFilterModule, + CoreH5PModule, AddonBadgesModule, AddonBlogModule, AddonCalendarModule, diff --git a/src/assets/img/icons/h5p.svg b/src/assets/img/icons/h5p.svg new file mode 100644 index 000000000..7856f9efb --- /dev/null +++ b/src/assets/img/icons/h5p.svg @@ -0,0 +1,14 @@ + + + + + + + + diff --git a/src/assets/lang/en.json b/src/assets/lang/en.json index 6879a9d11..e3d599345 100644 --- a/src/assets/lang/en.json +++ b/src/assets/lang/en.json @@ -1550,6 +1550,7 @@ "core.group": "Group", "core.groupsseparate": "Separate groups", "core.groupsvisible": "Visible groups", + "core.h5p.play": "Play H5P", "core.hasdatatosync": "This {{$a}} has offline data to be synchronised.", "core.help": "Help", "core.hide": "Hide", diff --git a/src/core/filter/providers/default-filter.ts b/src/core/filter/providers/default-filter.ts index bb9ff9d32..8b86c3028 100644 --- a/src/core/filter/providers/default-filter.ts +++ b/src/core/filter/providers/default-filter.ts @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { Injectable } from '@angular/core'; +import { Injectable, ViewContainerRef } from '@angular/core'; import { CoreFilterHandler } from './delegate'; import { CoreFilterFilter, CoreFilterFormatTextOptions } from './filter'; import { CoreSite } from '@classes/site'; @@ -50,11 +50,12 @@ export class CoreFilterDefaultHandler implements CoreFilterHandler { * @param container The HTML container to handle. * @param filter The filter. * @param options Options passed to the filters. + * @param viewContainerRef The ViewContainerRef where the container is. * @param siteId Site ID. If not defined, current site. * @return If async, promise resolved when done. */ - handleHtml(container: HTMLElement, filter: CoreFilterFilter, options: CoreFilterFormatTextOptions, siteId?: string) - : void | Promise { + handleHtml(container: HTMLElement, filter: CoreFilterFilter, options: CoreFilterFormatTextOptions, + viewContainerRef: ViewContainerRef, siteId?: string): void | Promise { // To be overridden. } diff --git a/src/core/filter/providers/delegate.ts b/src/core/filter/providers/delegate.ts index ef1e5852a..9b6184536 100644 --- a/src/core/filter/providers/delegate.ts +++ b/src/core/filter/providers/delegate.ts @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { Injectable } from '@angular/core'; +import { Injectable, ViewContainerRef } from '@angular/core'; import { CoreEventsProvider } from '@providers/events'; import { CoreLoggerProvider } from '@providers/logger'; import { CoreSitesProvider } from '@providers/sites'; @@ -48,11 +48,12 @@ export interface CoreFilterHandler extends CoreDelegateHandler { * @param container The HTML container to handle. * @param filter The filter. * @param options Options passed to the filters. + * @param viewContainerRef The ViewContainerRef where the container is. * @param siteId Site ID. If not defined, current site. * @return If async, promise resolved when done. */ - handleHtml?(container: HTMLElement, filter: CoreFilterFilter, options: CoreFilterFormatTextOptions, siteId?: string) - : void | Promise; + handleHtml?(container: HTMLElement, filter: CoreFilterFilter, options: CoreFilterFormatTextOptions, + viewContainerRef: ViewContainerRef, siteId?: string): void | Promise; /** * Check if the filter should be applied in a certain site based on some filter options. @@ -156,13 +157,14 @@ export class CoreFilterDelegate extends CoreDelegate { * * @param container The HTML container to handle. * @param filters Filters to apply. + * @param viewContainerRef The ViewContainerRef where the container is. * @param options Options passed to the filters. * @param skipFilters Names of filters that shouldn't be applied. * @param siteId Site ID. If not defined, current site. * @return Promise resolved when done. */ - handleHtml(container: HTMLElement, filters: CoreFilterFilter[], options?: any, skipFilters?: string[], siteId?: string) - : Promise { + handleHtml(container: HTMLElement, filters: CoreFilterFilter[], viewContainerRef?: ViewContainerRef, options?: any, + skipFilters?: string[], siteId?: string): Promise { // Wait for filters to be initialized. return this.handlersInitPromise.then(() => { @@ -182,7 +184,7 @@ export class CoreFilterDelegate extends CoreDelegate { promise = promise.then(() => { return Promise.resolve(this.executeFunctionOnEnabled(filter.filter, 'handleHtml', - [container, filter, options, siteId])).catch((error) => { + [container, filter, options, viewContainerRef, siteId])).catch((error) => { this.logger.error('Error handling HTML' + filter.filter, error); }); }); diff --git a/src/core/h5p/components/components.module.ts b/src/core/h5p/components/components.module.ts new file mode 100644 index 000000000..09480a6bb --- /dev/null +++ b/src/core/h5p/components/components.module.ts @@ -0,0 +1,43 @@ +// (C) Copyright 2015 Moodle Pty Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { IonicModule } from 'ionic-angular'; +import { TranslateModule } from '@ngx-translate/core'; +import { CoreDirectivesModule } from '@directives/directives.module'; +import { CoreH5PPlayerComponent } from './h5p-player/h5p-player'; +import { CoreComponentsModule } from '@components/components.module'; + +@NgModule({ + declarations: [ + CoreH5PPlayerComponent + ], + imports: [ + CommonModule, + IonicModule, + CoreDirectivesModule, + TranslateModule.forChild(), + CoreComponentsModule + ], + providers: [ + ], + exports: [ + CoreH5PPlayerComponent + ], + entryComponents: [ + CoreH5PPlayerComponent + ] +}) +export class CoreH5PComponentsModule {} diff --git a/src/core/h5p/components/h5p-player/core-h5p-player.html b/src/core/h5p/components/h5p-player/core-h5p-player.html new file mode 100644 index 000000000..3d9e67912 --- /dev/null +++ b/src/core/h5p/components/h5p-player/core-h5p-player.html @@ -0,0 +1,9 @@ +
+ + + + +
+ diff --git a/src/core/h5p/components/h5p-player/h5p-player.scss b/src/core/h5p/components/h5p-player/h5p-player.scss new file mode 100644 index 000000000..0c963aa79 --- /dev/null +++ b/src/core/h5p/components/h5p-player/h5p-player.scss @@ -0,0 +1,24 @@ +ion-app.app-root core-h5p-player { + .core-h5p-placeholder { + position: relative; + width: 100%; + height: 230px; + background: url('../assets/img/icons/h5p.svg') center top 25px / 100px auto no-repeat $core-h5p-placeholder-bg-color; + + .core-h5p-placeholder-play-button { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + font-size: 30px; + min-height: 50px; + } + + .core-h5p-placeholder-download-container { + position: absolute; + right: 10px; + font-size: 1.8em; + } + } + +} diff --git a/src/core/h5p/components/h5p-player/h5p-player.ts b/src/core/h5p/components/h5p-player/h5p-player.ts new file mode 100644 index 000000000..e6a73b2a4 --- /dev/null +++ b/src/core/h5p/components/h5p-player/h5p-player.ts @@ -0,0 +1,56 @@ +// (C) Copyright 2015 Moodle Pty Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { Component, Input, ElementRef } from '@angular/core'; +import { CoreSitesProvider } from '@providers/sites'; + +/** + * Component to render an H5P package. + */ +@Component({ + selector: 'core-h5p-player', + templateUrl: 'core-h5p-player.html' +}) +export class CoreH5PPlayerComponent { + @Input() src: string; // The URL of the player to display the H5P package. + + showPackage = false; + loading = false; + status: string; + canDownload: boolean; + calculating = true; + + constructor(public elementRef: ElementRef, + protected sitesProvider: CoreSitesProvider) { + } + + /** + * Play the H5P. + * + * @param e Event. + */ + play(e: MouseEvent): void { + e.preventDefault(); + e.stopPropagation(); + + this.loading = true; + + // Get auto-login URL so the user is automatically authenticated. + this.sitesProvider.getCurrentSite().getAutoLoginUrl(this.src, false).then((url) => { + this.src = url; + this.loading = false; + this.showPackage = true; + }); + } +} diff --git a/src/core/h5p/h5p.module.ts b/src/core/h5p/h5p.module.ts new file mode 100644 index 000000000..4fb4f6ba4 --- /dev/null +++ b/src/core/h5p/h5p.module.ts @@ -0,0 +1,27 @@ +// (C) Copyright 2015 Moodle Pty Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { NgModule } from '@angular/core'; +import { CoreH5PComponentsModule } from './components/components.module'; + +@NgModule({ + declarations: [], + imports: [ + CoreH5PComponentsModule + ], + providers: [ + ], + exports: [] +}) +export class CoreH5PModule { } diff --git a/src/core/h5p/lang/en.json b/src/core/h5p/lang/en.json new file mode 100644 index 000000000..954fa438a --- /dev/null +++ b/src/core/h5p/lang/en.json @@ -0,0 +1,3 @@ +{ + "play": "Play H5P" +} \ No newline at end of file diff --git a/src/directives/format-text.ts b/src/directives/format-text.ts index 68ef57895..1ae718161 100644 --- a/src/directives/format-text.ts +++ b/src/directives/format-text.ts @@ -12,7 +12,9 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { Directive, ElementRef, Input, Output, EventEmitter, OnChanges, SimpleChange, Optional } from '@angular/core'; +import { + Directive, ElementRef, Input, Output, EventEmitter, OnChanges, SimpleChange, Optional, ViewContainerRef +} from '@angular/core'; import { Platform, NavController, Content } from 'ionic-angular'; import { TranslateService } from '@ngx-translate/core'; import { CoreAppProvider } from '@providers/app'; @@ -90,7 +92,8 @@ export class CoreFormatTextDirective implements OnChanges { private eventsProvider: CoreEventsProvider, private filterProvider: CoreFilterProvider, private filterHelper: CoreFilterHelperProvider, - private filterDelegate: CoreFilterDelegate) { + private filterDelegate: CoreFilterDelegate, + private viewContainerRef: ViewContainerRef) { this.element = element.nativeElement; this.element.classList.add('opacity-hide'); // Hide contents until they're treated. @@ -371,7 +374,8 @@ export class CoreFormatTextDirective implements OnChanges { if (result.options.filter) { // Let filters hnadle HTML. We do it here because we don't want them to block the render of the text. - this.filterDelegate.handleHtml(this.element, result.filters, result.options, [], result.siteId); + this.filterDelegate.handleHtml(this.element, result.filters, this.viewContainerRef, result.options, [], + result.siteId); } this.element.classList.remove('core-disable-media-adapt'); diff --git a/src/providers/utils/text.ts b/src/providers/utils/text.ts index d08b82fe9..0a858127e 100644 --- a/src/providers/utils/text.ts +++ b/src/providers/utils/text.ts @@ -514,10 +514,9 @@ export class CoreTextUtilsProvider { return true; } - const div = document.createElement('div'); - div.innerHTML = content; + this.template.innerHTML = content; - return div.textContent === '' && div.querySelector('img, object, hr') === null; + return this.template.textContent === '' && this.template.content.querySelector('img, object, hr') === null; } /** diff --git a/src/theme/variables.scss b/src/theme/variables.scss index ebab93935..326cc4671 100644 --- a/src/theme/variables.scss +++ b/src/theme/variables.scss @@ -369,6 +369,9 @@ $core-question-state-incorrect-color: $red-light !default; $core-dd-question-selected-shadow: 2px 2px 4px $gray-dark !default; $core-dd-question-colors: $white, $blue-light, #DCDCDC, #D8BFD8, #87CEFA, #DAA520, #FFD700, #F0E68C !default; +// H5P variables. +$core-h5p-placeholder-bg-color: $gray-dark !default; + // Mixins // ------------------------- @mixin core-transition($where: all, $time: 500ms) {