{"version":3,"file":"app-1792db58.f584a1446b2fc034d810.bundle.js","mappings":"AAGA","sources":["webpack://lernplattform-admin-frontend/../common/aurelia-components/course-block/course-block-audio/course-block-audio.html","webpack://lernplattform-admin-frontend/../common/aurelia-components/course-block/course-block-file/course-block-file.html","webpack://lernplattform-admin-frontend/../common/aurelia-components/course-block/course-block-mp-flipping-card/course-block-mp-flipping-card.html","webpack://lernplattform-admin-frontend/../common/aurelia-components/course-block/course-block-mp-flipping-card/mp-flipping-card-page/mp-flipping-card-page.html","webpack://lernplattform-admin-frontend/../common/aurelia-components/course-block/course-block-mp-hot-spot/course-block-mp-hot-spot.html","webpack://lernplattform-admin-frontend/../common/aurelia-components/course-block/course-block-mp-rfg/course-block-mp-rfg.html","webpack://lernplattform-admin-frontend/../common/aurelia-components/course-block/course-block-mp-risi/course-block-mp-risi.html","webpack://lernplattform-admin-frontend/../common/aurelia-components/course-block/course-block-mp-scorm/course-block-mp-scorm.html","webpack://lernplattform-admin-frontend/../common/aurelia-components/course-block/course-block-mp-sequence/course-block-mp-sequence-sidebar.html","webpack://lernplattform-admin-frontend/../common/aurelia-components/course-block/course-block-mp-sequence/course-block-mp-sequence-slide.html","webpack://lernplattform-admin-frontend/../common/aurelia-components/course-block/course-block-mp-sequence/course-block-mp-sequence.html","webpack://lernplattform-admin-frontend/../common/aurelia-components/course-block/course-block-mp-tile/course-block-mp-tile.html","webpack://lernplattform-admin-frontend/../common/aurelia-components/course-block/course-block-mp/course-block-mp.html","webpack://lernplattform-admin-frontend/../common/aurelia-components/course-block/course-block-picture/course-block-picture.html","webpack://lernplattform-admin-frontend/../common/aurelia-components/course-block/course-block-question/course-block-question.html","webpack://lernplattform-admin-frontend/../common/aurelia-components/course-block/course-block-spacer/course-block-spacer.html","webpack://lernplattform-admin-frontend/../common/aurelia-components/course-block/course-block-text/course-block-text.html","webpack://lernplattform-admin-frontend/../common/aurelia-components/course-block/course-block-video/course-block-video.html","webpack://lernplattform-admin-frontend/../common/aurelia-components/course-block/course-block.html","webpack://lernplattform-admin-frontend/../common/aurelia-components/course-block/course-block-audio/course-block-audio.ts","webpack://lernplattform-admin-frontend/../common/aurelia-components/course-block/course-block-file/course-block-file.ts","webpack://lernplattform-admin-frontend/../common/aurelia-components/course-block/course-block-mp-flipping-card/course-block-mp-flipping-card.ts","webpack://lernplattform-admin-frontend/../common/aurelia-components/course-block/course-block-mp-flipping-card/index.ts","webpack://lernplattform-admin-frontend/../common/aurelia-components/course-block/course-block-mp-flipping-card/mp-flipping-card-page/mp-flipping-card-page.ts","webpack://lernplattform-admin-frontend/../common/aurelia-components/course-block/course-block-mp-hot-spot/course-block-mp-hot-spot.ts","webpack://lernplattform-admin-frontend/../common/aurelia-components/course-block/course-block-mp-rfg/course-block-mp-rfg.ts","webpack://lernplattform-admin-frontend/../common/aurelia-components/course-block/course-block-mp-risi/course-block-mp-risi.ts","webpack://lernplattform-admin-frontend/../common/aurelia-components/course-block/course-block-mp-scorm/course-block-mp-scorm.ts","webpack://lernplattform-admin-frontend/../common/aurelia-components/course-block/course-block-mp-sequence/course-block-mp-sequence-sidebar.ts","webpack://lernplattform-admin-frontend/../common/aurelia-components/course-block/course-block-mp-sequence/course-block-mp-sequence-slide.ts","webpack://lernplattform-admin-frontend/../common/aurelia-components/course-block/course-block-mp-sequence/course-block-mp-sequence.ts","webpack://lernplattform-admin-frontend/../common/aurelia-components/course-block/course-block-mp-tile/course-block-mp-tile.ts","webpack://lernplattform-admin-frontend/../common/aurelia-components/course-block/course-block-mp/course-block-mp.ts","webpack://lernplattform-admin-frontend/../common/aurelia-components/course-block/course-block-picture/course-block-picture.ts","webpack://lernplattform-admin-frontend/../common/aurelia-components/course-block/course-block-question/course-block-question.ts","webpack://lernplattform-admin-frontend/../common/aurelia-components/course-block/course-block-spacer/course-block-spacer.ts","webpack://lernplattform-admin-frontend/../common/aurelia-components/course-block/course-block-text/course-block-text.ts","webpack://lernplattform-admin-frontend/../common/aurelia-components/course-block/course-block-video/course-block-video.ts","webpack://lernplattform-admin-frontend/../common/aurelia-components/course-block/course-block.ts","webpack://lernplattform-admin-frontend/../common/aurelia-components/course-block/index.ts"],"sourcesContent":["// Module\nvar code = \"<template class=\\\"course-block-audio\\\"> <div class=\\\"course-block-audio__content-wrapper ${ compact ? 'course-block-audio__content-wrapper--compact' : '' }\\\"> <audio-player audio-src.to-view=\\\"block.audioUrl\\\" audio-title.to-view=\\\"block.title\\\" compact.to-view=\\\"compact\\\" current-time.to-view=\\\"currentTime\\\" class=\\\"course-block-audio__player\\\" color=\\\"secondary1\\\" transcript.to-view=\\\"block.text\\\" seconds-changed.trigger=\\\"handleSecondsChanged($event)\\\" play-state-changed.trigger=\\\"handlePlayStateChanged($event)\\\"></audio-player> </div> </template>\";\n// Exports\nexport default code;","// Module\nvar code = \"<template class=\\\"course-block-file lp-site-content-small\\\"> <x-icon class=\\\"course-block-file__icon\\\" size=\\\"unspecified\\\" icon-type=\\\"custom\\\" icon-name.to-view=\\\"getFileIcon(block)\\\"></x-icon> <div class=\\\"course-block-file__info-wrapper\\\"> <div class=\\\"course-block-file__title\\\">${block.fileTitle}</div> <div class=\\\"course-block-file__info\\\">${formatFileType(block)}, ${formatFileSize(block)}</div> </div> <button-standard class=\\\"course-block-file__button\\\" click.delegate=\\\"downloadFile()\\\" t=\\\"[btn-text]common.course-block-file.download\\\" btn-type.to-view=\\\"`contained`\\\"></button-standard> </template>\";\n// Exports\nexport default code;","// Module\nvar code = \"<template class=\\\"course-block-mp-flipping-card\\\" style.to-view=\\\"{ '--course-block-mp-flipping-card-animation-duration': ANIMATION_DURATION_IN_SECONDS + 's' }\\\"> <static-block-preview if.to-view=\\\"isNonInteractivePreview\\\" class=\\\"course-block-mp-flipping-card__placeholder\\\" icon-name=\\\"Uce/FlippingCard\\\"></static-block-preview> <template if.to-view=\\\"!isNonInteractivePreview && block.id\\\"> <div class=\\\"course-block-mp-flipping-card__outer-wrapper\\\"> <div class=\\\"course-block-mp-flipping-card__inner-wrapper\\\" click.trigger=\\\"toggleIsFlipped()\\\"> <div class=\\\"course-block-mp-flipping-card__card ${ isFlipped ? 'course-block-mp-flipping-card__card--is-flipped' : '' }\\\"> <mp-flipping-card-page repeat.for=\\\"flippingCardPage of flippingCardPages\\\" class=\\\"course-block-mp-flipping-card__card-page ${ $index === 1 ? 'course-block-mp-flipping-card__card-page--back' : '' } ${ (isFlipped && $index === 1 || !isFlipped && $index === 0) ? 'course-block-mp-flipping-card__card-page--visible' : '' }\\\" animating.to-view=\\\"animating\\\" page.to-view=\\\"flippingCardPage\\\"></mp-flipping-card-page> </div> </div> </div> </template> </template>\";\n// Exports\nexport default code;","// Module\nvar code = \"<template class=\\\"mp-flipping-card-page ${ page.picture && page.text ? 'mp-flipping-card-page--picture-and-text' : '' }\\\"> <div class=\\\"mp-flipping-card-page__outer-wrapper\\\"> <div class=\\\"mp-flipping-card-page__scrollbox\\\"> <div if.to-view=\\\"page.picture\\\" class=\\\"mp-flipping-card-page__inner-wrapper mp-flipping-card-page__inner-wrapper--picture\\\"> <x-picture class=\\\"mp-flipping-card-page__picture\\\" picture.to-view=\\\"page.picture\\\"></x-picture> </div> <div if.to-view=\\\"page.text\\\" class=\\\"mp-flipping-card-page__inner-wrapper\\\"> <rich-text class=\\\"mp-flipping-card-page__text\\\" annotate.to-view=\\\"!animating\\\" text.to-view=\\\"page.text\\\" mode=\\\"content\\\"></rich-text> </div> </div> </div> <button-round class=\\\"mp-flipping-card-page__button\\\" icon-name=\\\"System/Flip\\\" t=\\\"[aria-label]common.mp-flipping-card-page.flip\\\"></button-round> </template>\";\n// Exports\nexport default code;","// Module\nvar code = \"<template class=\\\"course-block-mp-hot-spot ${ isMobile ? 'course-block-mp-hot-spot--mobile-mode' : '' } ${ isMobile && selectedHotSpot ? 'course-block-mp-hot-spot--zoom-mode' : '' }\\\"> <div class=\\\"course-block-mp-hot-spot__content\\\"> <aspect-ratio-container class=\\\"course-block-mp-hot-spot__aspect-ratio-container\\\" vertical-alignment.to-view=\\\"isNonInteractivePreview ? Alignment.CENTER : Alignment.START\\\" center-horizontally.to-view=\\\"true\\\" mode=\\\"width-and-height-dependent\\\"> <div if.to-view=\\\"!isNonInteractivePreview\\\" ref=\\\"hotSpotWrapperDomElement\\\" class=\\\"course-block-mp-hot-spot__wrapper\\\"> <x-picture if.to-view=\\\"block.picture\\\" class=\\\"course-block-mp-hot-spot__picture\\\" picture.to-view=\\\"block.picture\\\"></x-picture> <hot-spot-button repeat.for=\\\"hotSpot of hotSpots\\\" class=\\\"course-block-mp-hot-spot__hot-spot\\\" id=\\\"${hotSpot.id}-${hotSpot.order}\\\" preview.to-view=\\\"isMobile && !selectedHotSpot\\\" click.delegate=\\\"selectHotSpot(hotSpot)\\\" style.to-view=\\\"getHotSpotStyle(hotSpot)\\\" selected.to-view=\\\"selectedHotSpot.id === hotSpot.id\\\" visited.to-view=\\\"getHotSpotVisitedState(hotSpot, visitedHotSpotIds)\\\" order.to-view=\\\"hotSpot.order\\\"></hot-spot-button> </div> <template-preview-block if.to-view=\\\"!isNonInteractivePreview && !block.picture\\\" class=\\\"course-block-mp-hot-spot__picture\\\" block-type.to-view=\\\"BlockType.PICTURE\\\"></template-preview-block> <static-block-preview if.to-view=\\\"isNonInteractivePreview\\\" class=\\\"course-block-mp-hot-spot__placeholder-picture\\\" icon-name=\\\"Uce/HotSpot\\\"></static-block-preview> </aspect-ratio-container> <placeable-dialog visible.to-view=\\\"!isMobile && !!selectedHotSpot\\\" text.to-view=\\\"selectedHotSpot.text\\\" t=\\\"[btn-text]common.course-block-mp-hot-spot.close\\\" anchor-element.to-view=\\\"selectedHotSpotButtonDomElement\\\" wrapper-element.to-view=\\\"hotSpotWrapperDomElement\\\" close.trigger=\\\"deselectHotSpot()\\\"></placeable-dialog> </div> <mp-hot-spot-drawer if.to-view=\\\"!isNonInteractivePreview && isMobile\\\" class=\\\"course-block-mp-hot-spot__drawer\\\" selected-hot-spot.to-view=\\\"selectedHotSpot\\\" pagination-display.to-view=\\\"paginationDisplay\\\" show-details-clicked.trigger=\\\"selectNextHotSpot()\\\" close-clicked.trigger=\\\"deselectHotSpot()\\\" next-clicked.trigger=\\\"selectNextHotSpot()\\\" previous-clicked.trigger=\\\"selectPreviousHotSpot()\\\"></mp-hot-spot-drawer> </template>\";\n// Exports\nexport default code;","// Module\nvar code = \"<template class=\\\"course-block-mp-rfg\\\"> <mp-rapid-fire-game if.to-view=\\\"!isNonInteractivePreview\\\" data.to-view=\\\"gameData\\\" nav-preview.to-view=\\\"navPreview\\\" mp-game-finished.trigger=\\\"handleMpGameFinished($event)\\\" mp-game-started.trigger=\\\"handleMpGameStarted($event)\\\"></mp-rapid-fire-game> <static-block-preview if.to-view=\\\"isNonInteractivePreview\\\" class=\\\"course-block-mp-rfg__preview\\\" icon-name=\\\"File/rapid-fire\\\"></static-block-preview> </template>\";\n// Exports\nexport default code;","// Module\nvar code = \"<template class=\\\"course-block-mp-risi\\\"> <template if.to-view=\\\"block\\\"></template> <static-block-preview if.to-view=\\\"isNonInteractivePreview\\\" class=\\\"course-block-mp-risi__placeholder\\\" icon-name=\\\"Uce/Risi\\\"></static-block-preview> <template else> <div class=\\\"course-block-mp-risi__start-container\\\"> <template if.to-view=\\\"!completed\\\"> <h1 class=\\\"course-block-mp-risi__title\\\" t=\\\"common.course-block-mp-risi.title\\\"></h1> <div class=\\\"course-block-mp-risi__text\\\" t=\\\"common.course-block-mp-risi.description\\\"></div> <button-smaller if.to-view=\\\"!isNonInteractivePreview && block\\\" t=\\\"[btn-text]common.course-block-mp-risi.start\\\" click.delegate=\\\"startLevel()\\\"></button-smaller> </template> <template else> <lottie-animation-player class=\\\"course-block-mp-risi__completed-animation\\\" animation-data.to-view=\\\"completedAnimationData\\\" loop.to-view=\\\"false\\\" autoplay.to-view=\\\"true\\\"></lottie-animation-player> <div class=\\\"course-block-mp-risi__text\\\" t=\\\"common.course-block-mp-risi.completed\\\"></div> <button-standard t=\\\"[btn-text]common.course-block-mp-risi.replay\\\" btn-type=\\\"outlined\\\" click.delegate=\\\"startLevel()\\\"></button-standard> </template> </div> <full-screen-container if.to-view=\\\"showDesktopOverlay\\\" view-model.ref=\\\"desktopFullScreenContainer\\\" overlap-main-navigation-bar.to-view=\\\"false\\\" overlap-navigation-side-bar.to-view=\\\"false\\\" overlap-header=\\\"mobile\\\" content-ready-to-render.from-view=\\\"desktopContentReadyToRender\\\"> <div if.to-view=\\\"currentLevel && desktopContentReadyToRender\\\" class=\\\"course-block-mp-risi__game-overlay\\\" element.ref=\\\"gameOverlay\\\"> <div class=\\\"course-block-mp-risi__game-container\\\" element.ref=\\\"gameContainer\\\"> <risi-game class=\\\"course-block-mp-risi__game\\\" style.to-view=\\\"gameStyle\\\" level-name.to-view=\\\"currentLevel\\\" risi-level-finished.trigger=\\\"handleLevelFinished($event)\\\"></risi-game> </div> </div> </full-screen-container> </template> </template>\";\n// Exports\nexport default code;","// Module\nvar code = \"<template class=\\\"course-block-mp-scorm\\\"> <div class=\\\"course-block-mp-scorm__full\\\" if.to-view=\\\"showScorm\\\"> <scorm-player class=\\\"course-block-mp-scorm__player\\\" title.to-view=\\\"block.scormTitle\\\" scorm-url.to-view=\\\"block.scormUrl\\\"></scorm-player> <div class=\\\"course-block-mp-scorm__navbar\\\"> <button-smaller btn-type=\\\"icon\\\" icon-type=\\\"custom\\\" icon-name=\\\"System/Close\\\" btn-style=\\\"inverted\\\" click.trigger=\\\"closeScorm()\\\"></button-smaller> </div> </div> <div else class=\\\"course-block-mp-scorm__start\\\"> <button-standard class=\\\"course-block-mp-scorm__start-button\\\" t=\\\"[btn-text]common.course-block-scorm.openScorm\\\" click.delegate=\\\"openScorm()\\\"></button-standard> <h1 class=\\\"course-block-mp-scorm__title\\\">${block.scormTitle}</h1> </div> </template>\";\n// Exports\nexport default code;","// Module\nvar code = \"<template class=\\\"course-block-mp-sequence-sidebar\\\"> <button-smaller show.to-view=\\\"showNextButton\\\" class=\\\"course-block-mp-sequence-sidebar__answer-button\\\" t=\\\"[aria-label]common.course-block-mp-sequence.next\\\" btn-type=\\\"icon\\\" icon-type=\\\"custom\\\" icon-name=\\\"System/Arrow - Type2 - Right\\\" disabled.to-view=\\\"disabled\\\" click.trigger=\\\"handleButtonClicked()\\\"></button-smaller> <button-smaller if.to-view=\\\"showCloseButton\\\" t=\\\"[aria-label]common.course-block-mp-sequence.quit\\\" btn-type=\\\"icon\\\" icon-type=\\\"custom\\\" icon-name=\\\"System/Close\\\" btn-style=\\\"inverted\\\" click.trigger=\\\"handleCloseButtonClicked()\\\"></button-smaller> <template if.to-view=\\\"!showCloseButton\\\"> <safe-icon class=\\\"course-block-mp-sequence-sidebar__exit-icon\\\" icon-name=\\\"System/Exit Landscape\\\"></safe-icon> <span class=\\\"course-block-mp-sequence-sidebar__exit-subtitle\\\" t=\\\"common.course-block-mp-sequence-sidebar.toQuit\\\"></span> </template> <audio-service-button if.to-view=\\\"audioUrl\\\" class=\\\"course-block-mp-sequence-sidebar__text-to-speech\\\" src.to-view=\\\"audioUrl\\\"></audio-service-button> </template>\";\n// Exports\nexport default code;","// Module\nvar code = \"<template class=\\\"course-block-mp-sequence-slide course-block-mp-sequence-slide--type-${currentBlock.type} ${ active ? '' : 'course-block-mp-sequence-slide--inactive' } ${ isDualVideo ? `course-block-mp-sequence-slide--dual-video course-block-mp-sequence-slide--dual-video-${currentBlock.pipVideoPosition}` : '' }\\\" markdown-jump-slide-link-click.delegate=\\\"handleJumpSlideLinkClick($event)\\\" inert.to-view=\\\"!active\\\"> <x-picture if.to-view=\\\"currentBlock.type === BlockType.PICTURE\\\" picture.to-view=\\\"currentBlock.picture\\\" class=\\\"course-block-picture__img\\\"></x-picture> <video-player if.to-view=\\\"currentBlock.type === BlockType.VIDEO && !isDualVideo\\\" show.to-view=\\\"!videoEnded && active\\\" view-model.ref=\\\"videoPlayer\\\" class=\\\"course-block-video__video-player\\\" auto-play.to-view=\\\"true\\\" reset-on-end.to-view=\\\"false\\\" video-sources.to-view=\\\"currentBlock.videoSources\\\" vimeo-id.to-view=\\\"currentBlock.vimeoId\\\" bunny-id.to-view=\\\"currentBlock.bunnyId\\\" used-thumbnail-src.from-view=\\\"videoThumbnailSrc\\\" play-state-changed.trigger=\\\"handlePlayStateChanged($event)\\\"></video-player> <dual-video-player if.to-view=\\\"currentBlock.type === BlockType.VIDEO && isDualVideo\\\" show.to-view=\\\"!videoEnded && active\\\" view-model.ref=\\\"videoPlayer\\\" class=\\\"course-block-video__video-player\\\" auto-play.to-view=\\\"true\\\" reset-on-end.to-view=\\\"false\\\" video-sources1.to-view=\\\"currentBlock.videoSources\\\" vimeo-id1.to-view=\\\"currentBlock.vimeoId\\\" bunny-id1.to-view=\\\"currentBlock.bunnyId\\\" used-thumbnail-src1.from-view=\\\"videoThumbnailSrc\\\" video-sources2.to-view=\\\"currentBlock.pipVideoSources\\\" vimeo-id2.to-view=\\\"currentBlock.pipVimeoId\\\" bunny-id2.to-view=\\\"currentBlock.pipBunnyId\\\" secondary-position.to-view=\\\"currentBlock.pipVideoPosition\\\" used-thumbnail-src2.from-view=\\\"videoThumbnailSrc2\\\" play-state-changed.trigger=\\\"handlePlayStateChanged($event)\\\"></dual-video-player> <div if.to-view=\\\"currentBlock.type === BlockType.VIDEO && videoEnded\\\" class=\\\"course-block-mp-sequence-slide__replay-video-overlay\\\" style.to-view=\\\"replayVideoOverlayStyle\\\"> <div if.to-view=\\\"isDualVideo\\\" class=\\\"course-block-mp-sequence-slide__replay-pip-video-overlay\\\" style.to-view=\\\"replayPipVideoOverlayStyle\\\"></div> <div class=\\\"course-block-mp-sequence-slide__replay-video-overlay-btn-container\\\"> <button-standard click.trigger=\\\"replayVideo()\\\" t=\\\"[btn-text]common.course-block-mp-sequence.replayVideo\\\" btn-type=\\\"transparent-outlined\\\" icon-type=\\\"custom\\\" icon-name=\\\"System/Refresh\\\" btn-shape=\\\"condensed\\\"></button-standard> <button-standard click.trigger=\\\"goToNextSlide()\\\" t=\\\"[btn-text]common.course-block-mp-sequence.next\\\" btn-type=\\\"contained\\\" btn-shape=\\\"condensed\\\"></button-standard> </div> </div> <div if.to-view=\\\"currentBlock.type === BlockType.AUDIO\\\" class=\\\"course-block-mp-sequence-slide__audio-block\\\"> <button-smaller if.to-view=\\\"showPreviousSlideButton\\\" class=\\\"course-block-mp-sequence-slide__text-block-previous-slide-btn\\\" click.trigger=\\\"goToPreviousSlide()\\\" t=\\\"[aria-label]common.course-block-mp-sequence.back\\\" btn-type=\\\"icon\\\" icon-type=\\\"custom\\\" icon-name=\\\"System/Arrow - Type2 - Left\\\"></button-smaller> <course-block-audio class=\\\"course-block-mp-sequence-slide__audio\\\" context-identifier.to-view=\\\"contextIdentifier\\\" compact.to-view=\\\"true\\\" block-id.to-view=\\\"currentBlock.id\\\"></course-block-audio> <course-block-mp-sequence-sidebar if.to-view=\\\"!isDesktopVersion\\\" button-clicked.trigger=\\\"goToNextSlide()\\\"></course-block-mp-sequence-sidebar> </div> <div if.to-view=\\\"currentBlock.type === BlockType.TEXT && active\\\" class=\\\"course-block-mp-sequence-slide__text-block\\\"> <button-smaller if.to-view=\\\"showPreviousSlideButton\\\" class=\\\"course-block-mp-sequence-slide__text-block-previous-slide-btn\\\" click.trigger=\\\"goToPreviousSlide()\\\" t=\\\"[aria-label]common.course-block-mp-sequence.back\\\" btn-type=\\\"icon\\\" icon-type=\\\"custom\\\" icon-name=\\\"System/Arrow - Type2 - Left\\\"></button-smaller> <div class=\\\"course-block-mp-sequence-slide__content-wrapper\\\"> <div class=\\\"course-block-mp-sequence-slide__title\\\"> <h6 if.to-view=\\\"currentBlock.title\\\" class=\\\"course-block-mp-sequence-slide__title-text\\\">${currentBlock.title}</h6> </div> <div class=\\\"course-block-mp-sequence-slide__text ${ slide.rowStyle === SlideRowStyle.GROW_FIRST ? 'course-block-mp-sequence-slide__text--center' : '' }\\\"> <rich-text text.to-view=\\\"currentBlock.text\\\" mode=\\\"content\\\"></rich-text> </div> </div> <course-block-mp-sequence-sidebar if.to-view=\\\"!isDesktopVersion\\\" show-next-button.to-view=\\\"showNextButton\\\" audio-url.to-view=\\\"currentBlock.audioUrl\\\" button-clicked.trigger=\\\"goToNextSlide()\\\"></course-block-mp-sequence-sidebar> </div> <div if.to-view=\\\"currentBlock.type === BlockType.MP_INTERACTIVE_TEXT && active\\\" class=\\\"course-block-mp-sequence-slide__text-block\\\"> <mp-interactive-text markdown.to-view=\\\"currentBlock.text\\\" class=\\\"course-block-mp-sequence-slide__mp-interactive-text\\\" allow-wrong-answers.to-view=\\\"currentBlock.errorSlideId !== null\\\" is-desktop-version.to-view=\\\"isDesktopVersion\\\" mp-game-finished.trigger=\\\"handleMpGameFinished($event)\\\" show-help.to-view=\\\"!isDesktopVersion\\\" landscape-mode.to-view=\\\"true\\\"> <button-smaller if.to-view=\\\"showPreviousSlideButton\\\" class=\\\"course-block-mp-sequence-slide__interactive-text-previous-slide-btn\\\" click.trigger=\\\"goToPreviousSlide()\\\" t=\\\"[aria-label]common.course-block-mp-sequence.back\\\" btn-type=\\\"icon\\\" icon-type=\\\"custom\\\" icon-name=\\\"System/Arrow - Type2 - Left\\\" slot=\\\"leftHeaderElement\\\"></button-smaller> </mp-interactive-text> </div> <mp-question if.to-view=\\\"currentBlock.type === BlockType.MP_QUESTION && active\\\" view-model.ref=\\\"mpQuestionElement\\\" question.to-view=\\\"currentQuestion\\\" is-desktop-version.to-view=\\\"isDesktopVersion\\\" is-answer-selected.from-view=\\\"mpQuestionIsAnswerSelected\\\" mp-question-answered.trigger=\\\"handleMpQuestionAnswered($event)\\\"> <button-smaller if.to-view=\\\"showPreviousSlideButton\\\" class=\\\"course-block-mp-sequence-slide__previous-slide-btn\\\" click.trigger=\\\"goToPreviousSlide()\\\" t=\\\"[aria-label]common.course-block-mp-sequence.back\\\" btn-type=\\\"icon\\\" icon-type=\\\"custom\\\" icon-name=\\\"System/Arrow - Type2 - Left\\\" slot=\\\"leftHeaderElement\\\"></button-smaller> </mp-question> <div if.to-view=\\\"currentBlock.type === BlockType.VIDEO\\\" class=\\\"course-block-mp-sequence-slide__header\\\"> <button-smaller if.to-view=\\\"showPreviousSlideButton\\\" class=\\\"course-block-mp-sequence-slide__previous-slide-btn\\\" click.trigger=\\\"goToPreviousSlide()\\\" t=\\\"[aria-label]common.course-block-mp-sequence.back\\\" btn-type=\\\"icon\\\" icon-type=\\\"custom\\\" icon-name=\\\"System/Arrow - Type2 - Left\\\"></button-smaller> <div if.to-view=\\\"currentBlock.type === BlockType.VIDEO && currentBlock.text\\\" class=\\\"course-block-mp-sequence-slide__video-next-block-question ${ showVideoHelpText ? `course-block-mp-sequence-slide__video-next-block-question--open` : `` }\\\"> <span if.to-view=\\\"showVideoHelpText\\\" class=\\\"course-block-mp-sequence-slide__video-next-block-question-question\\\">${ currentBlock.text }</span> <safe-icon class=\\\"course-block-mp-sequence-slide__video-next-block-question-icon\\\" click.trigger=\\\"toggleShowVideoHelpText()\\\" t=\\\"[aria-label]common.course-block-mp-sequence.${ showVideoHelpText ? 'quit' : 'back' }\\\" icon-name.to-view=\\\"showVideoHelpText ? `System/IAS - Close` : `System/LightBulb`\\\"></safe-icon> </div> </div> </template>\";\n// Exports\nexport default code;","// Module\nvar code = \"<template class=\\\"course-block-mp-sequence\\\"> <div if.to-view=\\\"!showInline && !completed\\\"> <full-screen-container view-model.ref=\\\"fullScreenContainer\\\" keep-inline.to-view=\\\"true\\\" content-ready-to-render.from-view=\\\"contentReadyToRender\\\"> <div if.to-view=\\\"contentReadyToRender\\\" class=\\\"course-block-mp-sequence__fullscreen\\\"> <aspect-ratio-container mode=\\\"width-and-height-dependent\\\" class=\\\"course-block-mp-sequence__fullscreen-aspect\\\" ratio.to-view=\\\"16/9\\\"> <template repeat.for=\\\"slideId of iasSlideIds\\\"> <course-block-mp-sequence-slide show.to-view=\\\"currentSlideId === slideId\\\" class=\\\"course-block-mp-sequence__slide\\\" course-content.to-view=\\\"courseContent\\\" context-identifier.to-view=\\\"contextIdentifier\\\" slide-id.to-view=\\\"slideId\\\" active.to-view=\\\"currentSlideId === slideId\\\" show-previous-slide-button.to-view=\\\"hasPreviousSlide && !completed\\\" go-to-slide.trigger=\\\"handleGoToSlide($event)\\\" go-to-previous-slide.trigger=\\\"handleGoToPreviousSlide()\\\"></course-block-mp-sequence-slide> </template> </aspect-ratio-container> </div> </full-screen-container> <div class=\\\"course-block-mp-sequence__portrait-container\\\"> <div class=\\\"course-block-mp-sequence__caption\\\" t=\\\"common.course-block-mp-sequence.caption\\\"></div> <div class=\\\"course-block-mp-sequence__title\\\">${block.text}</div> <lottie-animation-player class=\\\"course-block-mp-sequence__animation\\\" animation-data.to-view=\\\"rotateDeviceAnimationData\\\" loop.to-view=\\\"true\\\" autoplay.to-view=\\\"true\\\"></lottie-animation-player> <div class=\\\"course-block-mp-sequence__rotate-info\\\" t=\\\"common.course-block-mp-sequence.rotate-info\\\"></div> </div> </div> <div if.to-view=\\\"showInline && !completed\\\"> <full-screen-container if.to-view=\\\"showDesktopOverlay\\\" view-model.ref=\\\"desktopFullScreenContainer\\\" keep-inline.to-view=\\\"true\\\" content-ready-to-render.from-view=\\\"desktopContentReadyToRender\\\"> <div class=\\\"course-block-mp-sequence__desktop-fullscreen\\\"> <div class=\\\"course-block-mp-sequence__desktop-ias-card\\\" ref=\\\"desktopFullscreenContentElement\\\" tabindex=\\\"0\\\"> <div class=\\\"course-block-mp-sequence__desktop-ias-content\\\"> <aspect-ratio-container if.to-view=\\\"showInline && !completed && desktopContentReadyToRender\\\" class=\\\"course-block-mp-sequence__inline-aspect\\\" mode=\\\"width-and-height-dependent\\\" ratio.to-view=\\\"16/9\\\"> <template repeat.for=\\\"slideId of iasSlideIds\\\"> <course-block-mp-sequence-slide class=\\\"course-block-mp-sequence__slide\\\" course-content.to-view=\\\"courseContent\\\" context-identifier.to-view=\\\"contextIdentifier\\\" slide-id.to-view=\\\"slideId\\\" active.to-view=\\\"currentSlideId === slideId\\\" show-previous-slide-button.to-view=\\\"hasPreviousSlide && !completed\\\" go-to-slide.trigger=\\\"handleGoToSlide($event)\\\" go-to-previous-slide.trigger=\\\"handleGoToPreviousSlide()\\\" slide-controls-changed.trigger=\\\"handleSlideControlsChanged($event)\\\" is-desktop-version.to-view=\\\"true\\\"></course-block-mp-sequence-slide> </template> </aspect-ratio-container> </div> <course-block-mp-sequence-sidebar class=\\\"course-block-mp-sequence__desktop-sidebar\\\" show-close-button.to-view=\\\"true\\\" show-next-button.to-view=\\\"nextButtonCallback !== null\\\" audio-url.to-view=\\\"currentBlock.type === BlockType.TEXT ? currentBlock.audioUrl : null\\\" button-clicked.trigger=\\\"handleNextButtonClick()\\\" disabled.to-view=\\\"!nextButtonEnabled\\\" close-button-clicked.trigger=\\\"closeDesktopSequence()\\\"></course-block-mp-sequence-sidebar> </div> </div> </full-screen-container> <div class=\\\"course-block-mp-sequence__desktop-start-container\\\"> <h1 class=\\\"course-block-mp-sequence__desktop-title\\\">${block.text}</h1> <lottie-animation-player class=\\\"course-block-mp-sequence__animation\\\" animation-data.to-view=\\\"iasDesktopStartAnimationData\\\" loop.to-view=\\\"true\\\" autoplay.to-view=\\\"true\\\"></lottie-animation-player> <div class=\\\"course-block-mp-sequence__desktop-text\\\" t=\\\"common.course-block-mp-sequence.desktop-text\\\"></div> <button-standard t=\\\"[btn-text]common.course-block-mp-sequence.start-seqeuence\\\" click.delegate=\\\"startDesktopSequence()\\\"></button-standard> </div> </div> <div if.to-view=\\\"completed\\\" class=\\\"course-block-mp-sequence__desktop-completed-container\\\"> <lottie-animation-player class=\\\"course-block-mp-sequence__completed-animation\\\" animation-data.to-view=\\\"completedAnimationData\\\" loop.to-view=\\\"false\\\" autoplay.to-view=\\\"true\\\"></lottie-animation-player> <button-standard if.to-view=\\\"showInline\\\" t=\\\"[btn-text]common.course-block-mp-sequence.replay-seqeuence\\\" click.delegate=\\\"restartDesktopSequence()\\\"></button-standard> </div> </template>\";\n// Exports\nexport default code;","// Module\nvar code = \"<template class=\\\"course-block-mp-tile\\\" style.to-view=\\\"{\\n            '--course-block-mp-tile-count': block.tiles.length,\\n            '--course-block-mp-tile-width-per-tile': widthPerTile + 'px',\\n          }\\\"> <div class=\\\"lp-site-content-small course-block-mp-tile__content-wrapper ${ block.tiles.length === 1 ? 'course-block-mp-tile__content-wrapper--single-tile' : '' }\\\" ref=\\\"tileWrapperDomElement\\\"> <mp-tile repeat.for=\\\"tile of block.tiles\\\" size.to-view=\\\"block.tiles.length > 1 ? `small` : `big`\\\" tile.to-view=\\\"tile\\\" style.to-view=\\\"{ '--course-block-mp-tile-index': $index }\\\"></mp-tile> </div> </template>\";\n// Exports\nexport default code;","// Module\nvar code = \"<template class=\\\"course-block-mp\\\"> <mp-interactive-text class=\\\"course-block-mp__mp-container\\\" markdown.to-view=\\\"block.text\\\" is-desktop-version.to-view=\\\"true\\\" mp-game-finished.trigger=\\\"handleMpGameFinished($event)\\\"></mp-interactive-text> </template> \";\n// Exports\nexport default code;","// Module\nvar code = \"<template class=\\\"course-block-picture\\\"> <x-picture class=\\\"course-block-picture__img\\\" picture.to-view=\\\"block.picture\\\" click.delegate=\\\"openImageOverlay()\\\"></x-picture> <exam-image-overlay if.to-view=\\\"block && displayImageOverlay\\\" img-src.to-view=\\\"block.picture.url\\\" alt-text.to-view=\\\"block.title\\\" click.delegate=\\\"closeImageOverlay()\\\" close-exam-image-overlay.delegate=\\\"closeImageOverlay()\\\"></exam-image-overlay> </template> \";\n// Exports\nexport default code;","// Module\nvar code = \"<template class=\\\"course-block-question lp-site-content-small\\\"> <div class=\\\"course-block-question__content\\\" data-test-selector=\\\"course-block-question-${block.id}\\\"> <progress-bar progress.to-view=\\\"progress\\\" data-test-selector=\\\"course-block-question-progress-bar-${block.id}\\\"></progress-bar> <span class=\\\"course-block-question__title\\\" data-test-selector=\\\"course-block-question-title-${block.id}\\\" t=\\\"common.course-block-question.questionsTitle\\\"></span> <span class=\\\"course-block-question__description\\\" data-test-selector=\\\"course-block-question-description-${block.id}\\\" t=\\\"common.course-block-question.questionsDescription\\\"></span> <div class=\\\"course-block-question__status\\\"> <span class=\\\"course-block-question__status-text ${complete ? 'course-block-question__status-text--complete' : ''}\\\" t=\\\"common.course-block-question.answeredCorrectly\\\"></span> <span class=\\\"course-block-question__status-summary ${complete ? 'course-block-question__status-summary--complete' : ''}\\\" data-test-selector=\\\"course-block-question-progress-text-${block.id}\\\" t-params.to-view=\\\"{ count: answeredQuestionCount, total: block.questionIds.length }\\\" t=\\\"common.course-block-question.questionsProgress\\\"></span> </div> <div class=\\\"course-block-question__button-container\\\"> <button-standard if.to-view=\\\"!complete\\\" class=\\\"course-block-question__button-answer\\\" btn-type=\\\"contained\\\" click.delegate=\\\"goToAnswerQuestions()\\\" t=\\\"[btn-text]common.course-block-question.startQuestions\\\"></button-standard> <button-standard if.to-view=\\\"complete\\\" class=\\\"course-block-question__button-complete\\\" click.delegate=\\\"goToShowQuestions()\\\" btn-type=\\\"outlined\\\" t=\\\"[btn-text]common.course-block-question.showQuestions\\\"></button-standard> <x-icon if.to-view=\\\"complete\\\" class=\\\"course-block-question__clear-icon\\\" size=\\\"unspecified\\\" icon-type=\\\"custom\\\" icon-name=\\\"System/Check\\\"></x-icon> </div> </div> </template>\";\n// Exports\nexport default code;","// Module\nvar code = \"<template class=\\\"course-block-spacer\\\"> <div if.to-view=\\\"isNonInteractivePreview\\\" class=\\\"course-block-spacer__hatching\\\"></div> </template>\";\n// Exports\nexport default code;","// Module\nvar code = \"<template> <div class=\\\"lp-site-content-small course-block-text__content-wrapper\\\"> <rich-text text.to-view=\\\"block.text\\\" context-identifier.to-view=\\\"contextIdentifier\\\" mode.to-view=\\\"textMode\\\" annotate.to-view=\\\"!navPreview\\\" class=\\\"course-block-text__text\\\"></rich-text> <audio-service-button if.to-view=\\\"block.audioUrl\\\" class=\\\"course-block-text__text-to-speech\\\" src.to-view=\\\"block.audioUrl\\\"></audio-service-button> </div> </template>\";\n// Exports\nexport default code;","// Module\nvar code = \"<template class=\\\"course-block-video\\\"> <div class=\\\"course-block-video__wrapper\\\"> <video-player if.to-view=\\\"!isDualVideo\\\" class=\\\"course-block-video__video-player\\\" video-sources.to-view=\\\"block.videoSources\\\" vimeo-id.to-view=\\\"block.vimeoId\\\" bunny-id.to-view=\\\"block.bunnyId\\\" current-time.to-view=\\\"currentTime\\\" transcript.to-view=\\\"block.text\\\" seconds-changed.trigger=\\\"handleSecondsChanged($event)\\\" play-state-changed.trigger=\\\"handlePlayStateChanged($event)\\\" source-mode.to-view=\\\"sourceMode\\\" video-url.to-view=\\\"videoUrl\\\" bordered.to-view=\\\"true\\\"></video-player> <dual-video-player if.to-view=\\\"isDualVideo\\\" class=\\\"course-block-video__video-player\\\" video-sources1.to-view=\\\"block.videoSources\\\" vimeo-id1.to-view=\\\"block.vimeoId\\\" bunny-id1.to-view=\\\"block.bunnyId\\\" video-url1.to-view=\\\"videoUrl\\\" source-mode1.to-view=\\\"sourceMode\\\" video-sources2.to-view=\\\"block.pipVideoSources\\\" vimeo-id2.to-view=\\\"block.pipVimeoId\\\" bunny-id2.to-view=\\\"block.pipBunnyId\\\" video-url2.to-view=\\\"pipVideoUrl\\\" source-mode2.to-view=\\\"sourceMode\\\" secondary-position.to-view=\\\"block.pipVideoPosition\\\" current-time.to-view=\\\"currentTime\\\" transcript.to-view=\\\"block.text\\\" seconds-changed.trigger=\\\"handleSecondsChanged($event)\\\" play-state-changed.trigger=\\\"handlePlayStateChanged($event)\\\" bordered.to-view=\\\"true\\\"></dual-video-player> <img if.to-view=\\\"isStatic && thumbnailSrc\\\" class=\\\"course-block-video__thumbnail\\\" src.to-view=\\\"thumbnailSrc\\\"> </div> </template>\";\n// Exports\nexport default code;","// Module\nvar code = \"<template class=\\\"course-block ${ needsFullHeight ? 'course-block--full-height' : '' }\\\" style.to-view=\\\"blockStyle\\\"> <ias-block if.to-view=\\\"isIas === true\\\" class=\\\"universal-block__ias-block\\\" context-identifier.to-view=\\\"contextIdentifier\\\" block-id.to-view=\\\"blockId\\\" nav-preview.to-view=\\\"navPreview\\\" is-non-interactive-preview.to-view=\\\"isNonInteractivePreview\\\"></ias-block> <template else> <course-block-audio if.to-view=\\\"block.type === BlockType.AUDIO\\\" context-identifier.to-view=\\\"contextIdentifier\\\" block-id.to-view=\\\"blockId\\\"></course-block-audio> <course-block-file if.to-view=\\\"block.type === BlockType.FILE\\\" block.to-view=\\\"block\\\"></course-block-file> <content-block-formula if.to-view=\\\"block.type === BlockType.FORMULA\\\" formula.to-view=\\\"block.formula\\\"></content-block-formula> <course-block-mp if.to-view=\\\"block.type === BlockType.MP_INTERACTIVE_TEXT\\\" context-identifier.to-view=\\\"contextIdentifier\\\" block-id.to-view=\\\"blockId\\\" block.to-view=\\\"block\\\"></course-block-mp> <course-block-picture if.to-view=\\\"block.type === BlockType.PICTURE\\\" block.to-view=\\\"block\\\"></course-block-picture> <course-block-question if.to-view=\\\"block.type === BlockType.QUESTION\\\" context-identifier.to-view=\\\"contextIdentifier\\\" block-id.to-view=\\\"blockId\\\" block.to-view=\\\"block\\\"></course-block-question> <course-block-text if.to-view=\\\"block.type === BlockType.TEXT\\\" nav-preview.to-view=\\\"navPreview\\\" context-identifier.to-view=\\\"contextIdentifier\\\" block-id.to-view=\\\"blockId\\\" block.to-view=\\\"block\\\"></course-block-text> <course-block-video if.to-view=\\\"block.type === BlockType.VIDEO || block.type === BlockType.YOUTUBE || block.type === BlockType.VIMEO\\\" context-identifier.to-view=\\\"contextIdentifier\\\" is-static.to-view=\\\"isNonInteractivePreview || navPreview\\\" block-id.to-view=\\\"blockId\\\"></course-block-video> <course-block-mp-tile if.to-view=\\\"block.type === BlockType.MP_TILES\\\" context-identifier.to-view=\\\"contextIdentifier\\\" block-id.to-view=\\\"blockId\\\" block.to-view=\\\"block\\\"></course-block-mp-tile> <course-block-mp-rfg if.to-view=\\\"block.type === BlockType.MP_RFG\\\" nav-preview.to-view=\\\"navPreview\\\" context-identifier.to-view=\\\"contextIdentifier\\\" block-id.to-view=\\\"blockId\\\" block.to-view=\\\"block\\\" is-non-interactive-preview.to-view=\\\"isNonInteractivePreview\\\"></course-block-mp-rfg> <course-block-spacer if.to-view=\\\"block.type === BlockType.SPACER\\\" is-non-interactive-preview.to-view=\\\"isNonInteractivePreview\\\"></course-block-spacer> <course-block-mp-scorm if.to-view=\\\"block.type === BlockType.MP_SCORM\\\" context-identifier.to-view=\\\"contextIdentifier\\\" block-id.to-view=\\\"blockId\\\" is-non-interactive-preview.to-view=\\\"isNonInteractivePreview\\\"></course-block-mp-scorm> <course-block-mp-hot-spot if.to-view=\\\"block.type === BlockType.MP_HOT_SPOT\\\" context-identifier.to-view=\\\"contextIdentifier\\\" block-id.to-view=\\\"blockId\\\" is-non-interactive-preview.to-view=\\\"isNonInteractivePreview\\\"></course-block-mp-hot-spot> <course-block-mp-flipping-card if.to-view=\\\"block.type === BlockType.MP_FLIPPING_CARD\\\" context-identifier.to-view=\\\"contextIdentifier\\\" block-id.to-view=\\\"blockId\\\" is-non-interactive-preview.to-view=\\\"isNonInteractivePreview\\\"></course-block-mp-flipping-card> <course-block-mp-risi if.to-view=\\\"block.type === BlockType.MP_RISI\\\" context-identifier.to-view=\\\"contextIdentifier\\\" block-id.to-view=\\\"blockId\\\" is-non-interactive-preview.to-view=\\\"isNonInteractivePreview\\\"></course-block-mp-risi> </template> </template>\";\n// Exports\nexport default code;","import { bindable, autoinject, useView, PLATFORM, Parser, ObserverLocator } from 'aurelia-framework';\nimport { ICourseBlockDto } from 'common/dto/course/content/block.interface';\nimport { IBlockProgressReturnDto, ICourseProgress, IPendingBlockProgress } from 'common/dto/course/progress/course.progress.interface';\nimport { assertNotNullOrUndefined } from 'common/utils/assert';\nimport { SecondsChangedEvent, PlayStateChangedEvent, PlayState } from 'common/aurelia-components/audio-player/audio-player';\nimport { PendingBlockProgressUtils } from 'common/utils/PendingBlockProgressUtils';\nimport { UpdateTimer } from 'common/aurelia-classes/UpdateTimer';\nimport { MediaBlockUtils } from 'common/utils/MediaBlockUtils';\nimport { BlockProgressService } from '../course-block';\nimport { selectState, SelectStateObservableComponent } from 'common/aurelia-decorators/select-state';\nimport { blockByIdSelectorCreator } from 'common/store/selectors/content.context.selectors';\nimport { mediaPercentageConsideredSeenSelectorCreator } from 'common/store/selectors/course.context.selectors';\n\n@useView(PLATFORM.moduleName('common/aurelia-components/course-block/course-block-audio/course-block-audio.html'))\n@autoinject()\nexport class CourseBlockAudio extends SelectStateObservableComponent {\n  @bindable() public contextIdentifier: string | null = null;\n  @bindable() public blockId: number | null = null;\n  @bindable() public compact: boolean = false;\n\n  // todo(cleanup) why selectorCreator? -> can be provided by binding directly, wouldn't that be better?\n  @selectState(CourseBlockAudio, blockByIdSelectorCreator, ['contextIdentifier', 'blockId']) private block?: ICourseBlockDto | null;\n  @selectState(CourseBlockAudio, mediaPercentageConsideredSeenSelectorCreator, ['contextIdentifier']) private mediaPercentageConsideredSeen?: number | null;\n\n  private accessCodeId: number | null = null;\n  private blockProgress: Array<IBlockProgressReturnDto> | null = null;\n  private pendingBlockProgress: Array<IPendingBlockProgress> | null = null;\n  private courseProgress: ICourseProgress | null = null;\n  private currentTimeLoaded = false;\n  private currentTime = 0;\n  private lastPlayTimeTickTimestamp: number | null = null;\n  private isPlaying = false;\n\n  private domElement: HTMLElement;\n\n  constructor(\n    element: Element,\n    parser: Parser,\n    observerLocator: ObserverLocator,\n    private blockProgressService: BlockProgressService,\n  ) {\n    super(parser, observerLocator);\n    this.domElement = element as HTMLElement;\n  }\n\n  protected attached(): void {\n    this.subscribeToProgress();\n    UpdateTimer.getInstance().subscribe(this, this.handlePlayTimeUpdateTick.bind(this));\n  }\n\n  protected detached(): void {\n    this.blockProgressService.stopTrackInteraction(this);\n    this.blockProgressService.disposeSubscriptions(this);\n    UpdateTimer.getInstance().unsubscribe(this);\n\n    if(this.isPlaying) {\n      this.handlePlayTimeUpdateTick();\n      this.isPlaying = false;\n    }\n  }\n\n  private contextIdentifierChanged(): void {\n    this.subscribeToProgress();\n  }\n\n  private blockChanged(): void {\n    this.currentTimeLoaded = false;\n    this.loadCurrentTimeIfNecessary();\n  }\n\n  private subscribeToProgress(): void {\n    this.blockProgressService.disposeSubscriptions(this);\n    if(!this.domElement.isConnected || this.contextIdentifier == null) return;\n\n    this.blockProgressService.subscribeToProgress(this, this.contextIdentifier, (accessCodeId, blockProgress, courseProgress, pendingBlockProgress) => {\n      this.accessCodeId = accessCodeId;\n      this.blockProgress = blockProgress;\n      this.courseProgress = courseProgress;\n      this.pendingBlockProgress = pendingBlockProgress;\n\n\n      this.loadCurrentTimeIfNecessary();\n    });\n  }\n\n  private loadCurrentTimeIfNecessary(): void {\n    if(!this.currentTimeLoaded && this.blockProgress !== null && this.block && this.pendingBlockProgress) {\n      const blockId = this.block.id;\n      const progress = this.blockProgress.find(bp => bp.blockId === blockId);\n      const pendingProgress = this.pendingBlockProgress.find(bp => bp.blockId === blockId && bp.accessCodeId === this.accessCodeId);\n\n      if(pendingProgress) {\n        this.currentTime = pendingProgress.currentTime;\n      } else if(progress) {\n        this.currentTime = progress.currentTime;\n      } else {\n        this.currentTime = 0;\n      }\n\n      this.currentTimeLoaded = true;\n    }\n  }\n\n  private async handleSecondsChanged(event: SecondsChangedEvent): Promise<void> {\n    const pendingBlockProgress = this.generateDefaultPendingBlockProgress();\n    pendingBlockProgress.currentTime = event.detail.seconds;\n    await this.blockProgressService.addPendingBlockProgress(pendingBlockProgress);\n  }\n\n  private handlePlayStateChanged(event: PlayStateChangedEvent): void {\n    if(event.detail.playState === PlayState.Start) {\n      this.blockProgressService.startTrackInteraction(this);\n      this.lastPlayTimeTickTimestamp = Date.now();\n      this.isPlaying = true;\n    } else {\n      this.blockProgressService.stopTrackInteraction(this);\n      if(this.isPlaying === true) {\n        this.handlePlayTimeUpdateTick();\n      }\n      this.isPlaying = false;\n    }\n  }\n\n  private handlePlayTimeUpdateTick(): void {\n    if(!this.isPlaying) {\n      return;\n    }\n\n    assertNotNullOrUndefined(this.lastPlayTimeTickTimestamp, 'can\\'t handlePlayTimeUpdateTick without lastPlayTimeTickTimestamp');\n    const now = Date.now();\n    this.commitPlayTime(now - this.lastPlayTimeTickTimestamp);\n    this.lastPlayTimeTickTimestamp = now;\n  }\n\n  private async commitPlayTime(msPlayed: number): Promise<void> {\n    const { block, mediaPercentageConsideredSeen } = this;\n    assertNotNullOrUndefined(block, 'can\\'t commitPlayTime without a block');\n    assertNotNullOrUndefined(mediaPercentageConsideredSeen, 'can\\'t commitPlayTime without mediaPercentageConsideredSeen');\n    const pendingBlockProgress = this.generateDefaultPendingBlockProgress();\n    pendingBlockProgress.msInteractedWith += msPlayed;\n    pendingBlockProgress.isFinished = MediaBlockUtils.checkIfPendingBlockIsFinished(\n      block.duration,\n      mediaPercentageConsideredSeen,\n      pendingBlockProgress,\n    );\n    await this.blockProgressService.addPendingBlockProgress(pendingBlockProgress);\n  }\n\n  private generateDefaultPendingBlockProgress(): IPendingBlockProgress {\n    const { accessCodeId, block, courseProgress, pendingBlockProgress } = this;\n    assertNotNullOrUndefined(block, 'can\\'t generateDefaultPendingBlockProgress without block');\n    assertNotNullOrUndefined(courseProgress, 'can\\'t generateDefaultPendingBlockProgress without courseProgressesByAccessCodeId');\n    assertNotNullOrUndefined(accessCodeId, 'can\\'t generateDefaultPendingBlockProgress without accessCodeId');\n    assertNotNullOrUndefined(pendingBlockProgress, 'can\\'t generateDefaultPendingBlockProgress without pendingBlockProgresses');\n\n    return PendingBlockProgressUtils.generateDefaultPendingBlockProgress(\n      accessCodeId,\n      block,\n      courseProgress,\n      pendingBlockProgress,\n    );\n  }\n}\n","import { autoinject, bindable, PLATFORM, useView } from 'aurelia-framework';\nimport { MimeTypes } from 'common/constants/mimetypes.constants';\nimport { IBlockDto, ICourseBlockDto } from 'common/dto/course/content/block.interface';\nimport { FileSymbol } from 'common/enum/file.enum';\nimport { assertNotNullOrUndefined } from 'common/utils/assert';\nimport { DownloadUtils } from 'common/utils/DownloadUtils';\nimport { StringUtils } from 'common/utils/StringUtils';\n\n@useView(PLATFORM.moduleName('common/aurelia-components/course-block/course-block-file/course-block-file.html'))\n@autoinject()\nexport class CourseBlockFile {\n  @bindable() public block: ICourseBlockDto | null = null;\n\n  private formatFileSize(block: IBlockDto): string {\n    return block.fileSizeBytes ? StringUtils.formatFileSize(block.fileSizeBytes) : '-';\n  }\n\n  private formatFileType(block: IBlockDto): string {\n    switch(block.fileType) {\n      case MimeTypes.ApplicationPdf: return 'Pdf';\n      case MimeTypes.ApplicationZip:\n      case MimeTypes.ApplicationZipCompressed: return 'Zip';\n      default: return 'File';\n    }\n  }\n\n  private getFileIcon(block: IBlockDto): string | undefined {\n    return `File/${block.fileSymbol ?? FileSymbol.Pdf}`;\n  }\n\n  private async downloadFile(): Promise<void> {\n    if(this.block == null) return;\n    assertNotNullOrUndefined(this.block.fileUrl, 'cannot download file without url');\n    assertNotNullOrUndefined(this.block.fileType, 'will not download file without mime type');\n    const { fileType } = this.block;\n    const filename = this.block.fileFilename ?? 'download.bin';\n    await DownloadUtils.download({ url: this.block.fileUrl, filename, mimeType: fileType });\n  }\n}\n","import { PLATFORM, autoinject, bindable, useView } from 'aurelia-framework';\nimport { selectState } from 'common/aurelia-decorators/select-state';\nimport { IMpFlippingCardPageDto } from 'common/dto/content/methodpool/flipping.card.page.interface';\nimport { ICourseBlockDto } from 'common/dto/course/content/block.interface';\nimport { IBlockProgressReturnDto, ICourseProgress, IPendingBlockProgress } from 'common/dto/course/progress/course.progress.interface';\nimport { blockByIdSelectorCreator } from 'common/store/selectors/content.context.selectors';\nimport { ArrayUtils } from 'common/utils/ArrayUtils';\nimport { FunctionUtils } from 'common/utils/FunctionUtils';\nimport { PendingBlockProgressUtils } from 'common/utils/PendingBlockProgressUtils';\nimport { assertNotNullOrUndefined } from 'common/utils/assert';\nimport { BlockProgressService } from '../course-block';\n\n@autoinject()\n@useView(PLATFORM.moduleName('common/aurelia-components/course-block/course-block-mp-flipping-card/course-block-mp-flipping-card.html'))\nexport class CourseBlockMpFlippingCard {\n  @bindable() public contextIdentifier: string | null = null;\n  @bindable() public blockId: number | null = null;\n  @bindable() public isNonInteractivePreview = true;\n  @bindable() public navPreview = false;\n\n  @selectState(CourseBlockMpFlippingCard, blockByIdSelectorCreator, ['contextIdentifier', 'blockId']) public block?: ICourseBlockDto | null;\n\n  private flippingCardPages: IMpFlippingCardPageDto[] = [];\n  private isFlipped: boolean = false;\n  private ANIMATION_DURATION_IN_SECONDS: number = 0.8;\n  private animating: boolean = false;\n\n  private accessCodeId: number | null = null;\n  private blockProgress: IBlockProgressReturnDto[] | null = null;\n  private pendingBlockProgress: IPendingBlockProgress[] | null = null;\n  private courseProgress: ICourseProgress | null = null;\n  private domElement: HTMLElement;\n\n  constructor(\n    private blockProgressService: BlockProgressService,\n    element: Element,\n  ) {\n    this.domElement = element as HTMLElement;\n  }\n\n  protected attached(): void {\n    this.subscribeToProgress();\n  }\n\n  protected detached(): void {\n    this.blockProgressService.disposeSubscriptions(this);\n  }\n\n  private blockChanged(): void {\n    this.updateFlippingCards();\n  }\n\n  private async updateFlippingCards(): Promise<void> {\n    if(!this.block) return;\n    this.flippingCardPages = ArrayUtils.immutableSort(this.block.flippingCardPages, (a, b) => a.order - b.order);\n  }\n\n  private async toggleIsFlipped(): Promise<void> {\n    this.isFlipped = !this.isFlipped;\n    this.animating = true;\n    await FunctionUtils.timeout(this.ANIMATION_DURATION_IN_SECONDS * 1000);\n    this.animating = false;\n    this.finishBlock();\n  }\n\n  private subscribeToProgress(): void {\n    this.blockProgressService.disposeSubscriptions(this);\n    if(!this.domElement.isConnected || this.contextIdentifier == null) return;\n\n    this.blockProgressService.subscribeToProgress(this, this.contextIdentifier, (accessCodeId, blockProgress, courseProgress, pendingBlockProgress) => {\n      this.accessCodeId = accessCodeId;\n      this.blockProgress = blockProgress;\n      this.courseProgress = courseProgress;\n      this.pendingBlockProgress = pendingBlockProgress;\n    });\n  }\n\n  private async finishBlock(): Promise<void> {\n    const pendingBlockProgress = this.generateDefaultPendingBlockProgress();\n    pendingBlockProgress.isFinished = true;\n    await this.blockProgressService.addPendingBlockProgress(pendingBlockProgress);\n  }\n\n  private generateDefaultPendingBlockProgress(): IPendingBlockProgress {\n    const { accessCodeId, block, courseProgress, pendingBlockProgress } = this;\n    assertNotNullOrUndefined(block, 'can\\'t generateDefaultPendingBlockProgress without block');\n    assertNotNullOrUndefined(accessCodeId, 'can\\'t generateDefaultPendingBlockProgress without accessCodeId');\n    assertNotNullOrUndefined(courseProgress, 'can\\'t generateDefaultPendingBlockProgress without courseProgress');\n    assertNotNullOrUndefined(pendingBlockProgress, 'can\\'t generateDefaultPendingBlockProgress without pendingBlockProgresses');\n\n    return PendingBlockProgressUtils.generateDefaultPendingBlockProgress(\n      accessCodeId,\n      block,\n      courseProgress,\n      pendingBlockProgress,\n    );\n  }\n}","import { FrameworkConfiguration, PLATFORM } from 'aurelia-framework';\n\nexport function configure(config: FrameworkConfiguration): void {\n  config.globalResources(PLATFORM.moduleName('common/aurelia-components/course-block/course-block-mp-flipping-card/course-block-mp-flipping-card'));\n  config.globalResources(PLATFORM.moduleName('common/aurelia-components/course-block/course-block-mp-flipping-card/mp-flipping-card-page/mp-flipping-card-page'));\n}\n","import { PLATFORM, autoinject, bindable, useView } from 'aurelia-framework';\nimport { IMpFlippingCardPageDto } from 'common/dto/content/methodpool/flipping.card.page.interface';\n\n@autoinject()\n@useView(PLATFORM.moduleName('common/aurelia-components/course-block/course-block-mp-flipping-card/mp-flipping-card-page/mp-flipping-card-page.html'))\nexport class MpFlippingCardPage {\n  @bindable() public page: IMpFlippingCardPageDto | null = null;\n  @bindable() public animating: boolean = false;\n}","import { PLATFORM, autoinject, bindable, computedFrom, useView } from 'aurelia-framework';\nimport { SiteThemer, ThemeColorType } from 'common/aurelia-classes/SiteThemer';\nimport { Alignment } from 'common/aurelia-components/aspect-ratio-container/aspect-ratio-container';\nimport { getHotSpotStyle } from 'common/aurelia-components/hot-spot-button/hot-spot-button';\nimport { IPaginationDisplay } from 'common/aurelia-components/mp-hot-spot-drawer/mp-hot-spot-drawer';\nimport { selectState } from 'common/aurelia-decorators/select-state';\nimport { StoreService } from 'common/aurelia-services/StoreService';\nimport { SubscriptionManager, SubscriptionManagerService } from 'common/aurelia-services/SubscriptionManagerService';\nimport { WINDOW_BREAK_POINT_MOBILE } from 'common/constants/course.constants';\nimport { IMpHotSpotDto } from 'common/dto/content/methodpool/hot.spot.interface';\nimport { ICourseBlockDto } from 'common/dto/course/content/block.interface';\nimport { BlockType } from 'common/dto/course/content/course.types';\nimport { IBlockProgressReturnDto, ICourseProgress, IPendingBlockProgress } from 'common/dto/course/progress/course.progress.interface';\nimport * as OtherMutations from 'common/store/mutations/mutations';\nimport { blockByIdSelectorCreator } from 'common/store/selectors/content.context.selectors';\nimport { visitedHotSpotIdsByBlockIdSelectorCreator } from 'common/store/selectors/user.course.interaction.selectors';\nimport { ICommonState } from 'common/store/store.interface';\nimport { ArrayUtils } from 'common/utils/ArrayUtils';\nimport { FunctionUtils } from 'common/utils/FunctionUtils';\nimport { PendingBlockProgressUtils } from 'common/utils/PendingBlockProgressUtils';\nimport { assertNotNullOrUndefined } from 'common/utils/assert';\nimport { BlockProgressService } from '../course-block';\n\n@autoinject()\n@useView(PLATFORM.moduleName('common/aurelia-components/course-block/course-block-mp-hot-spot/course-block-mp-hot-spot.html'))\nexport class CourseBlockMpHotSpot {\n  @bindable() public contextIdentifier: string | null = null;\n  @bindable() public blockId: number | null = null;\n  @bindable() public isNonInteractivePreview = true;\n  @bindable() public navPreview = false;\n\n  @selectState(CourseBlockMpHotSpot, blockByIdSelectorCreator, ['contextIdentifier', 'blockId']) public block?: ICourseBlockDto | null;\n  @selectState(CourseBlockMpHotSpot, visitedHotSpotIdsByBlockIdSelectorCreator, ['contextIdentifier', 'blockId']) public visitedHotSpotIds?: number[] = [];\n\n  private isMobile: boolean = true;\n\n  private hotSpots: IMpHotSpotDto[] = [];\n  private selectedHotSpot: IMpHotSpotDto | null = null;\n\n  private accessCodeId: number | null = null;\n  private blockProgress: IBlockProgressReturnDto[] | null = null;\n  private pendingBlockProgress: IPendingBlockProgress[] | null = null;\n  private courseProgress: ICourseProgress | null = null;\n\n  private selectedHotSpotButtonDomElement: HTMLElement | null = null;\n  private hotSpotWrapperDomElement: HTMLElement | null = null;\n\n  private BlockType = BlockType;\n  private Alignment = Alignment;\n  private domElement: HTMLElement;\n  private subscriptionManager: SubscriptionManager;\n\n  constructor(\n    private blockProgressService: BlockProgressService,\n    private storeService: StoreService<ICommonState>,\n    private siteThemer: SiteThemer,\n    element: Element,\n    subscriptionManagerService: SubscriptionManagerService,\n  ) {\n    this.domElement = element as HTMLElement;\n    this.subscriptionManager = subscriptionManagerService.createSubscriptionManager();\n  }\n\n  protected attached(): void {\n    this.subscribeToProgress();\n    this.handleResize();\n    this.subscriptionManager.subscribeToResize(() => this.handleResize());\n  }\n\n  protected detached(): void {\n    this.subscriptionManager.disposeSubscriptions();\n    this.blockProgressService.disposeSubscriptions(this);\n  }\n\n  private handleResize(): void {\n    this.isMobile = window.innerWidth < WINDOW_BREAK_POINT_MOBILE;\n    if(!this.selectedHotSpot) return;\n    this.scrollToHotSpot(this.selectedHotSpot);\n  }\n\n  private blockChanged(): void {\n    this.hotSpots = ArrayUtils.immutableSort(\n      this.block?.hotSpots ?? [],\n      (a, b) => a.order - b.order,\n    );\n  }\n\n  private selectNextHotSpot(): void {\n    const unvisitedHotSpots = this.hotSpots.filter(hS => !this.visitedHotSpotIds?.includes(hS.id));\n    const nextHotSpot = unvisitedHotSpots.length\n      ? unvisitedHotSpots[0]\n      : this.hotSpots.find(hS => hS.order === (this.selectedHotSpot?.order ?? 0) + 1);\n    if(nextHotSpot == null) return;\n\n    this.selectHotSpot(nextHotSpot);\n  }\n\n  private selectPreviousHotSpot(): void {\n    assertNotNullOrUndefined(this.selectedHotSpot, 'selectedHotSpot cannot be null or undefined');\n    const { order } = this.selectedHotSpot;\n    const previousHotSpot = this.hotSpots.find(hS => hS.order === order - 1);\n    if(previousHotSpot == null) return;\n\n    this.selectHotSpot(previousHotSpot);\n  }\n\n  private async selectHotSpot(hotSpot: IMpHotSpotDto): Promise<void> {\n    assertNotNullOrUndefined(this.block, 'block cannot be null or undefined');\n    assertNotNullOrUndefined(this.contextIdentifier, 'contextIdentifier cannot be null or undefined');\n\n    this.scrollToHotSpot(hotSpot);\n    this.selectedHotSpot = hotSpot;\n    await this.storeService.dispatch(OtherMutations.updateVisitedHotSpotIdsByBlockId, this.block.id, hotSpot.id);\n\n    if(this.visitedHotSpotIds?.length === this.hotSpots.length) {\n      this.finishBlock();\n    }\n  }\n\n  private deselectHotSpot(): void {\n    this.selectedHotSpot = null;\n  }\n\n  private scrollToHotSpot(hotSpot: IMpHotSpotDto): void {\n    this.selectedHotSpotButtonDomElement = document.getElementById(`${hotSpot.id}-${hotSpot.order}`);\n    if(!this.isMobile) return;\n    FunctionUtils.timeout(100, () => {\n      this.selectedHotSpotButtonDomElement?.scrollIntoView({ behavior: 'smooth', block: 'center', inline: 'center' });\n    });\n  }\n\n  private getHotSpotStyle(hotSpot: IMpHotSpotDto): Record<string, string> {\n    const hotSpotColor = this.block?.stylePrimaryColor ?? this.siteThemer.getColor(ThemeColorType.Primary);\n    return getHotSpotStyle(hotSpot, hotSpotColor);\n  }\n\n  private getHotSpotVisitedState(hotSpot: IMpHotSpotDto, visitedHotSpotIds?: number[]): boolean {\n    return !!visitedHotSpotIds?.some(iD => iD === hotSpot.id);\n  }\n\n  @computedFrom('selectedHotSpot.id', 'hotSpots.length')\n  private get paginationDisplay(): IPaginationDisplay {\n    const currentIndex = this.hotSpots.findIndex(hS => hS.id === this.selectedHotSpot?.id) ?? 0;\n    return { current: currentIndex + 1, total: this.hotSpots.length };\n  }\n\n  private subscribeToProgress(): void {\n    this.blockProgressService.disposeSubscriptions(this);\n    if(!this.domElement.isConnected || this.contextIdentifier == null) return;\n\n    this.blockProgressService.subscribeToProgress(this, this.contextIdentifier, (accessCodeId, blockProgress, courseProgress, pendingBlockProgress) => {\n      this.accessCodeId = accessCodeId;\n      this.blockProgress = blockProgress;\n      this.courseProgress = courseProgress;\n      this.pendingBlockProgress = pendingBlockProgress;\n    });\n  }\n\n  private async finishBlock(): Promise<void> {\n    const pendingBlockProgress = this.generateDefaultPendingBlockProgress();\n    pendingBlockProgress.isFinished = true;\n    await this.blockProgressService.addPendingBlockProgress(pendingBlockProgress);\n  }\n\n  private generateDefaultPendingBlockProgress(): IPendingBlockProgress {\n    const { accessCodeId, block, courseProgress, pendingBlockProgress } = this;\n    assertNotNullOrUndefined(block, 'can\\'t generateDefaultPendingBlockProgress without block');\n    assertNotNullOrUndefined(accessCodeId, 'can\\'t generateDefaultPendingBlockProgress without accessCodeId');\n    assertNotNullOrUndefined(courseProgress, 'can\\'t generateDefaultPendingBlockProgress without courseProgress');\n    assertNotNullOrUndefined(pendingBlockProgress, 'can\\'t generateDefaultPendingBlockProgress without pendingBlockProgresses');\n\n    return PendingBlockProgressUtils.generateDefaultPendingBlockProgress(\n      accessCodeId,\n      block,\n      courseProgress,\n      pendingBlockProgress,\n    );\n  }\n}","import { autoinject, bindable, useView, PLATFORM, ObserverLocator, Parser, computedFrom } from 'aurelia-framework';\nimport { IMpRapidFireGameEntry } from 'common/aurelia-components/methodpool/mp-rapid-fire-game/mp-rapid-fire-game';\nimport { ICourseBlockDto } from 'common/dto/course/content/block.interface';\nimport { assertNotNullOrUndefined } from 'common/utils/assert';\nimport { MethodpoolUtils } from 'common/utils/MethodpoolUtils';\nimport { PendingBlockProgressUtils } from 'common/utils/PendingBlockProgressUtils';\nimport { selectState, SelectStateObservableComponent } from 'common/aurelia-decorators/select-state';\nimport { blockByIdSelectorCreator } from 'common/store/selectors/content.context.selectors';\nimport { ICourseProgress, IPendingBlockProgress } from 'common/dto/course/progress/course.progress.interface';\nimport { IMpRapidfireEntryDto } from 'common/dto/content/methodpool/rapidfire.interface';\nimport { rapidfireEntriesByCategorySelectorCreator } from 'common/store/selectors/course.context.selectors';\nimport { CourseLearnFooter } from 'common/aurelia-components/course-learn-footer/course-learn-footer';\nimport { BlockProgressService } from 'common/aurelia-components/course-block/course-block';\n\n@useView(PLATFORM.moduleName('common/aurelia-components/course-block/course-block-mp-rfg/course-block-mp-rfg.html'))\n@autoinject()\nexport class CourseBlockMpRfg extends SelectStateObservableComponent {\n  @bindable() public contextIdentifier: string | null = null;\n  @bindable() public blockId: number | null = null;\n  @bindable() public isNonInteractivePreview = true;\n  @bindable() public navPreview = false;\n\n  @selectState(CourseBlockMpRfg, blockByIdSelectorCreator, ['contextIdentifier', 'blockId']) public block?: ICourseBlockDto | null;\n  @selectState(CourseBlockMpRfg, rapidfireEntriesByCategorySelectorCreator, ['contextIdentifier']) public rapidfireEntriesByCategory?: {\n    [key: string]: Array<IMpRapidfireEntryDto>;\n  } | null;\n\n  private accessCodeId: number | null = null;\n  private pendingBlockProgress: Array<IPendingBlockProgress> | null = null;\n  private courseProgress: ICourseProgress | null = null;\n\n  private domElement: HTMLElement;\n\n  constructor(\n    element: Element,\n    parser: Parser,\n    observerLocator: ObserverLocator,\n    private blockProgressService: BlockProgressService,\n  ) {\n    super(parser, observerLocator);\n    this.domElement = element as HTMLElement;\n  }\n\n  protected attached(): void {\n    this.subscribeToProgress();\n  }\n\n  protected detached(): void {\n    this.blockProgressService.disposeSubscriptions(this);\n    CourseLearnFooter.resetConfiguration(this);\n  }\n\n  @computedFrom('block.text', 'rapidfireEntriesByCategory')\n  private get gameData(): Array<IMpRapidFireGameEntry> {\n    if(this.block?.text == null || this.rapidfireEntriesByCategory == null) return [];\n\n    const categories = MethodpoolUtils.parseRfgText(this.block.text);\n\n    return categories.reduce<Array<IMpRapidFireGameEntry>>((acc, category) => [\n      ...acc,\n      ...(this.rapidfireEntriesByCategory?.[category] ?? []).map(e => ({ category, text: e.text, picture: e.picture })),\n    ], []);\n  }\n\n  private subscribeToProgress(): void {\n    this.blockProgressService.disposeSubscriptions(this);\n    if(!this.domElement.isConnected || this.contextIdentifier == null) return;\n\n    this.blockProgressService.subscribeToProgress(this, this.contextIdentifier, (accessCodeId, _blockProgress, courseProgress, pendingBlockProgress) => {\n      this.accessCodeId = accessCodeId;\n      this.courseProgress = courseProgress;\n      this.pendingBlockProgress = pendingBlockProgress;\n    });\n  }\n\n  protected async finishBlock(): Promise<void> {\n    const pendingBlockProgress = this.generateDefaultPendingBlockProgress();\n    pendingBlockProgress.isFinished = true;\n    await this.blockProgressService.addPendingBlockProgress(pendingBlockProgress);\n  }\n\n  protected handleMpGameFinished(event: CustomEvent<any>): void {\n    this.finishBlock();\n    CourseLearnFooter.updateConfiguration(this, {\n      showOnMobile: true,\n      message: {\n        tk: 'common.course-block.course-block-mp-rfg.finished',\n        tkParams: { count: event.detail.correct },\n      },\n    });\n  }\n\n  protected handleMpGameStarted(): void {\n    try {\n      CourseLearnFooter.updateConfiguration(this, {\n        showOnMobile: false,\n        message: undefined,\n      });\n    } catch(e) {\n      CourseLearnFooter.configure(this, {});\n    }\n  }\n\n  private generateDefaultPendingBlockProgress(): IPendingBlockProgress {\n    const { accessCodeId, block, courseProgress, pendingBlockProgress } = this;\n    assertNotNullOrUndefined(block, 'can\\'t generateDefaultPendingBlockProgress without block');\n    assertNotNullOrUndefined(courseProgress, 'can\\'t generateDefaultPendingBlockProgress without courseProgressesByAccessCodeId');\n    assertNotNullOrUndefined(accessCodeId, 'can\\'t generateDefaultPendingBlockProgress without accessCodeId');\n    assertNotNullOrUndefined(courseProgress, 'can\\'t generateDefaultPendingBlockProgress without courseProgress');\n    assertNotNullOrUndefined(pendingBlockProgress, 'can\\'t generateDefaultPendingBlockProgress without pendingBlockProgresses');\n\n    return PendingBlockProgressUtils.generateDefaultPendingBlockProgress(\n      accessCodeId,\n      block,\n      courseProgress,\n      pendingBlockProgress,\n    );\n  }\n}\n","import { autoinject, bindable, observable, PLATFORM, useView } from 'aurelia-framework';\nimport completedAnimationData from 'common/animations/party-popper.json';\nimport { FocusTrap } from 'common/aurelia-classes/FocusTrap';\nimport { FullScreenContainer } from 'common/aurelia-components/full-screen-container/full-screen-container';\nimport { RisiLevelFinishedEvent } from 'common/aurelia-components/risi-game/risi-game';\nimport { selectState } from 'common/aurelia-decorators/select-state';\nimport { SubscriptionManager, SubscriptionManagerService } from 'common/aurelia-services/SubscriptionManagerService';\nimport { ICourseBlockDto } from 'common/dto/course/content/block.interface';\nimport { IBlockProgressReturnDto, ICourseProgress, IPendingBlockProgress } from 'common/dto/course/progress/course.progress.interface';\nimport { levels } from 'common/risi-game/RisiLevels';\nimport { blockByIdSelectorCreator } from 'common/store/selectors/content.context.selectors';\nimport { assertNotNullOrUndefined } from 'common/utils/assert';\nimport { PendingBlockProgressUtils } from 'common/utils/PendingBlockProgressUtils';\nimport { BlockProgressService } from '../course-block';\n\n@autoinject()\n@useView(PLATFORM.moduleName('common/aurelia-components/course-block/course-block-mp-risi/course-block-mp-risi.html'))\nexport class CourseBlockMpRisi {\n  @bindable() public contextIdentifier: string | null = null;\n  @bindable() public blockId: number | null = null;\n  @bindable() public isNonInteractivePreview = true;\n  @bindable() public navPreview = false;\n\n  @selectState(CourseBlockMpRisi, blockByIdSelectorCreator, ['contextIdentifier', 'blockId']) public block?: ICourseBlockDto | null;\n\n  private element: HTMLElement;\n  private desktopFullScreenContainer: FullScreenContainer | null = null;\n  private focusTrap: FocusTrap | null = null;\n  private gameOverlay: HTMLElement | null = null;\n  @observable() private gameContainer: HTMLElement | null = null;\n  private gameContainerSubscriptionManager: SubscriptionManager;\n\n  private accessCodeId: number | null = null;\n  private currentLevel: keyof typeof levels | null = null;\n\n  private blockProgress: IBlockProgressReturnDto[] | null = null;\n  private pendingBlockProgress: IPendingBlockProgress[] | null = null;\n  private courseProgress: ICourseProgress | null = null;\n\n\n  private showDesktopOverlay = true;\n  private gameStyle: { 'max-width': string; width: string } | null = null;\n  private completedAnimationData = completedAnimationData;\n  private completed = false;\n\n  constructor(\n    private blockProgressService: BlockProgressService,\n    private subscriptionManagerService: SubscriptionManagerService,\n    element: Element,\n  ) {\n    this.gameContainerSubscriptionManager = subscriptionManagerService.createSubscriptionManager();\n    this.element = element as HTMLElement;\n  }\n\n  protected attached(): void {\n    this.subscribeToProgress();\n  }\n\n  protected detached(): void {\n    this.blockProgressService.disposeSubscriptions(this);\n    this.gameContainerSubscriptionManager.disposeSubscriptions();\n  }\n\n  private gameContainerChanged(newRef: HTMLElement, oldRef: HTMLElement): void {\n    if(oldRef) this.gameContainerSubscriptionManager.disposeSubscriptions();\n\n    if(newRef) {\n      this.gameContainerSubscriptionManager.subscribeToElementResize(newRef, this.updateGameStyle.bind(this));\n      this.updateGameStyle();\n    }\n  }\n\n  private blockChanged(): void {\n    const blockText = this.block?.text;\n    this.currentLevel = blockText && blockText in levels ? blockText : null;\n    this.updateGameStyle();\n  }\n\n  protected handleLevelFinished(event: RisiLevelFinishedEvent): void {\n    const { success } = event.detail;\n    if(success) {\n      this.completed = true;\n      this.finishBlock();\n    }\n    this.closeLevel();\n  }\n\n  private updateGameStyle(): void {\n    if(!this.currentLevel || !this.gameContainer?.isConnected) return;\n\n    const contentRect = this.gameContainer.getBoundingClientRect();\n    const currentLevelColumns = (this.currentLevel != null && levels[this.currentLevel]?.columns) || 1;\n    // HAX: there is a better way to account for the controls this but it would require refactoring, which is out of budget\n    const controlsHeight = 90;\n    this.gameStyle = {\n      'max-width': `${Math.floor(((contentRect.height - controlsHeight) * 0.56) / currentLevelColumns) * currentLevelColumns}px`,\n      width: `${Math.floor((contentRect.width) / currentLevelColumns) * currentLevelColumns}px`,\n    };\n  }\n\n  private subscribeToProgress(): void {\n    this.blockProgressService.disposeSubscriptions(this);\n    if(!this.element.isConnected || this.contextIdentifier == null) return;\n\n    this.blockProgressService.subscribeToProgress(this, this.contextIdentifier, (accessCodeId, blockProgress, courseProgress, pendingBlockProgress) => {\n      this.accessCodeId = accessCodeId;\n      this.blockProgress = blockProgress;\n      this.courseProgress = courseProgress;\n      this.pendingBlockProgress = pendingBlockProgress;\n    });\n  }\n\n  private async finishBlock(): Promise<void> {\n    const pendingBlockProgress = this.generateDefaultPendingBlockProgress();\n    pendingBlockProgress.isFinished = true;\n    await this.blockProgressService.addPendingBlockProgress(pendingBlockProgress);\n  }\n\n  private generateDefaultPendingBlockProgress(): IPendingBlockProgress {\n    const { accessCodeId, block, courseProgress, pendingBlockProgress } = this;\n    assertNotNullOrUndefined(block, 'can\\'t generateDefaultPendingBlockProgress without block');\n    assertNotNullOrUndefined(accessCodeId, 'can\\'t generateDefaultPendingBlockProgress without accessCodeId');\n    assertNotNullOrUndefined(courseProgress, 'can\\'t generateDefaultPendingBlockProgress without courseProgress');\n    assertNotNullOrUndefined(pendingBlockProgress, 'can\\'t generateDefaultPendingBlockProgress without pendingBlockProgresses');\n\n    return PendingBlockProgressUtils.generateDefaultPendingBlockProgress(\n      accessCodeId,\n      block,\n      courseProgress,\n      pendingBlockProgress,\n    );\n  }\n\n  private startLevel(): void {\n    this.showDesktopOverlay = true;\n    setTimeout(() => {\n      this.getOrCreateFocusTrap()?.setup();\n      this.desktopFullScreenContainer?.show();\n    }, 0);\n  }\n\n  private closeLevel(): void {\n    this.desktopFullScreenContainer?.hide();\n    this.getOrCreateFocusTrap()?.destroy();\n    this.showDesktopOverlay = false;\n  }\n\n  private getOrCreateFocusTrap(): FocusTrap | null {\n    if(this.gameOverlay && !this.focusTrap) {\n      this.focusTrap = new FocusTrap(this.gameOverlay, this.subscriptionManagerService);\n    }\n    return this.focusTrap;\n  }\n}","import { autoinject, bindable, PLATFORM, useView } from 'aurelia-framework';\r\nimport { selectState } from 'common/aurelia-decorators/select-state';\r\nimport { SubscriptionManager, SubscriptionManagerService } from 'common/aurelia-services/SubscriptionManagerService';\r\nimport { ICourseBlockDto } from 'common/dto/course/content/block.interface';\r\nimport { ICourseProgress, IPendingBlockProgress } from 'common/dto/course/progress/course.progress.interface';\r\nimport { Config } from 'common/fe-config/config';\r\nimport { blockByIdSelectorCreator } from 'common/store/selectors/content.context.selectors';\r\nimport { assertNotNullOrUndefined } from 'common/utils/assert';\r\nimport { PendingBlockProgressUtils } from 'common/utils/PendingBlockProgressUtils';\r\nimport { BlockProgressService } from '../course-block';\r\n\r\n@useView(PLATFORM.moduleName('common/aurelia-components/course-block/course-block-mp-scorm/course-block-mp-scorm.html'))\r\n@autoinject()\r\nexport class CourseBlockMpScorm {\r\n  @bindable() public contextIdentifier: string | null = null;\r\n  @bindable() public blockId: number | null = null;\r\n\r\n  @selectState(CourseBlockMpScorm, blockByIdSelectorCreator, ['contextIdentifier', 'blockId']) public block?: ICourseBlockDto | null;\r\n\r\n  private accessCodeId: number | null = null;\r\n  private pendingBlockProgress: Array<IPendingBlockProgress> | null = null;\r\n  private courseProgress: ICourseProgress | null = null;\r\n\r\n  private domElement: HTMLElement;\r\n  private subscriptionManager: SubscriptionManager;\r\n\r\n  private showScorm = false;\r\n\r\n  constructor(\r\n    private blockProgressService: BlockProgressService,\r\n    subscriptionManagerService: SubscriptionManagerService,\r\n    element: Element,\r\n  ) {\r\n    this.domElement = element as HTMLElement;\r\n    this.subscriptionManager = subscriptionManagerService.createSubscriptionManager();\r\n  }\r\n\r\n  protected attached(): void {\r\n    this.subscriptionManager.subscribeToWindowEvent('message', event => this.handleScormEvent(event));\r\n    this.subscribeToProgress();\r\n  }\r\n\r\n  public openScorm(): void {\r\n    this.showScorm = true;\r\n  }\r\n\r\n  public closeScorm(): void {\r\n    this.showScorm = false;\r\n  }\r\n\r\n  protected detached(): void {\r\n    this.subscriptionManager.disposeSubscriptions();\r\n    this.blockProgressService.disposeSubscriptions(this);\r\n  }\r\n\r\n  protected handleScormEvent(event: MessageEvent): void {\r\n    const url = new URL(Config().scorm.playerUrl);\r\n    if(url.origin !== event.origin) {\r\n      return;\r\n    }\r\n    if(event.data.type === 'scorm') {\r\n      if(event.data.message === 'passed') {\r\n        this.finishBlock();\r\n      }\r\n      if(event.data.message === 'finished') {\r\n        this.closeScorm();\r\n      }\r\n    }\r\n  }\r\n\r\n  private finishBlock(): void {\r\n    const pendingBlockProgress = this.generateDefaultPendingBlockProgress();\r\n    pendingBlockProgress.isFinished = true;\r\n    this.blockProgressService.addPendingBlockProgress(pendingBlockProgress);\r\n  }\r\n\r\n  private generateDefaultPendingBlockProgress(): IPendingBlockProgress {\r\n    const { accessCodeId, block, courseProgress, pendingBlockProgress } = this;\r\n    assertNotNullOrUndefined(block, 'can\\'t generateDefaultPendingBlockProgress without block');\r\n    assertNotNullOrUndefined(accessCodeId, 'can\\'t generateDefaultPendingBlockProgress without accessCodeId');\r\n    assertNotNullOrUndefined(courseProgress, 'can\\'t generateDefaultPendingBlockProgress without courseProgress');\r\n    assertNotNullOrUndefined(pendingBlockProgress, 'can\\'t generateDefaultPendingBlockProgress without pendingBlockProgresses');\r\n\r\n    return PendingBlockProgressUtils.generateDefaultPendingBlockProgress(\r\n      accessCodeId,\r\n      block,\r\n      courseProgress,\r\n      pendingBlockProgress,\r\n    );\r\n  }\r\n\r\n  private subscribeToProgress(): void {\r\n    this.blockProgressService.disposeSubscriptions(this);\r\n    if(!this.domElement.isConnected || this.contextIdentifier == null) return;\r\n\r\n    this.blockProgressService.subscribeToProgress(this, this.contextIdentifier, (accessCodeId, blockProgress, courseProgress, pendingBlockProgress) => {\r\n      this.accessCodeId = accessCodeId;\r\n      this.courseProgress = courseProgress;\r\n      this.pendingBlockProgress = pendingBlockProgress;\r\n    });\r\n  }\r\n}\r\n","import { autoinject, bindable, useView, PLATFORM } from 'aurelia-framework';\nimport { DomUtils } from 'common/utils/DomUtils';\n\n/**\n * @event { ButtonClickedEvent } button-clicked\n * @event { CloseButtonClickedEvent } close-button-clicked\n */\n@useView(PLATFORM.moduleName('common/aurelia-components/course-block/course-block-mp-sequence/course-block-mp-sequence-sidebar.html'))\n@autoinject()\nexport class CourseBlockMpSequenceSidebar {\n  @bindable() public disabled = false;\n  @bindable() public showCloseButton = false;\n  @bindable() public showNextButton = true;\n  @bindable() public audioUrl: string | null = null;\n\n  private element: HTMLElement;\n\n  constructor(element: Element) {\n    this.element = element as HTMLElement;\n  }\n\n  private handleButtonClicked(): void {\n    DomUtils.fireEvent(this.element, {\n      name: 'button-clicked',\n      bubbles: false,\n    });\n  }\n\n  private handleCloseButtonClicked(): void {\n    DomUtils.fireEvent(this.element, {\n      name: 'close-button-clicked',\n      bubbles: false,\n    });\n  }\n}\n\nexport type ButtonClickedEvent = CustomEvent<void>;\n","import {\n  autoinject,\n  bindable,\n  computedFrom,\n  observable,\n  PLATFORM,\n  useView,\n} from 'aurelia-framework';\nimport { PlayState, PlayStateChangedEvent } from 'common/aurelia-components/audio-player/audio-player';\nimport { DualVideoPlayer } from 'common/aurelia-components/dual-video-player/dual-video-player';\nimport { IMpGameFinishedEventDetail } from 'common/aurelia-components/methodpool/mp-interactive-text/mp-interactive-text';\nimport { MpQuestion, MpQuestionAnsweredEvent } from 'common/aurelia-components/methodpool/mp-question/mp-question';\nimport { VideoPlayer } from 'common/aurelia-components/video-player/video-player';\nimport { ICourseBlockDto } from 'common/dto/course/content/block.interface';\nimport { ICourseContentDto } from 'common/dto/course/content/course.interface';\nimport { BlockType } from 'common/dto/course/content/course.types';\nimport { IQuizQuestionDto } from 'common/dto/course/quiz/quiz.question.interface';\nimport { SlideRowStyle } from 'common/enum/slide.row.sytle.enum';\nimport { MarkdownJumpSlideLinkClickEvent } from 'common/markdown/ShowdownJumpSlideLinkExtensions';\nimport { assertNotNullOrUndefined } from 'common/utils/assert';\nimport { BlockUtils } from 'common/utils/BlockUtils';\nimport { CssUtils } from 'common/utils/CssUtils';\nimport { DomUtils } from 'common/utils/DomUtils';\nimport { QuestionUtils } from 'common/utils/QuestionUtils';\n\nexport interface ICourseBlockMpSequenceSlideControlsChangedEventData {\n  buttonCallback: (() => void) | null;\n  buttonEnabled: boolean;\n  slideId: number;\n}\n\n@useView(PLATFORM.moduleName('common/aurelia-components/course-block/course-block-mp-sequence/course-block-mp-sequence-slide.html'))\n@autoinject()\nexport class CourseBlockMpSequenceSlide {\n  @bindable() public contextIdentifier: string | null = null;\n  @bindable() public slideId: number | null = null;\n  @bindable() public active = false;\n  @bindable() public showPreviousSlideButton = false;\n  @bindable() public isDesktopVersion = false;\n  @bindable() public courseContent: ICourseContentDto | null = null;\n\n  private slideIdChanged(): void {\n    this.update();\n  }\n\n  private courseContentChanged(): void {\n    this.update();\n  }\n\n  private update(): void {\n    this.currentVideoBlockEnded = false;\n\n    this.currentBlock = this.slideId != null\n      ? this.getBlockBySlideId(this.slideId)\n      : null;\n\n    this.currentQuestion = this.getBlockQuizQuestion(this.currentBlock, (block: ICourseBlockDto) => {\n      console.error(`No Question found for MP-QUESTION block ${block.id}. Block skipped.`);\n      this.goToNextSlide();\n    });\n  }\n\n  private getBlockBySlideId(slideId: number): ICourseBlockDto | null {\n    return this.courseContent?.blocks.find(block => block.iasSlideId === slideId) ?? null;\n  }\n\n  private getBlockQuizQuestion(block: ICourseBlockDto | null, errorCallback?: (block: ICourseBlockDto) => void): IQuizQuestionDto | null {\n    if(block?.type === BlockType.MP_QUESTION) {\n      const question = this.courseContent?.questions.find(q => q.id === block.questionIds[0]) ?? null;\n      if(!question) {\n        errorCallback?.(block);\n        return null;\n      }\n      return QuestionUtils.quizQuestionFromQuestion(question, false, null);\n    }\n\n    return null;\n  }\n\n  private SlideRowStyle = SlideRowStyle;\n\n  private BlockType = BlockType;\n\n  private currentBlock: ICourseBlockDto | null = null;\n  private currentQuestion: IQuizQuestionDto | null = null;\n\n  private showVideoHelpText = false;\n\n  private videoPlayer?: VideoPlayer | DualVideoPlayer;\n  private currentVideoBlockEnded = false;\n  private videoThumbnailSrc: string | null = null;\n  private videoThumbnailSrc2: string | null = null;\n\n  private mpQuestionElement: MpQuestion | null = null;\n\n  @observable() private mpQuestionIsAnswerSelected = false;\n\n  constructor(\n    private domElement: Element,\n  ) { }\n\n  protected attached(): void {\n    this.fixSafariVideoStyle();\n    if(this.active) {\n      this.sendControlsChangedEvent();\n    }\n  }\n\n  protected detached(): void {\n    this.showPreviousSlideButton = false;\n  }\n\n  private activeChanged(): void {\n    if(!this.domElement.isConnected) return;\n\n    this.currentVideoBlockEnded = false;\n    this.showVideoHelpText = false;\n\n    this.fixSafariVideoStyle();\n\n    if(this.active) {\n      this.videoPlayer?.rewind();\n      this.videoPlayer?.play({ preventAutoplayWithoutSound: true });\n    } else {\n      this.videoPlayer?.rewind();\n      this.videoPlayer?.pause();\n    }\n\n    if(this.active) {\n      this.sendControlsChangedEvent();\n    }\n  }\n\n  @computedFrom('currentBlock.successSlideId', 'currentBlock.slideId', 'currentBlock.type')\n  private get showNextButton(): boolean {\n    if(!this.currentBlock || ![BlockType.TEXT, BlockType.AUDIO, BlockType.PICTURE].includes(this.currentBlock.type)) return false;\n    return this.currentBlock.successSlideId !== this.currentBlock.iasSlideId;\n  }\n\n  @computedFrom('currentBlock.pipVideoUrl', 'currentBlock.pipVideoSources')\n  private get isDualVideo(): boolean {\n    return !!this.currentBlock && BlockUtils.isDualVideoBlock(this.currentBlock);\n  }\n\n  private sendControlsChangedEvent(): void {\n    assertNotNullOrUndefined(this.slideId, 'Cannot send slide controls changed event without slideId');\n    let buttonCallback = null;\n    let buttonEnabled = true;\n    switch(this.currentBlock?.type) {\n      case BlockType.TEXT:\n      case BlockType.AUDIO:\n      case BlockType.PICTURE:\n        // This callback will not be updated on a showTextBlockNextButton change\n        // but this does not matter as the block does not change after the slide has been set to active\n        buttonCallback = this.showNextButton ? () => { this.goToNextSlide(); } : null;\n        break;\n      case BlockType.MP_QUESTION:\n        buttonCallback = () => { this.mpQuestionElement?.evaluate(); };\n        buttonEnabled = this.mpQuestionIsAnswerSelected;\n        break;\n      default:\n    }\n    DomUtils.fireEvent<ICourseBlockMpSequenceSlideControlsChangedEventData>(this.domElement, {\n      name: 'slide-controls-changed',\n      bubbles: false,\n      detail: {\n        buttonCallback,\n        buttonEnabled,\n        slideId: this.slideId,\n      },\n    });\n  }\n\n  private fixSafariVideoStyle(): void {\n    if(this.active) {\n      if(this.currentBlock?.type === BlockType.VIDEO) {\n        document.documentElement.style.setProperty('--course-block-mp-sequence-video-corner-fix-gradient', '-webkit-radial-gradient(white, black)');\n      } else {\n        document.documentElement.style.removeProperty('--course-block-mp-sequence-video-corner-fix-gradient');\n      }\n    }\n  }\n\n  private goToNextSlide(): void {\n    this.goToSuccessSlide();\n  }\n\n  private goToSuccessSlide(): void {\n    if(!this.currentBlock) return;\n    const nextSlideId = this.currentBlock.successSlideId;\n    this.goToSlide(nextSlideId);\n  }\n\n  private goToErrorSlide(): void {\n    if(!this.currentBlock) return;\n    const nextSlideId = this.currentBlock.errorSlideId;\n    if(!nextSlideId) {\n      console.error('No Error Slide found!');\n      this.goToSlide(null);\n      return;\n    }\n    this.goToSlide(nextSlideId);\n  }\n\n  private goToPreviousSlide(): void {\n    DomUtils.fireEvent(this.domElement, {\n      name: 'go-to-previous-slide',\n      bubbles: false,\n    });\n  }\n\n  @computedFrom('active', 'currentVideoBlockEnded')\n  private get videoEnded(): boolean {\n    return this.active && this.currentVideoBlockEnded;\n  }\n\n  @computedFrom('videoThumbnailSrc')\n  private get replayVideoOverlayStyle(): { [key: string]: string } {\n    if(this.videoThumbnailSrc == null) return {};\n\n    return {\n      'background-image': CssUtils.imageUrl(this.videoThumbnailSrc),\n    };\n  }\n\n  @computedFrom('videoThumbnailSrc2')\n  private get replayPipVideoOverlayStyle(): { [key: string]: string } {\n    if(this.videoThumbnailSrc2 == null) return {};\n\n    return {\n      'background-image': CssUtils.imageUrl(this.videoThumbnailSrc2),\n    };\n  }\n\n  private goToSlide(nextSlideId: number | null): void {\n    DomUtils.fireEvent(this.domElement, {\n      name: 'go-to-slide',\n      detail: {\n        slideId: nextSlideId,\n      },\n      bubbles: false,\n    });\n  }\n\n  private replayVideo(): void {\n    this.currentVideoBlockEnded = false;\n    this.videoPlayer?.startFromTime({ time: 0, preventAutoplayWithoutSound: true });\n  }\n\n  private toggleShowVideoHelpText(): void {\n    this.showVideoHelpText = !this.showVideoHelpText;\n  }\n\n  private handlePlayStateChanged(event: PlayStateChangedEvent): void {\n    if(event.detail.playState === PlayState.Start && !this.active) {\n      this.videoPlayer?.rewind();\n      this.videoPlayer?.pause();\n    }\n    if(event.detail.playState === PlayState.Ended && this.active) {\n      this.videoPlayer?.exitFullscreen();\n      if(this.currentBlock?.errorSlideId) {\n        this.goToSlide(this.currentBlock?.errorSlideId);\n      } else {\n        this.currentVideoBlockEnded = true;\n      }\n    }\n  }\n\n  private handleMpGameFinished(event: CustomEvent<IMpGameFinishedEventDetail>): void {\n    if(event.detail.allCorrect) {\n      this.goToNextSlide();\n    } else {\n      this.goToErrorSlide();\n    }\n  }\n\n  protected handleJumpSlideLinkClick(event: MarkdownJumpSlideLinkClickEvent): void {\n    if(!this.currentBlock) return;\n    const {\n      jumpSlide1Id, jumpSlide2Id, jumpSlide3Id, jumpSlide4Id,\n    } = this.currentBlock;\n    switch(event.detail.jumpSlideOrdinal) {\n      case 1: this.goToSlide(jumpSlide1Id ?? null); break;\n      case 2: this.goToSlide(jumpSlide2Id ?? null); break;\n      case 3: this.goToSlide(jumpSlide3Id ?? null); break;\n      case 4: this.goToSlide(jumpSlide4Id ?? null); break;\n      default:\n    }\n  }\n\n  /**\n   * if a single answer is given and a jump slide is available, go there\n   * otherwise fall back to the success/error slide\n   */\n  private handleMpQuestionAnswered(event: MpQuestionAnsweredEvent): void {\n    const { answers, correct } = event.detail;\n    const {\n      jumpSlide1Id, jumpSlide2Id, jumpSlide3Id, jumpSlide4Id,\n    } = this.currentBlock ?? {};\n\n    const singleAnswer = answers.length === 1 ? answers[0] : null;\n    if(singleAnswer === 1 && jumpSlide1Id != null) this.goToSlide(jumpSlide1Id);\n    else if(singleAnswer === 2 && jumpSlide2Id != null) this.goToSlide(jumpSlide2Id);\n    else if(singleAnswer === 3 && jumpSlide3Id != null) this.goToSlide(jumpSlide3Id);\n    else if(singleAnswer === 4 && jumpSlide4Id != null) this.goToSlide(jumpSlide4Id);\n\n    else if(correct) this.goToSuccessSlide();\n\n    else this.goToErrorSlide();\n  }\n\n  private mpQuestionIsAnswerSelectedChanged(): void {\n    if(this.active) {\n      this.sendControlsChangedEvent();\n    }\n  }\n}\n","import { autoinject, bindable, computedFrom, ObserverLocator, Parser, PLATFORM, useView } from 'aurelia-framework';\nimport iasDesktopStartAnimationData from 'common/animations/ias-desktop-start.json';\nimport rotateDeviceAnimationData from 'common/animations/landscape.json';\nimport completedAnimationData from 'common/animations/party-popper.json';\nimport { FocusTrap } from 'common/aurelia-classes/FocusTrap';\nimport { BlockProgressService } from 'common/aurelia-components/course-block/course-block';\nimport { ICourseBlockMpSequenceSlideControlsChangedEventData } from 'common/aurelia-components/course-block/course-block-mp-sequence/course-block-mp-sequence-slide';\nimport { FullScreenContainer } from 'common/aurelia-components/full-screen-container/full-screen-container';\nimport { selectState, SelectStateObservableComponent } from 'common/aurelia-decorators/select-state';\nimport { OrientationQueryResult, SubscriptionManager, SubscriptionManagerService } from 'common/aurelia-services/SubscriptionManagerService';\nimport { ICourseBlockDto } from 'common/dto/course/content/block.interface';\nimport { ICourseContentDto } from 'common/dto/course/content/course.interface';\nimport { BlockType } from 'common/dto/course/content/course.types';\nimport { ICourseProgress, IPendingBlockProgress } from 'common/dto/course/progress/course.progress.interface';\nimport { blockByIdSelectorCreator } from 'common/store/selectors/content.context.selectors';\nimport { courseContentSelectorCreator, iasSlideIdsOrderedSelectorCreator } from 'common/store/selectors/course.context.selectors';\nimport { assertNotNullOrUndefined } from 'common/utils/assert';\nimport { PendingBlockProgressUtils } from 'common/utils/PendingBlockProgressUtils';\n\n@useView(PLATFORM.moduleName('common/aurelia-components/course-block/course-block-mp-sequence/course-block-mp-sequence.html'))\n@autoinject()\nexport class CourseBlockMpSequence extends SelectStateObservableComponent {\n  @bindable() public contextIdentifier: string | null = null;\n  @bindable() public blockId: number | null = null;\n  @bindable() public navPreview = false;\n\n  @selectState(CourseBlockMpSequence, blockByIdSelectorCreator, ['contextIdentifier', 'blockId']) public block?: ICourseBlockDto | null;\n  @selectState(CourseBlockMpSequence, iasSlideIdsOrderedSelectorCreator, ['contextIdentifier']) public iasSlideIds?: number[];\n  @selectState(CourseBlockMpSequence, courseContentSelectorCreator, ['contextIdentifier']) private courseContent?: ICourseContentDto | null;\n\n  private BlockType = BlockType;\n\n  private accessCodeId: number | null = null;\n  private pendingBlockProgress: Array<IPendingBlockProgress> | null = null;\n  private courseProgress: ICourseProgress | null = null;\n\n  private rotateDeviceAnimationData = rotateDeviceAnimationData;\n  private completedAnimationData = completedAnimationData;\n  private iasDesktopStartAnimationData = iasDesktopStartAnimationData;\n\n  private showDesktopOverlay = false;\n  private desktopContentReadyToRender = false;\n\n  private fullScreenContainer: FullScreenContainer | null = null;\n  private desktopFullScreenContainer: FullScreenContainer | null = null;\n  private focusTrap: FocusTrap | null = null;\n\n  private currentSlideId: number | null = null;\n  private currentBlock: ICourseBlockDto | null = null;\n  private firstSlideId: number | null = null;\n\n  private subscriptionManager: SubscriptionManager;\n\n  private showInline = false;\n\n  private completed = false;\n\n  private slideHistory: Array<number> = [];\n\n  private nextButtonCallback: (() => void) | null = null;\n  private nextButtonEnabled = false;\n\n  private domElement: HTMLElement;\n  private desktopFullscreenContentElement: HTMLElement | null = null;\n\n  constructor(\n    element: Element,\n    parser: Parser,\n    observerLocator: ObserverLocator,\n    private subscriptionManagerService: SubscriptionManagerService,\n    private blockProgressService: BlockProgressService,\n  ) {\n    super(parser, observerLocator);\n\n    this.domElement = element as HTMLElement;\n    this.subscriptionManager = subscriptionManagerService.createSubscriptionManager();\n  }\n\n  protected attached(): void {\n    this.onResizeChanged();\n    this.subscriptionManager.subscribeToOrientationChange(this.onOrientationChanged.bind(this));\n    this.subscriptionManager.subscribeToResize(this.onResizeChanged.bind(this));\n\n    this.subscribeToProgress();\n  }\n\n  protected detached(): void {\n    this.blockProgressService.disposeSubscriptions(this);\n    this.subscriptionManager.disposeSubscriptions();\n  }\n\n  private contextIdentifierChanged(): void {\n    this.subscribeToProgress();\n  }\n\n  private iasSlideIdsChanged(): void {\n    this.initialize();\n  }\n\n  private blockChanged(): void {\n    this.initialize();\n  }\n\n  private courseContentChanged(): void {\n    this.initialize();\n  }\n\n  private initialize(): void {\n    if(!this.courseContent) return;\n    const firstSlideId = this.iasSlideIds?.[0];\n    if(firstSlideId == null) return;\n\n    this.firstSlideId = firstSlideId;\n    this.gotoSlide(this.firstSlideId);\n  }\n\n  private subscribeToProgress(): void {\n    this.blockProgressService.disposeSubscriptions(this);\n    if(!this.domElement.isConnected || this.contextIdentifier == null) return;\n\n    this.blockProgressService.subscribeToProgress(this, this.contextIdentifier, (accessCodeId, blockProgress, courseProgress, pendingBlockProgress) => {\n      this.accessCodeId = accessCodeId;\n      this.courseProgress = courseProgress;\n      this.pendingBlockProgress = pendingBlockProgress;\n    });\n  }\n\n  private onResizeChanged(): void {\n    const newShowInline = window.innerWidth > 480 && window.innerHeight > 480;\n    if(newShowInline === this.showInline) return;\n    if(newShowInline) {\n      this.hideFullScreen();\n    } else {\n      this.closeDesktopSequence();\n    }\n    this.showInline = newShowInline;\n  }\n\n  private navPreviewChanged(): void {\n    if(this.navPreview) return;\n    this.onOrientationChanged(window.screen.orientation.type.startsWith('landscape') ? 'landscape' : 'portrait');\n  }\n\n  private onOrientationChanged(orientation: OrientationQueryResult): void {\n    if(this.showInline) return;\n    if(orientation === 'landscape' && !this.navPreview) {\n      this.showFullscreen();\n    } else {\n      this.hideFullScreen();\n    }\n  }\n\n  @computedFrom('slideHistory.length')\n  private get hasPreviousSlide(): boolean {\n    return this.slideHistory.length > 1;\n  }\n\n  private gotoSlide(id: number): void {\n    this.currentSlideId = id;\n    this.currentBlock = this.courseContent?.blocks.find(b => b.iasSlideId === id) ?? null;\n\n    const historySlideIdx = this.slideHistory.findIndex(slideId => slideId === id);\n    if(historySlideIdx === -1) {\n      this.slideHistory.push(id);\n    } else {\n      // reset the history to the historySlideIdx\n      this.slideHistory = this.slideHistory.slice(0, historySlideIdx + 1);\n    }\n  }\n\n  private goToPreviousSlide(): void {\n    if(!this.hasPreviousSlide) return;\n\n    // remove current slide first and then retrieve previous\n    this.slideHistory.pop();\n    const previousSlideId = this.slideHistory.pop();\n\n    assertNotNullOrUndefined(previousSlideId, 'previousSlideId cannot be null or undefined');\n    this.gotoSlide(previousSlideId);\n  }\n\n  private hideFullScreen(): void {\n    this.fullScreenContainer?.hide();\n    this.getOrCreateFocusTrap()?.destroy();\n  }\n\n  private showFullscreen(): void {\n    this.getOrCreateFocusTrap()?.setup();\n    this.fullScreenContainer?.show();\n  }\n\n  private restart(): void {\n    assertNotNullOrUndefined(this.firstSlideId, 'No First Slide to go to ...');\n    this.gotoSlide(this.firstSlideId);\n  }\n\n  private finishSequence(): void {\n    this.hideFullScreen();\n    this.completed = true;\n    this.currentSlideId = null;\n    this.currentBlock = null;\n    this.finishBlock();\n  }\n\n  private handleGoToSlide(event: CustomEvent<{ slideId: number | null }>): void {\n    if(!event.detail.slideId) {\n      this.finishSequence();\n      return;\n    }\n    this.gotoSlide(event.detail.slideId);\n  }\n\n  private handleGoToPreviousSlide(): void {\n    this.goToPreviousSlide();\n  }\n\n  private finishBlock(): void {\n    const pendingBlockProgress = this.generateDefaultPendingBlockProgress();\n    pendingBlockProgress.isFinished = true;\n    this.blockProgressService.addPendingBlockProgress(pendingBlockProgress);\n  }\n\n  private generateDefaultPendingBlockProgress(): IPendingBlockProgress {\n    const { accessCodeId, block, courseProgress, pendingBlockProgress } = this;\n    assertNotNullOrUndefined(block, 'can\\'t generateDefaultPendingBlockProgress without block');\n    assertNotNullOrUndefined(accessCodeId, 'can\\'t generateDefaultPendingBlockProgress without accessCodeId');\n    assertNotNullOrUndefined(courseProgress, 'can\\'t generateDefaultPendingBlockProgress without courseProgress');\n    assertNotNullOrUndefined(pendingBlockProgress, 'can\\'t generateDefaultPendingBlockProgress without pendingBlockProgresses');\n\n    return PendingBlockProgressUtils.generateDefaultPendingBlockProgress(\n      accessCodeId,\n      block,\n      courseProgress,\n      pendingBlockProgress,\n    );\n  }\n\n  private startDesktopSequence(): void {\n    this.showDesktopOverlay = true;\n    setTimeout(() => {\n      this.getOrCreateFocusTrap()?.setup();\n      this.desktopFullScreenContainer?.show();\n    }, 0);\n  }\n\n  private closeDesktopSequence(): void {\n    this.desktopFullScreenContainer?.hide();\n    this.getOrCreateFocusTrap()?.destroy();\n    this.showDesktopOverlay = false;\n  }\n\n  private restartDesktopSequence(): void {\n    this.completed = false;\n    this.restart();\n    this.startDesktopSequence();\n  }\n\n  private handleSlideControlsChanged(event: CustomEvent<ICourseBlockMpSequenceSlideControlsChangedEventData>): void {\n    this.nextButtonCallback = event.detail.buttonCallback;\n    this.nextButtonEnabled = event.detail.buttonEnabled;\n  }\n\n  private handleNextButtonClick(): void {\n    this.nextButtonCallback?.();\n  }\n\n  private getOrCreateFocusTrap(): FocusTrap | null {\n    if(this.desktopFullscreenContentElement && !this.focusTrap) {\n      this.focusTrap = new FocusTrap(this.desktopFullscreenContentElement, this.subscriptionManagerService);\n    }\n    return this.focusTrap;\n  }\n}\n","import { bindable, autoinject, useView, PLATFORM } from 'aurelia-framework';\nimport { SubscriptionManager, SubscriptionManagerService } from 'common/aurelia-services/SubscriptionManagerService';\nimport { ICourseBlockDto } from 'common/dto/course/content/block.interface';\n\nexport const DEFAULT_TILE_WIDTH = 190;\n\n@useView(PLATFORM.moduleName('common/aurelia-components/course-block/course-block-mp-tile/course-block-mp-tile.html'))\n@autoinject()\nexport class CourseBlockMpTile {\n  @bindable() public block: ICourseBlockDto | null = null;\n  private MAX_TILES_PER_ROW = 4;\n  private widthPerTile = 0;\n  private subscriptionManager: SubscriptionManager;\n  private tileWrapperDomElement: HTMLElement | null = null;\n\n  constructor(\n    subscriptionManagerService: SubscriptionManagerService,\n  ) {\n    this.subscriptionManager = subscriptionManagerService.createSubscriptionManager();\n  }\n\n  protected attached(): void {\n    this.updateScaleFactor();\n    this.subscribeToChanges();\n  }\n\n  protected detached(): void {\n    this.subscriptionManager.disposeSubscriptions();\n  }\n\n  private subscribeToChanges(): void {\n    if(!this.tileWrapperDomElement?.isConnected || !this.block?.tiles) return;\n    this.subscriptionManager.disposeSubscriptions();\n    this.subscriptionManager.subscribeToCollectionChange(this.block.tiles, () => this.updateScaleFactor());\n    this.subscriptionManager.subscribeToElementResize(this.tileWrapperDomElement, () => this.updateScaleFactor());\n  }\n\n  private updateScaleFactor(): void {\n    if(!this.tileWrapperDomElement) return;\n    const innerWidthWithoutPadding = this.tileWrapperDomElement.clientWidth - parseFloat(getComputedStyle(this.tileWrapperDomElement).paddingLeft) - parseFloat(getComputedStyle(this.tileWrapperDomElement).paddingRight);\n    this.widthPerTile = innerWidthWithoutPadding / this.MAX_TILES_PER_ROW;\n  }\n}\n","import { autoinject, bindable, useView, PLATFORM, Parser, ObserverLocator } from 'aurelia-framework';\nimport { ICourseBlockDto } from 'common/dto/course/content/block.interface';\nimport { IBlockProgressReturnDto, ICourseProgress, IPendingBlockProgress } from 'common/dto/course/progress/course.progress.interface';\nimport { assertNotNullOrUndefined } from 'common/utils/assert';\nimport { PendingBlockProgressUtils } from 'common/utils/PendingBlockProgressUtils';\nimport { selectState, SelectStateObservableComponent } from 'common/aurelia-decorators/select-state';\nimport { blockByIdSelectorCreator } from 'common/store/selectors/content.context.selectors';\nimport { CourseLearnFooter } from 'common/aurelia-components/course-learn-footer/course-learn-footer';\nimport { BlockProgressService } from 'common/aurelia-components/course-block/course-block';\n\n@useView(PLATFORM.moduleName('common/aurelia-components/course-block/course-block-mp/course-block-mp.html'))\n@autoinject()\nexport class CourseBlockMp extends SelectStateObservableComponent {\n  @bindable() public contextIdentifier: string | null = null;\n  @bindable() public blockId: number | null = null;\n\n  @selectState(CourseBlockMp, blockByIdSelectorCreator, ['contextIdentifier', 'blockId']) public block?: ICourseBlockDto | null;\n\n  private accessCodeId: number | null = null;\n  private blockProgress: Array<IBlockProgressReturnDto> | null = null;\n  private pendingBlockProgress: Array<IPendingBlockProgress> | null = null;\n  private courseProgress: ICourseProgress | null = null;\n\n  private domElement: HTMLElement;\n\n  constructor(\n    element: Element,\n    parser: Parser,\n    observerLocator: ObserverLocator,\n    private blockProgressService: BlockProgressService,\n  ) {\n    super(parser, observerLocator);\n    this.domElement = element as HTMLElement;\n  }\n\n\n  protected attached(): void {\n    CourseLearnFooter.configure(this, {});\n    this.subscribeToProgress();\n  }\n\n  protected detached(): void {\n    this.blockProgressService.disposeSubscriptions(this);\n    CourseLearnFooter.resetConfiguration(this);\n  }\n\n  private subscribeToProgress(): void {\n    this.blockProgressService.disposeSubscriptions(this);\n    if(!this.domElement.isConnected || this.contextIdentifier == null) return;\n\n    this.blockProgressService.subscribeToProgress(this, this.contextIdentifier, (accessCodeId, blockProgress, courseProgress, pendingBlockProgress) => {\n      this.accessCodeId = accessCodeId;\n      this.blockProgress = blockProgress;\n      this.courseProgress = courseProgress;\n      this.pendingBlockProgress = pendingBlockProgress;\n    });\n  }\n\n  protected async finishBlock(): Promise<void> {\n    const pendingBlockProgress = this.generateDefaultPendingBlockProgress();\n    pendingBlockProgress.isFinished = true;\n    await this.blockProgressService.addPendingBlockProgress(pendingBlockProgress);\n  }\n\n  protected handleMpGameFinished(): void {\n    this.finishBlock();\n    CourseLearnFooter.updateConfiguration(this, {\n      showOnMobile: true,\n      message: {\n        tk: 'common.course-block.course-block-mp.finished',\n        tkParams: {},\n      },\n    });\n  }\n\n  private generateDefaultPendingBlockProgress(): IPendingBlockProgress {\n    const { accessCodeId, block, courseProgress, pendingBlockProgress } = this;\n    assertNotNullOrUndefined(block, 'can\\'t generateDefaultPendingBlockProgress without block');\n    assertNotNullOrUndefined(accessCodeId, 'can\\'t generateDefaultPendingBlockProgress without accessCodeId');\n    assertNotNullOrUndefined(courseProgress, 'can\\'t generateDefaultPendingBlockProgress without courseProgress');\n    assertNotNullOrUndefined(pendingBlockProgress, 'can\\'t generateDefaultPendingBlockProgress without pendingBlockProgresses');\n\n    return PendingBlockProgressUtils.generateDefaultPendingBlockProgress(\n      accessCodeId,\n      block,\n      courseProgress,\n      pendingBlockProgress,\n    );\n  }\n}\n","import { bindable, autoinject, useView, PLATFORM } from 'aurelia-framework';\nimport { ICourseBlockDto } from 'common/dto/course/content/block.interface';\n\n@useView(PLATFORM.moduleName('common/aurelia-components/course-block/course-block-picture/course-block-picture.html'))\n@autoinject()\nexport class CourseBlockPicture {\n  @bindable() public block: ICourseBlockDto | null = null;\n  private displayImageOverlay = false;\n\n  private openImageOverlay(): void {\n    this.displayImageOverlay = true;\n  }\n\n  private closeImageOverlay(): void {\n    this.displayImageOverlay = false;\n  }\n}\n","import { autoinject, bindable, computedFrom, useView, PLATFORM, observable } from 'aurelia-framework';\nimport { Router } from 'aurelia-router';\nimport { ICourseBlockDto } from 'common/dto/course/content/block.interface';\nimport { ICourseProgress, IPendingBlockProgress } from 'common/dto/course/progress/course.progress.interface';\nimport { assertNotNullOrUndefined } from 'common/utils/assert';\nimport { LearnQuestionsService } from 'common/aurelia-services/LearnQuestionsService';\nimport { ILearnQuestionsSession } from 'common/dto/course/content/question.interface';\nimport { BlockProgressService } from 'common/aurelia-components/course-block/course-block';\nimport { PendingBlockProgressUtils } from 'common/utils/PendingBlockProgressUtils';\nimport { selectState } from 'common/aurelia-decorators/select-state';\nimport { blockByIdSelectorCreator } from 'common/store/selectors/content.context.selectors';\n\n@useView(PLATFORM.moduleName('common/aurelia-components/course-block/course-block-question/course-block-question.html'))\n@autoinject()\nexport class CourseBlockQuestion {\n  @bindable() public contextIdentifier: string | null = null;\n  @bindable() public blockId: number | null = null;\n\n  @selectState(CourseBlockQuestion, blockByIdSelectorCreator, ['contextIdentifier', 'blockId']) public block?: ICourseBlockDto | null;\n\n  @observable() private accessCodeId: number | null = null;\n\n  private learnQuestionsSession: ILearnQuestionsSession | null = null;\n  private pendingBlockProgress: Array<IPendingBlockProgress> | null = null;\n  private courseProgress: ICourseProgress | null = null;\n\n  private markedAsFinished: boolean | null = null;\n\n  private domElement: HTMLElement;\n\n  constructor(\n    element: Element,\n    private router: Router,\n    private learnQuestionsService: LearnQuestionsService,\n    private blockProgressService: BlockProgressService,\n  ) {\n    this.domElement = element as HTMLElement;\n  }\n\n  @computedFrom('learnQuestionsSession')\n  private get answeredQuestionCount(): number {\n    if(this.learnQuestionsSession) {\n      return this.learnQuestionsSession.questions.reduce((sum, q) => sum + (q.isAnswered && q.isCorrect ? 1 : 0), 0);\n    }\n\n    return 0;\n  }\n\n  @computedFrom('answeredQuestionCount', 'block')\n  private get progress(): number {\n    if(this.block) {\n      return Math.floor(this.answeredQuestionCount * 100 / this.block.questionIds.length);\n    }\n\n    return 0;\n  }\n\n  @computedFrom('answeredQuestionCount', 'block')\n  private get complete(): boolean {\n    if(this.block) {\n      return this.answeredQuestionCount === this.block.questionIds.length;\n    }\n\n    return false;\n  }\n\n  protected attached(): void {\n    this.markedAsFinished = false;\n    this.subscribeToProgress();\n    this.subscribeToLearnQuestionsSession();\n  }\n\n  protected detached(): void {\n    this.markedAsFinished = null;\n    this.blockProgressService.disposeSubscriptions(this);\n    this.learnQuestionsService.disposeSubscriptions(this);\n  }\n\n  private contextIdentifierChanged(): void {\n    this.subscribeToProgress();\n  }\n\n  private accessCodeIdChanged(): void {\n    this.subscribeToLearnQuestionsSession();\n    this.subscribeToProgress();\n  }\n\n  private blockChanged(): void {\n    this.subscribeToLearnQuestionsSession();\n    this.subscribeToProgress();\n  }\n\n  private subscribeToLearnQuestionsSession(): void {\n    this.learnQuestionsService.disposeSubscriptions(this);\n    if(this.accessCodeId == null || this.block == null) return;\n\n    this.learnQuestionsService.subscribeToLearnQuestionsSession(this, this.accessCodeId, this.block.pageId, this.block.id, learnQuestionsSession => {\n      this.learnQuestionsSession = learnQuestionsSession;\n    });\n  }\n\n  private subscribeToProgress(): void {\n    this.blockProgressService.disposeSubscriptions(this);\n    if(!this.domElement?.isConnected || this.contextIdentifier == null) return;\n\n    this.blockProgressService.subscribeToProgress(this, this.contextIdentifier, (accessCodeId, _blockProgress, courseProgress, pendingBlockProgress) => {\n      this.accessCodeId = accessCodeId;\n      this.courseProgress = courseProgress;\n      this.pendingBlockProgress = pendingBlockProgress;\n\n      if(this.markedAsFinished === false && this.complete && this.pendingBlockProgress != null && this.courseProgress != null) {\n        this.finishBlock();\n        this.markedAsFinished = true;\n      }\n    });\n  }\n\n  private async goToAnswerQuestions(): Promise<void> {\n    assertNotNullOrUndefined(this.block, 'block must be set');\n    assertNotNullOrUndefined(this.accessCodeId, 'accessCodeId must be set');\n\n    await this.learnQuestionsService.createOrResetLearnQuestionsSession(this.accessCodeId, this.block.pageId, this.block.id);\n    this.router.navigateToRoute('course-learn-questions-page', { blockId: this.block.id });\n  }\n\n  private goToShowQuestions(): void {\n    assertNotNullOrUndefined(this.block, 'block must be set');\n\n    this.router.navigateToRoute('course-learn-questions-result-page', { blockId: this.block.id });\n  }\n\n  private finishBlock(): void {\n    const pendingBlockProgress = this.generateDefaultPendingBlockProgress();\n    pendingBlockProgress.isFinished = true;\n    this.blockProgressService.addPendingBlockProgress(pendingBlockProgress);\n  }\n\n  private generateDefaultPendingBlockProgress(): IPendingBlockProgress {\n    const { accessCodeId, block, courseProgress, pendingBlockProgress } = this;\n    assertNotNullOrUndefined(block, 'can\\'t generateDefaultPendingBlockProgress without block');\n    assertNotNullOrUndefined(accessCodeId, 'can\\'t generateDefaultPendingBlockProgress without accessCodeId');\n    assertNotNullOrUndefined(courseProgress, 'can\\'t generateDefaultPendingBlockProgress without courseProgress');\n    assertNotNullOrUndefined(pendingBlockProgress, 'can\\'t generateDefaultPendingBlockProgress without pendingBlockProgresses');\n\n    return PendingBlockProgressUtils.generateDefaultPendingBlockProgress(\n      accessCodeId,\n      block,\n      courseProgress,\n      pendingBlockProgress,\n    );\n  }\n}\n","import { bindable, PLATFORM, useView } from 'aurelia-framework';\n\n@useView(PLATFORM.moduleName('common/aurelia-components/course-block/course-block-spacer/course-block-spacer.html'))\nexport class CourseBlockSpacer {\n  @bindable() public isNonInteractivePreview = true;\n}","import { bindable, useView, PLATFORM, autoinject } from 'aurelia-framework';\nimport { RichTextMode } from 'common/aurelia-components/rich-text/rich-text';\nimport { selectState } from 'common/aurelia-decorators/select-state';\nimport { ICourseBlockDto } from 'common/dto/course/content/block.interface';\nimport { blockByIdSelectorCreator } from 'common/store/selectors/content.context.selectors';\n\n@useView(PLATFORM.moduleName('common/aurelia-components/course-block/course-block-text/course-block-text.html'))\n@autoinject()\nexport class CourseBlockText {\n  @bindable() public contextIdentifier: string | null = null;\n  @bindable() public blockId: number | null = null;\n  @bindable() public navPreview = false;\n\n  @selectState(CourseBlockText, blockByIdSelectorCreator, ['contextIdentifier', 'blockId']) public block?: ICourseBlockDto | null;\n\n  private textMode = RichTextMode.CONTENT;\n}\n","import { autoinject, bindable, computedFrom, ObserverLocator, Parser, PLATFORM, useView } from 'aurelia-framework';\nimport { UpdateTimer } from 'common/aurelia-classes/UpdateTimer';\nimport { PlayState, PlayStateChangedEvent, SecondsChangedEvent, SourceMode } from 'common/aurelia-components/video-player/video-player';\nimport { selectState, SelectStateObservableComponent } from 'common/aurelia-decorators/select-state';\nimport { VimeoApiService } from 'common/aurelia-services/VimeoApiService';\nimport { ICourseBlockDto } from 'common/dto/course/content/block.interface';\nimport { BlockType } from 'common/dto/course/content/course.types';\nimport { IBlockProgressReturnDto, ICourseProgress, IPendingBlockProgress } from 'common/dto/course/progress/course.progress.interface';\nimport { blockByIdSelectorCreator } from 'common/store/selectors/content.context.selectors';\nimport { mediaPercentageConsideredSeenSelectorCreator } from 'common/store/selectors/course.context.selectors';\nimport { assertNotNullOrUndefined } from 'common/utils/assert';\nimport { BlockUtils } from 'common/utils/BlockUtils';\nimport { BunnyUtils } from 'common/utils/BunnyUtils';\nimport { MediaBlockUtils } from 'common/utils/MediaBlockUtils';\nimport { PendingBlockProgressUtils } from 'common/utils/PendingBlockProgressUtils';\nimport { BlockProgressService } from '../course-block';\n\n@useView(PLATFORM.moduleName('common/aurelia-components/course-block/course-block-video/course-block-video.html'))\n@autoinject()\nexport class CourseBlockVideo extends SelectStateObservableComponent {\n  @bindable() public contextIdentifier: string | null = null;\n  @bindable() public blockId: number | null = null;\n  @bindable() public isStatic = true;\n\n  @selectState(CourseBlockVideo, blockByIdSelectorCreator, ['contextIdentifier', 'blockId']) public block?: ICourseBlockDto | null;\n  @selectState(CourseBlockVideo, mediaPercentageConsideredSeenSelectorCreator, ['contextIdentifier']) private mediaPercentageConsideredSeen?: number | null;\n\n  private accessCodeId: number | null = null;\n  private blockProgress: Array<IBlockProgressReturnDto> | null = null;\n  private pendingBlockProgress: Array<IPendingBlockProgress> | null = null;\n  private courseProgress: ICourseProgress | null = null;\n  private currentTimeLoaded = false;\n  private currentTime = 0;\n  private lastPlayTimeTickTimestamp: number | null = null;\n  private isPlaying = false;\n  private thumbnailSrc: string | null = null;\n\n  private domElement: HTMLElement;\n\n  constructor(\n    element: Element,\n    parser: Parser,\n    observerLocator: ObserverLocator,\n    private blockProgressService: BlockProgressService,\n    private vimeoApiService: VimeoApiService,\n  ) {\n    super(parser, observerLocator);\n    this.domElement = element as HTMLElement;\n  }\n\n  protected attached(): void {\n    this.subscribeToProgress();\n    UpdateTimer.getInstance().subscribe(this, this.handlePlayTimeUpdateTick.bind(this));\n  }\n\n  protected detached(): void {\n    this.blockProgressService.stopTrackInteraction(this);\n    this.blockProgressService.disposeSubscriptions(this);\n\n    UpdateTimer.getInstance().unsubscribe(this);\n    // manually trigger the tick so we don't lose the last X seconds\n    if(this.isPlaying) {\n      this.handlePlayTimeUpdateTick();\n    }\n\n    this.isPlaying = false;\n  }\n\n  private contextIdentifierChanged(): void {\n    this.subscribeToProgress();\n  }\n\n  private blockChanged(): void {\n    this.currentTimeLoaded = false;\n    this.loadCurrentTimeIfNecessary();\n\n    this.thumbnailSrc = null;\n    if(this.block != null) this.loadVideoThumbnail();\n  }\n\n  @computedFrom('block.videoUrl')\n  private get vimeoId(): number | null {\n    return this.block?.vimeoId ?? null;\n  }\n\n  @computedFrom('block.videoUrl')\n  private get bunnyId(): string | null {\n    return this.block?.bunnyId ?? null;\n  }\n\n  @computedFrom('block.videoUrl')\n  private get videoUrl(): string | null {\n    return this.block?.videoUrl ?? null;\n  }\n\n  @computedFrom('block.videoUrl2')\n  private get videoUrl2(): string | null {\n    return this.block?.pipVideoUrl ?? null;\n  }\n\n  @computedFrom('block.title', 'block.type')\n  private get videoTitle(): string | null {\n    return this.block?.type === BlockType.VIDEO ? this.block?.title : null;\n  }\n\n  @computedFrom('block.pipVideoUrl', 'block.pipVideoSources')\n  private get isDualVideo(): boolean {\n    return !!this.block && BlockUtils.isDualVideoBlock(this.block);\n  }\n\n  @computedFrom('block.type')\n  private get sourceMode(): SourceMode {\n    switch(this.block?.type) {\n      case BlockType.YOUTUBE: return SourceMode.YouTube;\n      case BlockType.VIMEO: return SourceMode.Vimeo;\n      default: return SourceMode.Default;\n    }\n  }\n\n  private async loadVideoThumbnail(): Promise<void> {\n    try {\n      switch(this.sourceMode) {\n        case SourceMode.YouTube: {\n          // ID extraction taken from private plyr functions\n          const videoId = this.videoUrl?.match(/^.*(youtu.be\\/|v\\/|u\\/\\w\\/|embed\\/|watch\\?v=|&v=)([^#&?]*).*/)?.[2];\n          this.thumbnailSrc = videoId ? `http://img.youtube.com/vi/${videoId}/0.jpg` : null;\n        } break;\n        case SourceMode.Vimeo: {\n          // ID extraction taken from private plyr functions\n          const videoId = Number(this.videoUrl?.match(/^.*(vimeo.com\\/|video\\/)(\\d+).*/)?.[2]);\n          this.thumbnailSrc = Number.isNaN(videoId) ? null : await this.getVimeoThumbnail(videoId);\n        } break;\n        default: {\n          if(this.vimeoId) {\n            this.thumbnailSrc = await this.getVimeoThumbnail(this.vimeoId);\n          } else if(this.bunnyId && this.videoUrl) {\n            this.thumbnailSrc = BunnyUtils.thumbnailFromUrl(this.videoUrl);\n          } else {\n            throw new Error('vimeoId or bunnyId must be defined');\n          }\n        }\n      }\n    } catch(ex) {\n      console.error(ex);\n    }\n  }\n\n  private async getVimeoThumbnail(vimeoId: number): Promise<string | null> {\n    const videoInfo = await this.vimeoApiService.getVideoInfo(vimeoId);\n    return videoInfo?.thumbnail_url ?? null;\n  }\n\n  private subscribeToProgress(): void {\n    this.blockProgressService.disposeSubscriptions(this);\n    if(!this.domElement.isConnected || this.contextIdentifier == null) return;\n\n    this.blockProgressService.subscribeToProgress(this, this.contextIdentifier, (accessCodeId, blockProgress, courseProgress, pendingBlockProgress) => {\n      this.accessCodeId = accessCodeId;\n      this.blockProgress = blockProgress;\n      this.courseProgress = courseProgress;\n      this.pendingBlockProgress = pendingBlockProgress;\n\n      this.loadCurrentTimeIfNecessary();\n    });\n  }\n\n  private loadCurrentTimeIfNecessary(): void {\n    if(!this.currentTimeLoaded && this.blockProgress !== null && this.block && this.pendingBlockProgress) {\n      const blockId = this.block.id;\n      const progress = this.blockProgress.find(bp => bp.blockId === blockId);\n      const pendingProgress = this.pendingBlockProgress.find(bp => bp.blockId === blockId && bp.accessCodeId === this.accessCodeId);\n\n      if(pendingProgress) {\n        this.currentTime = pendingProgress.currentTime;\n      } else if(progress) {\n        this.currentTime = progress.currentTime;\n      } else {\n        this.currentTime = 0;\n      }\n\n      this.currentTimeLoaded = true;\n    }\n  }\n\n  private async handleSecondsChanged(event: SecondsChangedEvent): Promise<void> {\n    const pendingBlockProgress = this.generateDefaultPendingBlockProgress();\n    pendingBlockProgress.currentTime = event.detail.seconds;\n    await this.blockProgressService.addPendingBlockProgress(pendingBlockProgress);\n  }\n\n  private handlePlayStateChanged(event: PlayStateChangedEvent): void {\n    if(event.detail.playState === PlayState.Start) {\n      this.blockProgressService.startTrackInteraction(this);\n      this.lastPlayTimeTickTimestamp = Date.now();\n      this.isPlaying = true;\n    } else {\n      this.blockProgressService.stopTrackInteraction(this);\n      if(this.isPlaying === true) {\n        this.handlePlayTimeUpdateTick();\n      }\n      this.isPlaying = false;\n    }\n  }\n\n  private handlePlayTimeUpdateTick(): void {\n    if(!this.isPlaying) {\n      return;\n    }\n\n    assertNotNullOrUndefined(this.lastPlayTimeTickTimestamp, 'can\\'t handlePlayTimeCommitIntervalTick without lastPlayTimeTickTimestamp');\n    const now = Date.now();\n    this.commitPlayTime(now - this.lastPlayTimeTickTimestamp);\n    this.lastPlayTimeTickTimestamp = now;\n  }\n\n  private async commitPlayTime(msPlayed: number): Promise<void> {\n    assertNotNullOrUndefined(this.block, 'can\\'t commitPlayTime without a block');\n    assertNotNullOrUndefined(this.mediaPercentageConsideredSeen, 'can\\'t commitPlayTime without mediaPercentageConsideredSeen');\n    const pendingBlockProgress = this.generateDefaultPendingBlockProgress();\n    pendingBlockProgress.msInteractedWith += msPlayed;\n    pendingBlockProgress.isFinished = MediaBlockUtils.checkIfPendingBlockIsFinished(\n      this.block.duration,\n      this.mediaPercentageConsideredSeen,\n      pendingBlockProgress,\n    );\n    await this.blockProgressService.addPendingBlockProgress(pendingBlockProgress);\n  }\n\n  private generateDefaultPendingBlockProgress(): IPendingBlockProgress {\n    const { accessCodeId, block, courseProgress, pendingBlockProgress } = this;\n    assertNotNullOrUndefined(block, 'can\\'t generateDefaultPendingBlockProgress without block');\n    assertNotNullOrUndefined(accessCodeId, 'can\\'t generateDefaultPendingBlockProgress without accessCodeId');\n    assertNotNullOrUndefined(courseProgress, 'can\\'t generateDefaultPendingBlockProgress without courseProgress');\n    assertNotNullOrUndefined(pendingBlockProgress, 'can\\'t generateDefaultPendingBlockProgress without pendingBlockProgresses');\n\n    return PendingBlockProgressUtils.generateDefaultPendingBlockProgress(\n      accessCodeId,\n      block,\n      courseProgress,\n      pendingBlockProgress,\n    );\n  }\n}\n","import { autoinject, bindable, computedFrom, PLATFORM, useView } from 'aurelia-framework';\nimport { selectState } from 'common/aurelia-decorators/select-state';\nimport { ICourseBlockDto } from 'common/dto/course/content/block.interface';\nimport { BlockType } from 'common/dto/course/content/course.types';\nimport { IBlockProgressReturnDto, ICourseProgress, IPendingBlockProgress } from 'common/dto/course/progress/course.progress.interface';\nimport { blockByIdSelectorCreator } from 'common/store/selectors/content.context.selectors';\nimport { isIasSelectorCreator } from 'common/store/selectors/course.context.selectors';\n\nexport abstract class BlockProgressService {\n  public abstract addPendingBlockProgress(blockProgress: IPendingBlockProgress): Promise<void>;\n  public abstract subscribeToProgress(\n    scope: unknown,\n    contextIdentifier: string,\n    callback: (\n      accessCodeId: number,\n      blockProgress: Array<IBlockProgressReturnDto>,\n      courseProgress: ICourseProgress,\n      pendingBlockProgress: Array<IPendingBlockProgress>,\n    ) => void,\n  ): void;\n  public abstract disposeSubscriptions(scope: unknown): void;\n  public abstract startTrackInteraction(scope: unknown): void;\n  public abstract stopTrackInteraction(scope: unknown): void;\n}\n\n@useView(PLATFORM.moduleName('common/aurelia-components/course-block/course-block.html'))\n@autoinject()\nexport class CourseBlock {\n  @bindable() public contextIdentifier: string | null = null;\n  @bindable() public blockId: number | null = null;\n\n  /** for \"peeking\" at a page before it is actually loaded: display all the things ready to start, but do not start anything */\n  @bindable() public navPreview = false;\n\n  /** all content will be static - for previews */\n  @bindable() public isNonInteractivePreview = true;\n\n  @selectState(CourseBlock, isIasSelectorCreator, ['contextIdentifier', 'blockId']) private isIas?: boolean = false;\n  @selectState(CourseBlock, blockByIdSelectorCreator, ['contextIdentifier', 'blockId']) private block?: ICourseBlockDto | null;\n\n  private BlockType = BlockType;\n\n  @computedFrom('block.styleSpacingOptions')\n  private get blockStyle(): Record<string, unknown> {\n    const style: Record<string, unknown> = {};\n    if(this.block?.styleSpacingOptions) {\n      const { right, left } = this.block.styleSpacingOptions;\n      style['margin-left'] = `${left}%`;\n      style['margin-right'] = `${right}%`;\n    }\n\n    return style;\n  }\n\n  @computedFrom('block.type')\n  private get needsFullHeight(): boolean {\n    return !!this.block?.type\n      && [\n        BlockType.MP_FLIPPING_CARD,\n        BlockType.MP_HOT_SPOT,\n        BlockType.MP_INTERACTIVE_TEXT,\n        BlockType.MP_QUESTION,\n        BlockType.MP_RFG,\n        BlockType.MP_RISI,\n      ].includes(this.block.type);\n  }\n}\n","import { PLATFORM, FrameworkConfiguration } from 'aurelia-framework';\n\nexport function configure(config: FrameworkConfiguration): void {\n  config.feature(PLATFORM.moduleName('common/aurelia-components/course-block/course-block-mp-flipping-card/index'));\n  config.globalResources(PLATFORM.moduleName('common/aurelia-components/course-block/course-block-audio/course-block-audio'));\n  config.globalResources(PLATFORM.moduleName('common/aurelia-components/course-block/course-block-file/course-block-file'));\n  config.globalResources(PLATFORM.moduleName('common/aurelia-components/course-block/course-block-mp-hot-spot/course-block-mp-hot-spot'));\n  config.globalResources(PLATFORM.moduleName('common/aurelia-components/course-block/course-block-mp-rfg/course-block-mp-rfg'));\n  config.globalResources(PLATFORM.moduleName('common/aurelia-components/course-block/course-block-mp-risi/course-block-mp-risi'));\n  config.globalResources(PLATFORM.moduleName('common/aurelia-components/course-block/course-block-mp-scorm/course-block-mp-scorm'));\n  config.globalResources(PLATFORM.moduleName('common/aurelia-components/course-block/course-block-mp-sequence/course-block-mp-sequence-sidebar'));\n  config.globalResources(PLATFORM.moduleName('common/aurelia-components/course-block/course-block-mp-sequence/course-block-mp-sequence-slide'));\n  config.globalResources(PLATFORM.moduleName('common/aurelia-components/course-block/course-block-mp-sequence/course-block-mp-sequence'));\n  config.globalResources(PLATFORM.moduleName('common/aurelia-components/course-block/course-block-mp-tile/course-block-mp-tile'));\n  config.globalResources(PLATFORM.moduleName('common/aurelia-components/course-block/course-block-mp/course-block-mp'));\n  config.globalResources(PLATFORM.moduleName('common/aurelia-components/course-block/course-block-picture/course-block-picture'));\n  config.globalResources(PLATFORM.moduleName('common/aurelia-components/course-block/course-block-question/course-block-question'));\n  config.globalResources(PLATFORM.moduleName('common/aurelia-components/course-block/course-block-spacer/course-block-spacer'));\n  config.globalResources(PLATFORM.moduleName('common/aurelia-components/course-block/course-block-text/course-block-text'));\n  config.globalResources(PLATFORM.moduleName('common/aurelia-components/course-block/course-block-video/course-block-video'));\n  config.globalResources(PLATFORM.moduleName('common/aurelia-components/course-block/course-block'));\n}\n"],"names":[],"sourceRoot":""}