{"version":3,"file":"app-2f8026c1.f584a1446b2fc034d810.bundle.js","mappings":"AAGA","sources":["webpack://lernplattform-admin-frontend/../common/aurelia-components/dual-video-player/dual-video-player.html","webpack://lernplattform-admin-frontend/../common/aurelia-components/exam-image-overlay/exam-image-overlay.html","webpack://lernplattform-admin-frontend/../common/aurelia-components/exam-image/exam-image.html","webpack://lernplattform-admin-frontend/../common/aurelia-components/dual-video-player/dual-video-player.ts","webpack://lernplattform-admin-frontend/../common/aurelia-components/dual-video-player/index.ts","webpack://lernplattform-admin-frontend/../common/aurelia-components/exam-image-overlay/exam-image-overlay.ts","webpack://lernplattform-admin-frontend/../common/aurelia-components/exam-image-overlay/index.ts","webpack://lernplattform-admin-frontend/../common/aurelia-components/exam-image/exam-image.ts","webpack://lernplattform-admin-frontend/../common/aurelia-components/exam-image/index.ts"],"sourcesContent":["// Module\nvar code = \"<template class=\\\"dual-video-player dual-video-player--pip-${secondaryPosition} ${isFullscreen ? 'dual-video-player--fullscreen' : ''}\\\"> <fullscreenable-container class=\\\"dual-video-player__fullscreen-container\\\" view-model.ref=\\\"fullscreenableContainer\\\" is-fullscreen.from-view=\\\"isFullscreen\\\"> <div class=\\\"dual-video-player__container ${primary === playerId ? 'dual-video-player__container--primary' : 'dual-video-player__container--secondary'}\\\" repeat.for=\\\"playerId of [1,2]\\\"> <video-player class=\\\"dual-video-player__player\\\" current-time.to-view=\\\"currentTime\\\" has-controls.to-view=\\\"hasControls\\\" reset-on-end.to-view=\\\"resetOnEnd\\\" fit-to-container.to-view=\\\"fitToContainer\\\" bordered.to-view=\\\"primary === playerId && !isFullscreen ? bordered : false\\\" title.to-view=\\\"title\\\" transcript.to-view=\\\"transcript\\\" video-url.to-view=\\\"playerId === 1 ? videoUrl1 : videoUrl2\\\" video-sources.to-view=\\\"playerId === 1 ? videoSources1 : videoSources2\\\" vimeo-id.to-view=\\\"playerId === 1 ? vimeoId1 : vimeoId2\\\" bunny-id.to-view=\\\"playerId === 1 ? bunnyId1 : bunnyId2\\\" thumbnail-src.to-view=\\\"playerId === 1 ? thumbnailSrc1 : thumbnailSrc2\\\" source-mode.to-view=\\\"playerId === 1 ? sourceMode1 : sourceMode2\\\" dual-player-role.to-view=\\\"primary === playerId ? 'primary' : 'secondary'\\\" view-model.ref=\\\"videoPlayer[playerId]\\\" click.trigger=\\\"switch(playerId)\\\" used-thumbnail-src-changed.trigger=\\\"handleUsedThumbnailSrcChanged($event, playerId)\\\" player-volume-changed.trigger=\\\"handlePlayerVolumeChanged($event, playerId)\\\" play-state-changed.trigger=\\\"handlePlayStateChanged($event, playerId)\\\" player-seeking.trigger=\\\"handlePlayerSeeking($event, playerId)\\\" fullscreen-clicked.trigger=\\\"handleFullscreenClicked($event, playerId)\\\"></video-player> <x-icon if.to-view=\\\"primary !== playerId\\\" class=\\\"dual-video-player__switch-icon\\\" size=\\\"unspecified\\\" icon-type=\\\"custom\\\" icon-name=\\\"System/Swap\\\"></x-icon> </div> </fullscreenable-container> </template>\";\n// Exports\nexport default code;","// Module\nvar code = \"<template class=\\\"exam-image-overlay\\\" tabindex=\\\"0\\\"> <div class=\\\"exam-image-overlay__container\\\" ref=\\\"domImageContainerElement\\\"> <img alt.to-view=\\\"altText\\\" ref=\\\"domImageElement\\\" class=\\\"exam-image-overlay__image\\\" load.trigger=\\\"onImageLoad($event)\\\" src.to-view=\\\"imageToDisplay\\\"/> </div> <safe-icon class=\\\"exam-image-overlay__close\\\" use-as-button t=\\\"[aria-label]common.exam-image.close\\\" icon-name=\\\"System/Close\\\"></safe-icon> </template>\";\n// Exports\nexport default code;","// Module\nvar code = \"<template class=\\\"exam-image\\\" tabindex=\\\"${ enableZoom ? '0' : '-1' }\\\"> <aspect-ratio-container if.to-view=\\\"imageLoading || blinkImageLoading\\\" show.to-view=\\\"!blink\\\" class=\\\"exam-image__blurhash-container\\\" ratio.to-view=\\\"16/9\\\" mode=\\\"width-and-height-dependent\\\"> <blurhash class=\\\"exam-image__blurhash\\\" hash.to-view=\\\"blurHash\\\" ratio.to-view=\\\"16/9\\\"></blurhash> </aspect-ratio-container> <img show.to-view=\\\"!blink\\\" alt.to-view=\\\"altText\\\" class=\\\"exam-image__file ${enableZoom ? 'exam-image__file--zoom-enabled' : ''} ${imageLoading || blinkImageLoading ? 'exam-image__file--loading' : ''}\\\" click.delegate=\\\"openImageOverlay()\\\" data-test-selector=\\\"testexam-question-image\\\" src.to-view=\\\"imgSrc\\\" load.trigger=\\\"handleImageLoaded()\\\"/> <aspect-ratio-container if.to-view=\\\"imageLoading || blinkImageLoading\\\" show.to-view=\\\"blink\\\" class=\\\"exam-image__blurhash-container\\\" ratio.to-view=\\\"16/9\\\" mode=\\\"width-and-height-dependent\\\"> <blurhash class=\\\"exam-image__blurhash\\\" hash.to-view=\\\"blinkBlurHash\\\" ratio.to-view=\\\"16/9\\\"></blurhash> </aspect-ratio-container> <img if.to-view=\\\"blinkImgSrc\\\" show.to-view=\\\"blink\\\" alt.to-view=\\\"altText\\\" class=\\\"exam-image__file ${enableZoom ? 'exam-image__file--zoom-enabled' : ''} ${imageLoading || blinkImageLoading ? 'exam-image__file--loading' : ''}\\\" click.delegate=\\\"openImageOverlay()\\\" data-test-selector=\\\"testexam-question-image\\\" src.to-view=\\\"blinkImgSrc\\\" load.trigger=\\\"handleBlinkImageLoaded()\\\"/> </template>\";\n// Exports\nexport default code;","import { autoinject, bindable, PLATFORM, useView } from 'aurelia-framework';\nimport { IVideoSource } from 'common/dto/content/videos/video.interface';\nimport { PipVideoPosition } from 'common/enum/block.style.enum';\nimport { assertNotNullOrUndefined } from 'common/utils/assert';\nimport { DomUtils } from 'common/utils/DomUtils';\nimport { FullscreenableContainer } from '../fullscreenable-container/fullscreenable-container';\nimport {\n  DualPlayerRole,\n  IPlayOptions,\n  PlayerSeekingEvent,\n  PlayerVolumeChangedEvent,\n  PlayState,\n  PlayStateChangedEvent,\n  SourceMode,\n  UsedThumbnailSrcChangedEvent,\n  VideoPlayer,\n} from '../video-player/video-player';\n\n/**\n * @event {SecondsChangedEvent} seconds-changed\n * @event {PlayStateChangedEvent} play-state-changed\n * @event {TimeUpdateEvent} current-time-changed\n * @event {PlayerVolumeChangedEvent} player-volume-changed\n * @event {PlayerSeekingEvent} player-seeking\n */\n@autoinject()\n@useView(PLATFORM.moduleName('./dual-video-player.html'))\nexport class DualVideoPlayer {\n  // Video 1\n  @bindable() public videoUrl1: string | null = null;\n  @bindable() public videoSources1: Array<IVideoSource> | null = null;\n  /** used to load vimeo thumbnails */\n  @bindable() public vimeoId1: number | null = null;\n  @bindable() public bunnyId1: string | null = null;\n  @bindable() public thumbnailSrc1: string | null = null;\n  /** only use with .from-view binding */\n  @bindable() public usedThumbnailSrc1: string | null = null;\n  @bindable() public sourceMode1 = SourceMode.Default;\n\n  // Video 1\n  @bindable() public videoUrl2: string | null = null;\n  @bindable() public videoSources2: Array<IVideoSource> | null = null;\n  /** used to load vimeo thumbnails */\n  @bindable() public vimeoId2: number | null = null;\n  @bindable() public bunnyId2: string | null = null;\n  @bindable() public thumbnailSrc2: string | null = null;\n  /** only use with .from-view binding */\n  @bindable() public usedThumbnailSrc2: string | null = null;\n  @bindable() public sourceMode2 = SourceMode.Default;\n\n  // Shared\n  @bindable() public currentTime = 0;\n  @bindable() public hasControls = true;\n  @bindable() public resetOnEnd = true;\n  @bindable() public fitToContainer = false;\n  @bindable() public bordered = false;\n  @bindable() public title: string | null = null;\n  @bindable() public transcript: string | null = null;\n  @bindable() public dualPlayerRole: DualPlayerRole | null = null;\n  @bindable() public secondaryPosition = PipVideoPosition.TopRight;\n\n  public fullscreenableContainer: FullscreenableContainer | null = null;\n  public videoPlayer: Array<VideoPlayer | null> = [];\n  public primary: 1 | 2 = 1;\n  public attachTo1: HTMLElement | null = null;\n  public attachTo2: HTMLElement | null = null;\n\n  private element: HTMLElement;\n  private primaryVolume: number = 0;\n\n  constructor(\n    element: Element,\n  ) {\n    this.element = element as HTMLElement;\n  }\n\n  protected attached(): void {\n    const { primary } = this.getPrimaryAndSecondary();\n    this.primaryVolume = primary.getVolume();\n  }\n\n  public rewind(): void {\n    const { primary } = this.getPrimaryAndSecondary();\n    primary.rewind();\n  }\n\n  public pause(): void {\n    const { primary } = this.getPrimaryAndSecondary();\n    primary.pause();\n  }\n\n  public play(options: IPlayOptions): void {\n    const { primary } = this.getPrimaryAndSecondary();\n    primary.play(options);\n  }\n\n  public startFromTime(options: IPlayOptions): void {\n    const { primary } = this.getPrimaryAndSecondary();\n    primary.startFromTime(options);\n  }\n\n  public stopAtTime(seconds: number): void {\n    const { primary } = this.getPrimaryAndSecondary();\n    primary.stopAtTime(seconds);\n  }\n\n  public syncTime(seconds: number, allowDeviation = true): void {\n    const { primary } = this.getPrimaryAndSecondary();\n    primary.syncTime(seconds, allowDeviation);\n  }\n\n  public exitFullscreen(): void {\n    const { primary } = this.getPrimaryAndSecondary();\n    primary?.exitFullscreen();\n  }\n\n  private switch(caller: number): void {\n    if(this.isPrimary(caller)) return;\n    this.primary = this.primary === 1 ? 2 : 1;\n    const { primary, secondary } = this.getPrimaryAndSecondary();\n    primary.setVolume(this.primaryVolume);\n    secondary.setVolume(0);\n  }\n\n  private handleUsedThumbnailSrcChanged(event: UsedThumbnailSrcChangedEvent, caller: number): void {\n    if(caller === 1) this.usedThumbnailSrc1 = event.detail.usedThumbnailSrc;\n    else if(caller === 2) this.usedThumbnailSrc2 = event.detail.usedThumbnailSrc;\n  }\n\n  private handlePlayerVolumeChanged(event: PlayerVolumeChangedEvent, caller: number): void {\n    if(this.isPrimary(caller)) {\n      this.primaryVolume = event.detail.volume;\n    } else {\n      DomUtils.passThroughEvent(this.element, event);\n    }\n  }\n\n  private handlePlayStateChanged(event: PlayStateChangedEvent, caller: number): void {\n    if(!this.isPrimary(caller)) return;\n\n    const { primary, secondary } = this.getPrimaryAndSecondary();\n\n    switch(event.detail.playState) {\n      case PlayState.Start: {\n        secondary.startFromTime({ time: event.detail.currentTime, allowDeviation: false, testAutoPlay: false });\n      } break;\n\n      case PlayState.Stop: {\n        secondary.pause();\n        secondary.syncTime(event.detail.currentTime, false);\n        primary.syncTime(event.detail.currentTime, false);\n      } break;\n\n      case PlayState.Ended: {\n        secondary.stopAtTime(0);\n      } break;\n    }\n\n    DomUtils.passThroughEvent(this.element, event);\n  }\n\n  private handlePlayerSeeking(event: PlayerSeekingEvent, caller: number): void {\n    if(!this.isPrimary(caller) || event.detail.playing) return;\n\n    const { secondary } = this.getPrimaryAndSecondary();\n    secondary.syncTime(event.detail.currentTime, false);\n\n    DomUtils.passThroughEvent(this.element, event);\n  }\n\n  private handleFullscreenClicked(): void {\n    if(this.fullscreenableContainer?.isFullscreen) this.fullscreenableContainer.exitFullscreen();\n    else this.fullscreenableContainer?.requestFullscreen();\n  }\n\n  private getPrimaryAndSecondary(): { primary: VideoPlayer; secondary: VideoPlayer } {\n    assertNotNullOrUndefined(this.videoPlayer[1], 'forgot to ref videoPlayer1');\n    assertNotNullOrUndefined(this.videoPlayer[2], 'forgot to ref videoPlayer2');\n\n    return this.primary === 2 ? {\n      primary: this.videoPlayer[2],\n      secondary: this.videoPlayer[1],\n    } : {\n      primary: this.videoPlayer[1],\n      secondary: this.videoPlayer[2],\n    };\n  }\n\n  private isPrimary(playerId: number): boolean {\n    return this.primary === playerId;\n  }\n}\n","import { PLATFORM, FrameworkConfiguration } from 'aurelia-framework';\n\nexport function configure(config: FrameworkConfiguration): void {\n  config.globalResources(PLATFORM.moduleName('common/aurelia-components/dual-video-player/dual-video-player'));\n}\n","import { autoinject, bindable, computedFrom, PLATFORM, useView } from 'aurelia-framework';\nimport { FocusTrap } from 'common/aurelia-classes/FocusTrap';\nimport { SubscriptionManager, SubscriptionManagerService } from 'common/aurelia-services/SubscriptionManagerService';\nimport { KeyboardKey } from 'common/enum/keyboard.key.enum';\nimport { assertNotNullOrUndefined } from 'common/utils/assert';\nimport { DomUtils } from 'common/utils/DomUtils';\nimport { ExamMediaUtils } from 'common/utils/ExamMediaUtils';\nimport pinchit, { IPinchIt } from 'pinchit';\nimport { ContentRect } from 'resize-observer/lib/ContentRect';\n\n/**\n * @event close-exam-image-overlay\n */\n@autoinject()\n@useView(PLATFORM.moduleName('./exam-image-overlay.html'))\nexport class ExamImageOverlay {\n  @bindable() public altText: string | null = null;\n  @bindable() public blinkImgSrc: string | null = null;\n  @bindable() public imgSrc: string | null = null;\n\n  private blinkImageInterval: number | undefined = undefined;\n  private imageToDisplay: string | null = null;\n  private readonly element: HTMLElement;\n  private pinchIt: IPinchIt | null = null;\n  private domImageContainerElement: HTMLElement | null = null;\n  private domImageElement: HTMLImageElement | null = null;\n  private subscriptionManager: SubscriptionManager;\n  private focusTrap: FocusTrap | null = null;\n\n  private availableSpace: ContentRect | null = null;\n  private currentImageLoaded = false;\n  private imageNaturalWidth = 0;\n  private imageNaturalHeight = 0;\n\n  constructor(\n    element: Element,\n    private subscriptionManagerService: SubscriptionManagerService,\n  ) {\n    this.subscriptionManager = subscriptionManagerService.createSubscriptionManager();\n    this.element = element as HTMLElement;\n  }\n\n  protected attached(): void {\n    assertNotNullOrUndefined(this.domImageContainerElement, 'forgot to ref domImageContainerElement');\n    this.imageToDisplay = this.imgSrc;\n    if(this.blinkImgSrc) {\n      this.subscriptionManager.subscribeToInterval(() => this.setUpBlinkImage(), ExamMediaUtils.blinkImageTimeout);\n    }\n    this.subscriptionManager.subscribeToKeyboardEvent(window, KeyboardKey.Escape, () => {\n      this.closeOverlay();\n    });\n    this.subscriptionManager.subscribeToElementResize(this.element, rect => {\n      this.availableSpace = rect;\n      this.fitImage();\n    });\n    this.pinchIt = pinchit(this.domImageContainerElement, {});\n    this.getOrCreateFocusTrap()?.setup();\n  }\n\n  protected detached(): void {\n    this.subscriptionManager.disposeSubscriptions();\n    this.pinchIt?.destroy();\n    this.getOrCreateFocusTrap()?.destroy();\n  }\n\n  private closeOverlay(): void {\n    this.getOrCreateFocusTrap()?.destroy();\n    DomUtils.fireEvent(this.element, {\n      bubbles: true,\n      name: 'close-exam-image-overlay',\n    });\n  }\n\n  private setUpBlinkImage(): void {\n    if(this.imageToDisplay === this.imgSrc) {\n      this.imageToDisplay = this.blinkImgSrc;\n    } else {\n      this.imageToDisplay = this.imgSrc;\n    }\n  }\n\n  private fitImage(): void {\n    assertNotNullOrUndefined(this.domImageElement, 'forgot to ref domImageElement');\n    if(!this.availableSpace) return;\n    if(!this.currentImageLoaded) return;\n    const aspectRatio = this.imageNaturalWidth / (this.imageNaturalHeight || 1);\n    let targetWidth = (this.imageNaturalWidth > this.availableSpace.width || this.isSvg) ? this.availableSpace.width : this.imageNaturalWidth;\n    let targetHeight = targetWidth / aspectRatio;\n    if(targetHeight > this.availableSpace.height) {\n      targetHeight = this.availableSpace.height;\n      targetWidth = this.availableSpace.height * aspectRatio;\n    }\n    this.domImageElement.style.width = `${targetWidth}px`;\n    this.domImageElement.style.height = `${targetHeight}px`;\n  }\n\n  @computedFrom('imgSrc')\n  private get isSvg(): boolean {\n    return this.imgSrc?.includes('.svg') ?? false;\n  }\n\n  private imgSrcChanged(): void {\n    this.currentImageLoaded = false;\n  }\n\n  private onImageLoad(): void {\n    this.imageNaturalWidth = this.domImageElement?.naturalWidth ?? 0;\n    this.imageNaturalHeight = this.domImageElement?.naturalHeight ?? 0;\n    if(this.currentImageLoaded) return;\n    this.currentImageLoaded = true;\n    this.fitImage();\n  }\n\n  private getOrCreateFocusTrap(): FocusTrap | null {\n    if(this.element && !this.focusTrap) {\n      this.focusTrap = new FocusTrap(this.element, this.subscriptionManagerService);\n    }\n    return this.focusTrap;\n  }\n}\n","import { PLATFORM, FrameworkConfiguration } from 'aurelia-framework';\n\nexport function configure(config: FrameworkConfiguration): void {\n  config.globalResources(PLATFORM.moduleName('common/aurelia-components/exam-image-overlay/exam-image-overlay'));\n}\n","import { autoinject, bindable, PLATFORM, useView } from 'aurelia-framework';\nimport { ExamMediaUtils } from '../../utils/ExamMediaUtils';\n\n/**\n * @event {OpenImageOverlayEvent} open-image-overlay\n */\n@autoinject()\n@useView(PLATFORM.moduleName('./exam-image.html'))\nexport class ExamImage {\n  @bindable() public altText: string | null = null;\n  @bindable() public blinkImgSrc: string | null = null;\n  @bindable() public imgSrc: string | null = null;\n  @bindable() public blurHash: string | null = null;\n  @bindable() public blinkBlurHash: string | null = null;\n  @bindable() public enableZoom = false;\n  @bindable() public blinkEnabled = true;\n\n  private blinkImageInterval: number | null = null;\n  private blink = false;\n  private imageLoading = true;\n  private blinkImageLoading = true;\n\n  private readonly element: HTMLElement;\n\n  constructor(element: Element) {\n    this.element = element as HTMLElement;\n  }\n\n  protected attached(): void {\n    this.imageLoading = true;\n    if(this.blinkImgSrc) this.blinkImageLoading = true;\n    this.blink = false;\n    this.blinkEnabledChanged();\n  }\n\n  protected detached(): void {\n    if(this.blinkImageInterval) window.clearInterval(this.blinkImageInterval);\n  }\n\n  private blinkEnabledChanged(): void {\n    if(this.blinkImageInterval) window.clearInterval(this.blinkImageInterval);\n    if(this.blinkEnabled && this.element.isConnected) {\n      this.blinkImageInterval = window.setInterval(() => {\n        this.toggleImage();\n      }, ExamMediaUtils.blinkImageTimeout);\n    }\n  }\n\n  private toggleImage(): void {\n    this.blink = this.blinkImgSrc\n      ? !this.blink\n      : false;\n  }\n\n  private imgSrcChanged(): void {\n    this.imageLoading = true;\n  }\n\n  private blinkImgSrcChanged(): void {\n    if(this.blinkImgSrc) this.blinkImageLoading = true;\n    else {\n      this.blinkImageLoading = false;\n      this.blink = false;\n    }\n  }\n\n  private openImageOverlay(): void {\n    if(!this.enableZoom || !this.imgSrc) {\n      return;\n    }\n\n    ExamMediaUtils.emitOpenImageOverlayEvent(this.element, { blinkImgSrc: this.blinkImgSrc, imgSrc: this.imgSrc });\n  }\n\n  private handleImageLoaded(): void {\n    this.imageLoading = false;\n  }\n\n  private handleBlinkImageLoaded(): void {\n    this.blinkImageLoading = false;\n  }\n}\n","import { PLATFORM, FrameworkConfiguration } from 'aurelia-framework';\n\nexport function configure(config: FrameworkConfiguration): void {\n  config.globalResources(PLATFORM.moduleName('common/aurelia-components/exam-image/exam-image'));\n}\n"],"names":[],"sourceRoot":""}