{"version":3,"file":"app-d8fd8614.f584a1446b2fc034d810.bundle.js","mappings":"AAGA","sources":["webpack://lernplattform-admin-frontend/./src/resources/elements/info-icon/info-icon.html","webpack://lernplattform-admin-frontend/./src/resources/elements/inline-edit-field/inline-edit-field.html","webpack://lernplattform-admin-frontend/./src/resources/elements/layout-container-inside-dropzone-with-target/layout-container-inside-dropzone-with-target.html","webpack://lernplattform-admin-frontend/./src/resources/elements/layout-container-outside-dropzone-with-target/layout-container-outside-dropzone-with-target.html","webpack://lernplattform-admin-frontend/./src/resources/elements/layout-container-resizer/layout-container-resizer.html","webpack://lernplattform-admin-frontend/./src/resources/elements/layout-footer/layout-footer.html","webpack://lernplattform-admin-frontend/./src/resources/elements/learning-journey-course-structure/learning-journey-course-structure.html","webpack://lernplattform-admin-frontend/./src/resources/elements/lecture-button/lecture-button.html","webpack://lernplattform-admin-frontend/./src/resources/elements/lecture-quiz-final-result/lecture-quiz-final-result.html","webpack://lernplattform-admin-frontend/./src/resources/elements/lecture-quiz-intermediate-result/lecture-quiz-intermediate-result.html","webpack://lernplattform-admin-frontend/./src/resources/elements/lecture-quiz-loading/lecture-quiz-loading.html","webpack://lernplattform-admin-frontend/./src/resources/elements/lecture-quiz-question/lecture-quiz-question.html","webpack://lernplattform-admin-frontend/./src/resources/elements/lecture-quiz-slide/lecture-quiz-slide.html","webpack://lernplattform-admin-frontend/./src/resources/elements/lecture-quiz-start/lecture-quiz-start.html","webpack://lernplattform-admin-frontend/./src/resources/elements/lecture-side-menu/lecture-side-menu-attendee-list/lecture-side-menu-attendee-list.html","webpack://lernplattform-admin-frontend/./src/resources/elements/lecture-side-menu/lecture-side-menu-attendee/lecture-side-menu-attendee.html","webpack://lernplattform-admin-frontend/./src/resources/elements/lecture-side-menu/lecture-side-menu-chat/lecture-side-menu-chat.html","webpack://lernplattform-admin-frontend/./src/resources/elements/lecture-side-menu/lecture-side-menu.html","webpack://lernplattform-admin-frontend/./src/resources/elements/lecturer-events-table/lecturer-events-table.html","webpack://lernplattform-admin-frontend/./src/resources/elements/lecturer-lecture-quiz/lecturer-lecture-quiz.html","webpack://lernplattform-admin-frontend/./src/resources/elements/login-status-widget/login-status-widget.html","webpack://lernplattform-admin-frontend/./src/resources/elements/markdownarea/markdownarea.html","webpack://lernplattform-admin-frontend/./src/resources/elements/matrix-table/matrix-table.html","webpack://lernplattform-admin-frontend/./src/resources/elements/info-icon/info-icon.ts","webpack://lernplattform-admin-frontend/./src/resources/elements/inline-edit-field/index.ts","webpack://lernplattform-admin-frontend/./src/resources/elements/inline-edit-field/inline-edit-field.ts","webpack://lernplattform-admin-frontend/./src/resources/elements/layout-container-inside-dropzone-with-target/layout-container-inside-dropzone-with-target.ts","webpack://lernplattform-admin-frontend/./src/resources/elements/layout-container-outside-dropzone-with-target/layout-container-outside-dropzone-with-target.ts","webpack://lernplattform-admin-frontend/./src/resources/elements/layout-container-resizer/layout-container-resizer.ts","webpack://lernplattform-admin-frontend/./src/resources/elements/layout-footer/layout-footer.ts","webpack://lernplattform-admin-frontend/./src/resources/elements/learning-journey-course-structure/learning-journey-course-structure.ts","webpack://lernplattform-admin-frontend/./src/resources/elements/lecture-button/lecture-button.ts","webpack://lernplattform-admin-frontend/./src/resources/elements/lecture-quiz-final-result/lecture-quiz-final-result.ts","webpack://lernplattform-admin-frontend/./src/resources/elements/lecture-quiz-intermediate-result/lecture-quiz-intermediate-result.ts","webpack://lernplattform-admin-frontend/./src/resources/elements/lecture-quiz-loading/lecture-quiz-loading.ts","webpack://lernplattform-admin-frontend/./src/resources/elements/lecture-quiz-question/lecture-quiz-question.ts","webpack://lernplattform-admin-frontend/./src/resources/elements/lecture-quiz-slide/lecture-quiz-slide.ts","webpack://lernplattform-admin-frontend/./src/resources/elements/lecture-quiz-start/lecture-quiz-start.ts","webpack://lernplattform-admin-frontend/./src/resources/elements/lecture-side-menu/index.ts","webpack://lernplattform-admin-frontend/./src/resources/elements/lecture-side-menu/lecture-side-menu-attendee-list/lecture-side-menu-attendee-list.ts","webpack://lernplattform-admin-frontend/./src/resources/elements/lecture-side-menu/lecture-side-menu-attendee/lecture-side-menu-attendee.ts","webpack://lernplattform-admin-frontend/./src/resources/elements/lecture-side-menu/lecture-side-menu-chat/lecture-side-menu-chat.ts","webpack://lernplattform-admin-frontend/./src/resources/elements/lecture-side-menu/lecture-side-menu.ts","webpack://lernplattform-admin-frontend/./src/resources/elements/lecturer-events-table/lecturer-events-table.ts","webpack://lernplattform-admin-frontend/./src/resources/elements/lecturer-lecture-quiz/lecturer-lecture-quiz.ts","webpack://lernplattform-admin-frontend/./src/resources/elements/login-status-widget/login-status-widget.ts","webpack://lernplattform-admin-frontend/./src/resources/elements/markdownarea/markdownarea.ts","webpack://lernplattform-admin-frontend/./src/resources/elements/matrix-table/matrix-table.ts"],"sourcesContent":["// Module\nvar code = \"<template class=\\\"info-icon\\\"> <safe-icon class=\\\"info-icon__icon\\\" icon-name.to-view=\\\"iconName\\\" ref=\\\"iconDomElement\\\"></safe-icon> <x-tooltip for-element.to-view=\\\"iconDomElement\\\">${infoText}</x-tooltip> </template> \";\n// Exports\nexport default code;","// Module\nvar code = \"<template class=\\\"inline-edit-field inline-edit-field--mode-${mode} ${ showEditBorder ? 'inline-edit-field--edit-border' : '' } ${ isPlaceholder ? 'inline-edit-field--placeholder' : '' }\\\" click.trigger=\\\"mode !== Mode.Button && startEditing()\\\"> <div ref=\\\"inputElement\\\" tabindex=\\\"0\\\" class=\\\"inline-edit-field__input ${ isEditing ? 'inline-edit-field__input--editing' : '' }\\\" style=\\\"--inline-edit-field-placeholder:'\\\\200b ${ placeholderTk | t }'\\\" contenteditable.to-view=\\\"isEditing\\\" textcontent.two-way=\\\"currentValue\\\" focus.trigger=\\\"startEditing()\\\" blur.trigger=\\\"mode !== Mode.Button && isEditing && saveChange()\\\"></div> <template if.to-view=\\\"mode === Mode.Click\\\"> <safe-icon class=\\\"inline-edit-field__editable-indicator\\\" icon-name=\\\"System/Edit2\\\"></safe-icon> </template> <template if.to-view=\\\"mode === Mode.Button\\\"> <safe-icon if.to-view=\\\"!isEditing\\\" class=\\\"inline-edit-field__button\\\" click.trigger=\\\"startEditing()\\\" icon-name=\\\"System/Tabler/Edit\\\"></safe-icon> <safe-icon if.to-view=\\\"isEditing\\\" class=\\\"inline-edit-field__button\\\" click.trigger=\\\"saveChange()\\\" icon-name=\\\"System/Tabler/Check\\\"></safe-icon> <safe-icon if.to-view=\\\"isEditing\\\" class=\\\"inline-edit-field__button\\\" click.trigger=\\\"cancelEditing()\\\" icon-name=\\\"System/Tabler/X\\\"></safe-icon> </template> </template> \";\n// Exports\nexport default code;","// Module\nvar code = \"<template> <div class=\\\"layout-container-inside-dropzone-with-target layout-container-inside-dropzone-with-target--direction-${direction} layout-container-inside-dropzone-with-target--direction-${direction}-${dropzoneConfig.sequentialPosition} ${ isTargeted ? 'layout-container-inside-dropzone-with-target--targeted' : '' }\\\"> <drop-zone class=\\\"layout-container-inside-dropzone-with-target__dropzone layout-container-inside-dropzone-with-target__dropzone--${direction} layout-container-inside-dropzone-with-target__dropzone--${dropzoneConfig.sequentialPosition}\\\" config.to-view=\\\"dropzoneConfig\\\" context.to-view=\\\"dndContext\\\" dnd-active.to-view=\\\"dndActive\\\"> <div if.to-view=\\\"showTarget || isTargeted\\\" class=\\\"layout-container-inside-dropzone-with-target__target layout-container-inside-dropzone-with-target__target--${direction}\\\"></div> </drop-zone> </div> </template> \";\n// Exports\nexport default code;","// Module\nvar code = \"<template> <div class=\\\"layout-container-outside-dropzone-with-target ${ isTargeted ? 'layout-container-outside-dropzone-with-target--targeted' : '' }\\\"> <drop-zone config.to-view=\\\"dropzoneConfig\\\" context.to-view=\\\"dndContext\\\" dnd-active.to-view=\\\"dndActive\\\"> <div if.to-view=\\\"showTarget || isTargeted\\\" class=\\\"layout-container-outside-dropzone-with-target__target layout-container-outside-dropzone-with-target__target--${dropzoneConfig.insertModeCardinal}\\\"></div> </drop-zone> </div> </template> \";\n// Exports\nexport default code;","// Module\nvar code = \"<template class=\\\"layout-container-resizer layout-container-resizer--direction-${direction} layout-container-resizer${showHandleClass} ${ !isDisabled ? 'layout-container-resizer__allow-hover' : '' }\\\"> <div class=\\\"layout-container-resizer__handle-frame layout-container-resizer__handle-frame--${direction}\\\"> <div class=\\\"layout-container-resizer__icon-wrapper\\\"> <safe-icon class=\\\"layout-container-resizer__edit-icon\\\" click.trigger=\\\"handleEditIconClicked()\\\" icon-name=\\\"System/Tabler/Edit\\\"></safe-icon> </div> <div class=\\\"layout-container-resizer__handle layout-container-resizer__handle--${direction}\\\"> <div class=\\\"layout-container-resizer__handle-ghost layout-container-resizer__handle-ghost--${direction}\\\" mousedown.trigger=\\\"handleHandleMousedown()\\\"></div> </div> <div if.to-view=\\\"canApply\\\" class=\\\"layout-container-resizer__icon-wrapper\\\"> <safe-icon class=\\\"layout-container-resizer__edit-icon\\\" click.trigger=\\\"handleApplyIconClicked()\\\" icon-name=\\\"Distinar/WhiteboardUndo\\\"></safe-icon> </div> </div> </template>\";\n// Exports\nexport default code;","// Module\nvar code = \"<template class=\\\"layout-footer\\\"> <div class=\\\"layout-footer__external-links mb-1\\\"> <a route-href=\\\"route: login/data-protection\\\" class=\\\"layout-footer__external-link-text\\\" t=\\\"globals.termsOfUse\\\"></a> <span class=\\\"layout-footer__external-link-separator\\\">.</span> <a href.to-view=\\\"legalNoticeLink\\\" target=\\\"_blank\\\" class=\\\"layout-footer__external-link-text\\\" t=\\\"elements.layoutFooter.legalNotice\\\"></a> <span class=\\\"layout-footer__external-link-separator\\\">.</span> <a href.to-view=\\\"dataPrivacyAgreementLink\\\" target=\\\"_blank\\\" class=\\\"layout-footer__external-link-text\\\" t=\\\"globals.dataPrivacyAgreement\\\"></a> </div> <version-info class=\\\"text-center mb-1 layout-footer__version-info\\\"></version-info> </template>\";\n// Exports\nexport default code;","// Module\nvar code = \"<template class=\\\"learning-journey-course-structure\\\"> <div repeat.for=\\\"course of courses\\\" class=\\\"learning-journey-course-structure__single-element\\\"> <draggable class=\\\"learning-journey-course-structure__draggable\\\" payload.to-view=\\\"{ existingPayloadId: course.learningJourneyCourseId }\\\" dnd-active.to-view=\\\"dndActive\\\" context.to-view=\\\"dndContext\\\"> <div class=\\\"card learning-journey-course-structure__course-list-item px-5 ${ course.learningJourneyCourseId === dndPayload.existingPayloadId ? 'learning-journey-course-structure__course-list-item--ghost' : '' }\\\"> <safe-icon class=\\\"learning-journey-course-structure__course-list-icon learning-journey-course-structure__course-list-icon--drag mr-2\\\" icon-name=\\\"System/DragAlt\\\"></safe-icon> <div class=\\\"learning-journey-course-structure__course-list-text\\\"> ${course.title} </div> <div if.to-view=\\\"course.learningJourneyCourseToCompleteIds.length\\\" class=\\\"d-flex align-items-center mr-6 text-muted\\\"> <safe-icon class=\\\"learning-journey-course-structure__course-list-icon mr-2\\\" icon-name=\\\"System/Tabler/Requirement\\\"></safe-icon> <small class=\\\"form-text\\\" t=\\\"pages.course.requirementActive\\\"></small> </div> <safe-icon class=\\\"learning-journey-course-structure__course-list-icon mr-2\\\" icon-name=\\\"System/Tabler/Dots-Vertical\\\" role=\\\"button\\\" click.trigger=\\\"handleShowCourseOptionsClick(course)\\\"> </safe-icon> <safe-icon class=\\\"learning-journey-course-structure__course-list-icon learning-journey-course-structure__course-list-icon--delete\\\" icon-name=\\\"System/Tabler/Trash\\\" role=\\\"button\\\" click.trigger=\\\"handleRemoveCourseClick(course)\\\"></safe-icon> </div> </draggable> <drop-zone if.to-view=\\\"$first\\\" class=\\\"learning-journey-course-structure__drop-zone learning-journey-course-structure__drop-zone--before learning-journey-course-structure__drop-zone--first learning-journey-course-structure__drop-zone--${ dndTargetDisplaySettings[$index].before } ${ dndActive && dndTargetDisplaySettings[$index].before !== DndTargetDisplay.Hidden ? 'learning-journey-course-structure__drop-zone--front' : '' }\\\" dnd-active.to-view=\\\"dndActive\\\" context.to-view=\\\"dndContext\\\" config.to-view=\\\"getDropzoneConfig($index, DndInsertModeSequential.Before)\\\" provide-dropzone-identifiers.to-view=\\\"false\\\"></drop-zone> <drop-zone class=\\\"learning-journey-course-structure__drop-zone learning-journey-course-structure__drop-zone--after learning-journey-course-structure__drop-zone--${ dndTargetDisplaySettings[$index].after } ${ dndActive && dndTargetDisplaySettings[$index].after !== DndTargetDisplay.Hidden ? 'learning-journey-course-structure__drop-zone--front' : '' } ${ $last ? 'learning-journey-course-structure__drop-zone--last' : '' }\\\" dnd-active.to-view=\\\"dndActive\\\" context.to-view=\\\"dndContext\\\" config.to-view=\\\"getDropzoneConfig($index, DndInsertModeSequential.After)\\\" provide-dropzone-identifiers.to-view=\\\"false\\\"></drop-zone> </div> <separator class=\\\"learning-journey-course-structure__course-list-separator\\\" text-tk=\\\"pages.learning-journey-edit.addCourse\\\" click.delegate=\\\"handleAddCourseClick()\\\"></separator> </template>\";\n// Exports\nexport default code;","// Module\nvar code = \"<template class=\\\"lecture-button lecture-button--${type} ${toggled ? `lecture-button--toggled` : ``} ${disabled ? `lecture-button--disabled` : ``}\\\" role=\\\"button\\\" click.capture=\\\"handleClick($event)\\\"> <slot></slot> </template> \";\n// Exports\nexport default code;","// Module\nvar code = \"<template class=\\\"lecture-quiz-final-result\\\"> <course-lecture-quiz-header class=\\\"lecture-quiz-final-result__header\\\" contrast-mode.to-view=\\\"contrastMode\\\" button-tk=\\\"elements.lectureQuizFinalResult.closeQuiz\\\" button-clicked.trigger=\\\"closeQuiz()\\\" show-button.to-view=\\\"showHeaderButton\\\"></course-lecture-quiz-header> <div class=\\\"lecture-quiz-final-result__title\\\" t=\\\"elements.lectureQuizFinalResult.title\\\"></div> <div repeat.for=\\\"item of podiumItems\\\" class=\\\"lecture-quiz-final-result__podium-container lecture-quiz-final-result__podium-container--position${item.position} lecture-quiz-final-result__podium-container--index${$index}\\\"> <div class=\\\"lecture-quiz-final-result__podium-player ${showPlayer[item.position] ? 'lecture-quiz-final-result__podium-player--show' : ''}\\\"> <avatar-icon class=\\\"lecture-quiz-final-result__podium-avatar\\\" size.to-view=\\\"`unspecified`\\\" avatar-svg-download-link.to-view=\\\"item.avatarSvgDownloadLink\\\"></avatar-icon> <div if.to-view=\\\"item.nickname\\\" class=\\\"lecture-quiz-final-result__podium-name\\\">${item.nickname}</div> <div else class=\\\"lecture-quiz-final-result__podium-name\\\" t=\\\"pages.lectureQuiz.anonymousPlayer\\\"></div> <div class=\\\"lecture-quiz-final-result__podium-points\\\" t-params.to-view=\\\"{count: item.points}\\\" t=\\\"elements.lectureQuizFinalResult.points\\\"></div> </div> <div class=\\\"lecture-quiz-final-result__podium-block ${showPodium[item.position] ? 'lecture-quiz-final-result__podium-block--show' : ''}\\\">${item.position}</div> <lottie-animation-player if.to-view=\\\"item.position === 1 && showPlayer[1]\\\" class=\\\"lecture-quiz-final-result__podium-popper\\\" animation-data.to-view=\\\"partyPopperAnimation\\\" autoplay.to-view=\\\"true\\\" loop.to-view=\\\"false\\\"></lottie-animation-player> </div> </template>\";\n// Exports\nexport default code;","// Module\nvar code = \"<template class=\\\"lecture-quiz-intermediate-result lecture-quiz-intermediate-result--${questionState}\\\"> <course-lecture-quiz-header class=\\\"lecture-quiz-intermediate-result__header\\\" progress.to-view=\\\"progress\\\" contrast-mode.to-view=\\\"contrastMode\\\" text-tk=\\\"elements.lectureQuizIntermediateResult.questionXofY\\\" text-tk-params.to-view=\\\"{index: questionIndex + 1, total: questionCount}\\\" button-tk=\\\"elements.lectureQuizIntermediateResult.nextQuestion\\\" button-clicked.trigger=\\\"handleHeaderButtonClicked()\\\" show-button.to-view=\\\"showHeaderButton\\\"></course-lecture-quiz-header> <div class=\\\"lecture-quiz-intermediate-result__title\\\" t=\\\"elements.lectureQuizIntermediateResult.title\\\"></div> <list-standard class=\\\"lecture-quiz-intermediate-result__score-board\\\" items.to-view=\\\"scoreBoardItems\\\" enumeration.to-view=\\\"true\\\"></list-standard> </template>\";\n// Exports\nexport default code;","// Module\nvar code = \"<template class=\\\"lecture-quiz-loading\\\"> <course-lecture-quiz-header class=\\\"lecture-quiz-loading__header\\\" contrast-mode.to-view=\\\"contrastMode\\\" progress.to-view=\\\"progress\\\"></course-lecture-quiz-header> <div class=\\\"lecture-quiz-loading__questions\\\" t-params.to-view=\\\"{count: questionCount}\\\" t=\\\"elements.lectureQuizLoading.questions\\\"></div> <div class=\\\"lecture-quiz-loading__title\\\" t=\\\"elements.lectureQuizLoading.title\\\"></div> <div class=\\\"lecture-quiz-loading__loading\\\" t-params.to-view=\\\"{percent: percentLoaded}\\\" t=\\\"elements.lectureQuizLoading.loadingPercent\\\"></div> </template>\";\n// Exports\nexport default code;","// Module\nvar code = \"<template class=\\\"lecture-quiz-question lecture-quiz-question--${questionState} lecture-quiz-question--contrast-${contrastMode}\\\"> <course-lecture-quiz-header class=\\\"lecture-quiz-question__header\\\" contrast-mode.to-view=\\\"contrastMode\\\" progress.to-view=\\\"progress\\\" text-tk=\\\"elements.lectureQuizQuestion.questionXofY\\\" text-tk-params.to-view=\\\"{index: questionIndex + 1, total: questionCount}\\\" button-tk.to-view=\\\"headerButtonTk\\\" button-clicked.trigger=\\\"handleHeaderButtonClicked()\\\" show-button.to-view=\\\"showHeaderButton\\\" show-progress.to-view=\\\"showProgress\\\"></course-lecture-quiz-header> <div class=\\\"lecture-quiz-question__question-text ${ question.type !== QuestionType.LT && question.pictureUrl ? 'lecture-quiz-question__question-text--with-image' : '' }\\\"> ${questionText}</div> <safe-icon if.to-view=\\\"showHeaderButton && helpText\\\" class=\\\"lecture-quiz-question__help-text-icon\\\" icon-name=\\\"System/LightBulb\\\" t=\\\"[title]elements.lectureQuizQuestion.showHelp\\\" click.trigger=\\\"handleHelpTextButtonClicked()\\\"></safe-icon> <lottie-animation-player class=\\\"lecture-quiz-question__animation-player ${ question.type !== QuestionType.LT && question.pictureUrl ? 'lecture-quiz-question__animation-player--with-image' : '' }\\\" view-model.ref=\\\"lottieAnimationPlayer\\\" animation-data.to-view=\\\"animationData\\\" lottie-animation-ready.trigger=\\\"onAnimationReady()\\\" autoplay.to-view=\\\"false\\\"></lottie-animation-player> <div class=\\\"lecture-quiz-question__answer-count lecture-quiz-question__answer-count--correct\\\"> <div class=\\\"lecture-quiz-question__answer-count-number\\\">${answerStats.correctCount}</div> <div class=\\\"lecture-quiz-question__answer-count-text\\\" t=\\\"elements.lectureQuizQuestion.correct\\\"></div> </div> <div class=\\\"lecture-quiz-question__answer-count lecture-quiz-question__answer-count--wrong\\\"> <div class=\\\"lecture-quiz-question__answer-count-number\\\">${answerStats.wrongCount}</div> <div class=\\\"lecture-quiz-question__answer-count-text\\\" t=\\\"elements.lectureQuizQuestion.wrong\\\"></div> </div> <div class=\\\"lecture-quiz-question__answer-count lecture-quiz-question__answer-count--timeout\\\"> <div class=\\\"lecture-quiz-question__answer-count-number\\\">${timeoutCount}</div> <div class=\\\"lecture-quiz-question__answer-count-text\\\" t=\\\"elements.lectureQuizQuestion.timeout\\\"></div> </div> <div if.to-view=\\\"question.type === QuestionType.LT\\\" class=\\\"lecture-quiz-question__sub-question-index\\\" t-params.to-view=\\\"{index: answerIndex + 1, total: question.answers.length}\\\" t=\\\"elements.lectureQuizQuestion.subQuestionXofY\\\"></div> <exam-image if.to-view=\\\"question.type !== QuestionType.LT && question.pictureUrl\\\" class=\\\"lecture-quiz-question__image\\\" alt-text.to-view=\\\"question.text\\\" blink-img-src.to-view=\\\"question.pictureBlinkUrl\\\" img-src.to-view=\\\"question.pictureUrl\\\" blur-hash.to-view=\\\"question.pictureBlurHash\\\" blink-blur-hash.to-view=\\\"question.pictureBlinkBlurHash\\\" enable-zoom.to-view=\\\"true\\\" open-image-overlay.delegate=\\\"openImageOverlay($event)\\\"></exam-image> <possible-answers class=\\\"lecture-quiz-question__answers\\\" contrast-mode.to-view=\\\"contrastMode\\\" theme=\\\"lecturer\\\" question-type.to-view=\\\"question.type\\\" answers.to-view=\\\"answersToDisplay\\\" readonly.to-view=\\\"true\\\" pictures-enabled.to-view=\\\"true\\\" voice-over-enabled.to-view=\\\"false\\\" reveal-answer.to-view=\\\"questionState === LectureQuizState.Result\\\" open-image-overlay.delegate=\\\"openImageOverlay($event)\\\"></possible-answers> <div if.to-view=\\\"showHelpText\\\" class=\\\"lecture-quiz-question__help-text-container\\\"> <div class=\\\"lecture-quiz-question__help-text-title\\\" t=\\\"common.course-question-help-text.title\\\"></div> <div class=\\\"lecture-quiz-question__help-text\\\">${helpText}</div> <button class=\\\"btn btn-primary lecture-quiz-question__help-text-close-button\\\" click.delegate=\\\"handleHelpTextButtonClicked()\\\"> <span t=\\\"elements.lectureQuizQuestion.close\\\"></span> </button> </div> <exam-image-overlay if.to-view=\\\"displayImageOverlay\\\" alt-text.to-view=\\\"overlayImageAltText\\\" blink-img-src.to-view=\\\"overlayBlinkImageToDisplay\\\" click.delegate=\\\"closeImageOverlay()\\\" close-exam-image-overlay.delegate=\\\"closeImageOverlay()\\\" img-src.to-view=\\\"overlayImageToDisplay\\\"></exam-image-overlay> </template>\";\n// Exports\nexport default code;","// Module\nvar code = \"<template class=\\\"lecture-quiz-slide\\\" style.to-view=\\\"{ '--course-lecture-quiz-scale-factor': scaleFactor }\\\"> <aspect-ratio-container class=\\\"lecture-quiz-slide__aspect-ratio-container\\\" mode=\\\"width-and-height-dependent\\\"> <div class=\\\"lecture-quiz-slide__content-wrapper\\\" ref=\\\"quizContainer\\\"> <lecture-quiz-start if.to-view=\\\"!lectureQuizStatus.quiz || lectureStatus.quiz.state === LectureQuizState.Ready\\\" quiz-content.to-view=\\\"quizContent\\\" players.to-view=\\\"currentConferenceAttendees\\\" quiz-started.trigger=\\\"startQuiz()\\\" contrast-mode=\\\"high\\\" show-header-button.to-view=\\\"showHeaderButtons\\\"></lecture-quiz-start> <lecture-quiz-loading if.to-view=\\\"lectureStatus.quiz.state === LectureQuizState.Loading\\\" question-count.to-view=\\\"quizContent.questions.length\\\" progress.to-view=\\\"progress\\\" contrast-mode=\\\"high\\\" percent-loaded.to-view=\\\"percentLoaded\\\"></lecture-quiz-loading> <lecture-quiz-question if.to-view=\\\"shouldShowQuizQuestion(lectureStatus.quiz.state) && quizContent.questions[lectureStatus.quiz.questionIndex]\\\" question.to-view=\\\"quizContent.questions[lectureStatus.quiz.questionIndex]\\\" question-index.to-view=\\\"lectureStatus.quiz.questionIndex\\\" answer-index.to-view=\\\"lectureStatus.quiz.subQuestionIndex\\\" question-state.to-view=\\\"lectureStatus.quiz.state\\\" answer-stats.to-view=\\\"lectureStatus.quiz.lastQuestionAnswers\\\" progress.to-view=\\\"progress\\\" contrast-mode=\\\"high\\\" question-count.to-view=\\\"quizContent.questions.length\\\" show-help-text.to-view=\\\"lectureStatus.quiz.showHelpText\\\" next-question.trigger=\\\"handleContinueButtonClicked()\\\" toggle-help-text.trigger=\\\"handleHelpTextButtonClicked()\\\" show-header-button.to-view=\\\"showHeaderButtons\\\" player-count.one-time=\\\"currentConferenceAttendees.length\\\" answer-time.to-view=\\\"quizAnswerTime\\\"></lecture-quiz-question> <lecture-quiz-intermediate-result if.to-view=\\\"lectureStatus.quiz.state === LectureQuizState.IntermediateResult\\\" question-index.to-view=\\\"lectureStatus.quiz.questionIndex\\\" question-count.to-view=\\\"quizContent.questions.length\\\" scores.to-view=\\\"lectureStatus.quiz.scores\\\" players.to-view=\\\"allConferenceAttendees\\\" contrast-mode=\\\"high\\\" next-question.trigger=\\\"handleContinueButtonClicked()\\\" show-header-button.to-view=\\\"showHeaderButtons\\\"></lecture-quiz-intermediate-result> <lecture-quiz-final-result if.to-view=\\\"lectureStatus.quiz.state === LectureQuizState.FinalResult\\\" show-header-button.to-view=\\\"showHeaderButtons\\\" scores.to-view=\\\"lectureStatus.quiz.scores\\\" contrast-mode=\\\"high\\\" players.to-view=\\\"allConferenceAttendees\\\" quiz-closed.trigger=\\\"closeQuiz()\\\"></lecture-quiz-final-result> </div> </aspect-ratio-container> </template>\";\n// Exports\nexport default code;","// Module\nvar code = \"<template class=\\\"lecture-quiz-start\\\"> <course-lecture-quiz-header class=\\\"lecture-quiz-start__header\\\" text-tk.to-view=\\\"quizContent ? 'elements.lectureQuizReady.questions' : ''\\\" text-tk-params.to-view=\\\"{count: quizContent.questions.length}\\\" button-tk=\\\"elements.lectureQuizReady.startQuiz\\\" contrast-mode.to-view=\\\"contrastMode\\\" button-clicked.trigger=\\\"handleHeaderButtonClicked()\\\" show-button.to-view=\\\"showHeaderButton\\\"></course-lecture-quiz-header> <div class=\\\"lecture-quiz-start__title\\\" t=\\\"elements.lectureQuizReady.title\\\"></div> <div class=\\\"lecture-quiz-start__players\\\"> <div class=\\\"lecture-quiz-start__players-title\\\" t-params.to-view=\\\"{count: players.length}\\\" t=\\\"elements.lectureQuizReady.players\\\"></div> <list-standard class=\\\"lecture-quiz-start__players-list\\\" items.to-view=\\\"playersListItems\\\"></list-standard> </div> </template>\";\n// Exports\nexport default code;","// Module\nvar code = \"<template class=\\\"lecture-side-menu-attendee-list\\\"> <div class=\\\"lecture-side-menu-attendee-list__attendee-list\\\"> <span if.to-view=\\\"participatingAttendeeIds.length\\\" t=\\\"elements.lectureSideMenu.participatingAttendees\\\" class=\\\"lecture-side-menu-attendee-list__separator-title\\\"></span> <lecture-side-menu-attendee repeat.for=\\\"attendeeId of participatingAttendeeIds\\\" attendee-id.to-view=\\\"attendeeId\\\" tenant-id.to-view=\\\"tenantId\\\" lecture-id.to-view=\\\"activeLecture.id\\\" show-media-buttons.to-view=\\\"isConferenceActive\\\"></lecture-side-menu-attendee> <span if.to-view=\\\"notParticipatingAttendeeIds.length\\\" t=\\\"elements.lectureSideMenu.notParticipatingAttendees\\\" class=\\\"lecture-side-menu-attendee-list__separator-title\\\"></span> <lecture-side-menu-attendee repeat.for=\\\"attendeeId of notParticipatingAttendeeIds\\\" attendee-id.to-view=\\\"attendeeId\\\" tenant-id.to-view=\\\"tenantId\\\" lecture-id.to-view=\\\"activeLecture.id\\\" show-media-buttons.to-view=\\\"isConferenceActive\\\"></lecture-side-menu-attendee> </div> <div if.to-view=\\\"isConferenceActive\\\" class=\\\"lecture-side-menu-attendee-list__controls\\\"> <button if.to-view=\\\"!allVideoLocked\\\" class=\\\"btn btn-primary lecture-side-menu-attendee-list__lock-unlock-all-btn\\\" click.delegate=\\\"deactivateAndLockAllVideo()\\\"> <safe-icon class=\\\"lecture-side-menu-attendee-list__lock-unlock-all-btn-icon\\\" icon-name=\\\"Distinar/LecturerCamOff\\\"></safe-icon> <span t=\\\"elements.lectureSideMenu.deactivateAndLockAllVideo\\\"></span> </button> <button if.to-view=\\\"allVideoLocked\\\" class=\\\"btn btn-primary lecture-side-menu-attendee-list__lock-unlock-all-btn\\\" click.delegate=\\\"unlockAllVideo()\\\"> <safe-icon class=\\\"lecture-side-menu-attendee-list__lock-unlock-all-btn-icon\\\" icon-name=\\\"Distinar/LecturerCam\\\"></safe-icon> <span t=\\\"elements.lectureSideMenu.unlockAllVideo\\\"></span> </button> <button if.to-view=\\\"!allAudioLocked\\\" class=\\\"btn btn-primary lecture-side-menu-attendee-list__lock-unlock-all-btn\\\" click.delegate=\\\"deactivateAndLockAllAudio()\\\"> <safe-icon class=\\\"lecture-side-menu-attendee-list__lock-unlock-all-btn-icon\\\" icon-name=\\\"Distinar/LecturerMicOff\\\"> </safe-icon> <span t=\\\"elements.lectureSideMenu.deactivateAndLockAllAudio\\\"></span> </button> <button if.to-view=\\\"allAudioLocked\\\" class=\\\"btn btn-primary lecture-side-menu-attendee-list__lock-unlock-all-btn\\\" click.delegate=\\\"unlockAllAudio()\\\"> <safe-icon class=\\\"lecture-side-menu-attendee-list__lock-unlock-all-btn-icon\\\" icon-name=\\\"Distinar/LecturerMic\\\"></safe-icon> <span t=\\\"elements.lectureSideMenu.unlockAllAudio\\\"></span> </button> </div> </template>\";\n// Exports\nexport default code;","// Module\nvar code = \"<template class=\\\"lecture-side-menu-attendee\\\"> <avatar-icon avatar-svg-download-link.to-view=\\\"attendee.avatarSvgDownloadLink\\\" online.to-view=\\\"attendee.online\\\"></avatar-icon> <div class=\\\"lecture-side-menu-attendee__name\\\"> <span class=\\\"lecture-side-menu-attendee__real-name\\\">${attendee.lastname}, ${attendee.firstname}</span> <span class=\\\"lecture-side-menu-attendee__nickname\\\">${attendee.nickname}</span> </div> <div class=\\\"lecture-side-menu-attendee__controls\\\"> <div if.to-view=\\\"isPhysicallyPresent\\\" class=\\\"lecture-side-menu-attendee__caption\\\" t=\\\"elements.lectureSideMenu.isPhysicallyPresent\\\"></div> <template if.to-view=\\\"!isPhysicallyPresent && showMediaButtons\\\"> <icon-button class=\\\"lecture-side-menu-attendee__icon-btn lecture-side-menu-attendee__icon-btn--${attendee.online ? attendee.conferenceVideoState : `offline`}\\\" icon.to-view=\\\"videoStateIcon\\\" click.delegate=\\\"handleVideoButtonClicked()\\\"></icon-button> <icon-button class=\\\"lecture-side-menu-attendee__icon-btn lecture-side-menu-attendee__icon-btn--${attendee.online ? attendee.conferenceAudioState : `offline`}\\\" icon.to-view=\\\"audioStateIcon\\\" click.delegate=\\\"handleAudioButtonClicked()\\\"></icon-button> </template> <div if.to-view=\\\"attendee.isHandRaised\\\" class=\\\"lecture-side-menu-attendee__icon-hand-raised-wrapper\\\"> <icon-button class=\\\"lecture-side-menu-attendee__icon-hand-raised\\\" icon.to-view=\\\"`Distinar/RaiseHandFilled`\\\" click.delegate=\\\"handleLowerHandButtonClicked()\\\"></icon-button> </div> <icon-button else class=\\\"lecture-side-menu-attendee__icon-btn lecture-side-menu-attendee__icon-btn--active\\\" icon=\\\"Distinar/LecturerEdit\\\" click.delegate=\\\"showManageAttendanceDialog()\\\"></icon-button> </div> <div class=\\\"lecture-side-menu-attendee__divider\\\"></div> </template>\";\n// Exports\nexport default code;","// Module\nvar code = \"<template class=\\\"lecture-side-menu-chat\\\"> <chat-view class=\\\"lecture-side-menu-chat__chat-view\\\" chat-room.to-view=\\\"chatRoom\\\" current-user-id.to-view=\\\"currentUserId\\\" messaging-allowed.to-view=\\\"messagingAllowed\\\" can-upload-files.to-view=\\\"true\\\" can-toggle-anonymous-message.to-view=\\\"true\\\"></chat-view> </template> \";\n// Exports\nexport default code;","// Module\nvar code = \"<template class=\\\"lecture-side-menu ${isOpen ? `lecture-side-menu--open` : ``}\\\"> <div class=\\\"lecture-side-menu__wrapper\\\"> <div class=\\\"lecture-side-menu__header\\\"> <div repeat.for=\\\"tab of shownTabs\\\" class=\\\"lecture-side-menu__tab ${tab === activeTab ? 'lecture-side-menu__tab--active' : ''} ${tabs[tab].hasUpdates ? 'lecture-side-menu__tab--has-updates' : ''}\\\" t.to-view=\\\"tabs[tab].titleTk\\\" click.delegate=\\\"handleTabClicked(tab)\\\"></div> <icon-button class=\\\"lecture-side-menu__close-button\\\" icon=\\\"System/Tabler/X\\\" click.delegate=\\\"closeSideMenu()\\\"></icon-button> </div> <div class=\\\"lecture-side-menu__content\\\"> <lecture-side-menu-chat if.to-view=\\\"activeTab === LectureSideMenuSectionName.Chat && isOpen\\\" current-user-id.to-view=\\\"currentUserId\\\"></lecture-side-menu-chat> <lecture-side-menu-attendee-list if.to-view=\\\"activeTab === LectureSideMenuSectionName.AttendeeList\\\" active-lecture.to-view=\\\"activeLecture\\\" is-conference-active.to-view=\\\"isConferenceActive\\\"></lecture-side-menu-attendee-list> </div> </div> </template>\";\n// Exports\nexport default code;","// Module\nvar code = \"<template class=\\\"lecturer-events-table\\\"> <custom-table columns.to-view=\\\"tableColumns\\\" table-id.to-view=\\\"showFilters ? `lecturer-events-table` : null\\\" extendable.to-view=\\\"showFilters\\\" show-filters.to-view=\\\"showFilters\\\" get-shown-entries-data.to-view=\\\"fetchTableDataDelegate\\\" hide-footer.to-view=\\\"hideFooter\\\" allow-sorting.to-view=\\\"allowSorting\\\" refresh-version.to-view=\\\"tableRefreshVersion\\\" prevent-fetching.to-view=\\\"preventFetching\\\"></custom-table> </template>\";\n// Exports\nexport default code;","// Module\nvar code = \"<template class=\\\"lecturer-lecture-quiz\\\"> <fullscreenable-container class=\\\"lecturer-lecture-quiz__quiz-container ${isFullscreen ? 'lecturer-lecture-quiz__quiz-container--fullscreen' : ''}\\\" view-model.ref=\\\"fullscreenableContainer\\\" mousemove.trigger=\\\"handleMousemove($event)\\\" is-fullscreen.from-view=\\\"isFullscreen\\\"> <lecture-quiz-slide lecture-status.to-view=\\\"lectureStatus\\\" class=\\\"lecturer-lecture-quiz__slide\\\"></lecture-quiz-slide> <div if.to-view=\\\"isFullscreen\\\" class=\\\"lecturer-lecture-quiz__controls ${(mouseMovedRecently || mouseInsideControlElement) ? 'lecturer-lecture-quiz__controls--visible' : ''}\\\" data-control-element=\\\"data-control-element\\\"> <button class=\\\"btn btn-secondary\\\" click.delegate=\\\"handleMinimizeClick()\\\"> <i class=\\\"fe fe-x\\\"></i> <span t=\\\"elements.lecturerLectureQuiz.exitFullscreen\\\"></span> </button> </div> </fullscreenable-container> </template> \";\n// Exports\nexport default code;","// Module\nvar code = \"<template> <div class=\\\"dropdown login-status-widget\\\"> <a href=\\\"#\\\" class=\\\"nav-link pr-0 leading-none login-status-widget__dropdown-anchor\\\" data-toggle=\\\"dropdown\\\" click.delegate=\\\"toggleMenu()\\\"> <span class=\\\"ml-2\\\"> <span class=\\\"text-default\\\">${ tenantUserName }</span> <small if.to-view=\\\"state.user.isProvider\\\" class=\\\"text-muted d-block mt-1\\\">${ PROVIDER_NAME } (<span t=\\\"globals.provider\\\"></span>)</small> <small if.to-view=\\\"state.user.isOperator\\\" class=\\\"text-muted d-block mt-1\\\">${ state.currentRole.tenantName } (<span t=\\\"globals.operator\\\"></span>)</small> <small if.to-view=\\\"state.user.isPowerUser\\\" class=\\\"text-muted d-block mt-1\\\">${ state.currentRole.tenantName } (<span t=\\\"globals.poweruser\\\"></span>)</small> <small if.to-view=\\\"state.user.isLecturer\\\" class=\\\"text-muted d-block mt-1\\\">${ state.currentRole.tenantName } (<span t=\\\"globals.lecturer\\\"></span>)</small> </span> </a> <div class=\\\"dropdown-menu dropdown-menu-right dropdown-menu-arrow login-status-widget__dropdown-menu ${ showMenu ? 'show' : ''}\\\" set-false-on-outside-click.two-way=\\\"showMenu\\\"> <h6 if.to-view=\\\"otherRoles.length > 0\\\" class=\\\"dropdown-header\\\" t=\\\"elements.loginStatus.changeRole\\\"></h6> <a repeat.for=\\\"role of otherRoles\\\" class=\\\"dropdown-item\\\" href=\\\"javascript:void(0)\\\" click.delegate=\\\"changeRole(role.userTenantId)\\\"> <safe-icon class=\\\"dropdown-icon login-status-widget__icon\\\" icon-name=\\\"System/Tabler/Repeat\\\"></safe-icon> ${ role.tenantName ? role.tenantName : PROVIDER_NAME } (<span t=\\\"globals.${ role.role.toLowerCase() }\\\"></span>) </a> <div if.to-view=\\\"otherRoles.length > 0\\\" class=\\\"dropdown-divider\\\"></div> <a class=\\\"dropdown-item\\\" route-href=\\\"route: user-settings\\\"> <safe-icon class=\\\"dropdown-icon login-status-widget__icon\\\" icon-name=\\\"System/Tabler/User\\\"></safe-icon><span t=\\\"navigation.profile\\\"></span> </a> <a if.to-view=\\\"accountLinksEnabled\\\" class=\\\"dropdown-item\\\" route-href=\\\"route: myaccount/link\\\"> <safe-icon class=\\\"dropdown-icon login-status-widget__icon\\\" icon-name=\\\"System/Tabler/Settings\\\"></safe-icon><span t=\\\"navigation.accountLinks\\\"></span> </a> <a if.to-view=\\\"state.user.isProvider\\\" class=\\\"dropdown-item\\\" route-href=\\\"route: tenant-settings\\\"> <safe-icon class=\\\"dropdown-icon login-status-widget__icon\\\" icon-name=\\\"System/Tabler/Settings\\\"></safe-icon><span t=\\\"navigation.tenantSettings\\\"></span> </a> <a class=\\\"dropdown-item\\\" route-href=\\\"route: myaccount/tfa\\\"> <safe-icon class=\\\"dropdown-icon login-status-widget__icon\\\" icon-name=\\\"System/Tabler/Settings\\\"></safe-icon><span t=\\\"navigation.tfa\\\"></span> </a> <div class=\\\"dropdown-divider\\\"></div> <a class=\\\"dropdown-item\\\" href=\\\"javascript:void(0);\\\" click.delegate=\\\"signOutClickedHandler()\\\"> <safe-icon class=\\\"dropdown-icon login-status-widget__icon\\\" icon-name=\\\"System/Tabler/Logout\\\"></safe-icon><span t=\\\"navigation.signOut\\\"></span> </a> </div> </div> </template>\";\n// Exports\nexport default code;","// Module\nvar code = \"<template> <div class=\\\"${showPreview ? 'col-md-6' : 'col-md-12'}\\\"> <textarea class=\\\"form-control markdownarea__input\\\" value.two-way=\\\"value\\\"></textarea> </div> <div if.to-view=\\\"showPreview\\\" class=\\\"col-md-6 d-flex align-items-stretch align-content-stretch pt-3 pt-md-0\\\"> <markdown-text class=\\\"border rounded p-2 flex-grow-1 markdownarea__preview\\\" text.to-view=\\\"value\\\"></markdown-text> </div> <div class=\\\"col-md-12 pt-1\\\"> <label class=\\\"custom-control custom-checkbox custom-control-inline form-check\\\"> <input class=\\\"custom-control-input form-check-input\\\" type=\\\"checkbox\\\" checked.bind=\\\"showPreview\\\"> <span class=\\\"custom-control-label text-muted\\\" t=\\\"elements.markdownarea.showPreview\\\"></span> </label> </div> </template>\";\n// Exports\nexport default code;","// Module\nvar code = \"<template class=\\\"matrix-table\\\"> <div class=\\\"matrix-table__side\\\"> <div class=\\\"matrix-table__side-item\\\"> <button-lightswitch value.to-view=\\\"editModeActive\\\" label-id=\\\"editModeLabel\\\" click.trigger=\\\"toggleEditMode()\\\" theme=\\\"admin\\\"> <label class=\\\"matrix-table__edit-label ml-2\\\" id=\\\"editModeLabel\\\" t=\\\"globals.edit\\\"></label> </button-lightswitch> </div> <div repeat.for=\\\"row of rowData\\\" class=\\\"matrix-table__side-item\\\"> <avatar-icon avatar-svg-download-link.to-view=\\\"rowConfig.avatarSvgDownloadLink(row)\\\"></avatar-icon> <div class=\\\"matrix-table__text matrix-table__text--link\\\" click.trigger=\\\"rowConfig.labelClickCallback(row)\\\">${rowConfig.labelTransformer(row)}</div> <hr class=\\\"matrix-table__row-hr\\\"/> </div> <div class=\\\"matrix-table__side-item\\\"> <hr class=\\\"matrix-table__row-hr\\\"/> </div> </div> <div class=\\\"matrix-table__scrollbox\\\"> <div class=\\\"matrix-table__column-head\\\"> <div repeat.for=\\\"column of columnData\\\" class=\\\"matrix-table__column-head-title\\\"> <div class=\\\"matrix-table__column-head-rotate-spacer\\\">${columnConfig.labelTransformer(column)}</div> <div class=\\\"matrix-table__column-head-rotate-text\\\">${columnConfig.labelTransformer(column)}</div> <skill-icon class=\\\"matrix-table__column-head-icon\\\" icon-key.to-view=\\\"columnConfig.iconKeyTransformer(column)\\\"></skill-icon> </div> </div> <div repeat.for=\\\"row of rowData\\\" class=\\\"matrix-table__row\\\"> <div repeat.for=\\\"column of columnData\\\" class=\\\"matrix-table__data-cell\\\"> <tri-state-checkbox if.to-view=\\\"editModeActive\\\" state.to-view=\\\"getCheckedState(column, row, refreshVersion)\\\" click.trigger=\\\"rowConfig.dataClickCallback(column, row)\\\"></tri-state-checkbox> <safe-icon else class=\\\"${rowConfig.statusFlairTransformer(column, row).iconClass}\\\" icon-name.to-view=\\\"rowConfig.statusFlairTransformer(column, row).iconName\\\"></safe-icon> </div> </div> <div class=\\\"matrix-table__row\\\"> <div repeat.for=\\\"column of columnData\\\" class=\\\"matrix-table__data-cell\\\"> <div class=\\\"matrix-table__text\\\">${getColumnCount(column, refreshVersion)}</div> </div> </div> </div> <div class=\\\"matrix-table__side\\\"> <div class=\\\"matrix-table__side-item\\\"> <div class=\\\"matrix-table__text text-uppercase\\\" t=\\\"globals.total\\\"></div> </div> <div repeat.for=\\\"row of rowData\\\" class=\\\"matrix-table__side-item\\\"> <div class=\\\"matrix-table__text\\\">${getRowCount(row, refreshVersion)}</div> </div> <div class=\\\"matrix-table__side-item\\\"></div> </div> </template>\";\n// Exports\nexport default code;","import { autoinject, bindable } from 'aurelia-framework';\n\n@autoinject()\nexport class InfoIcon {\n  @bindable() public infoText: string = '';\n  @bindable() public iconName: string = 'System/Tabler/Info';\n}\n","import { FrameworkConfiguration } from 'aurelia-framework';\nimport { PLATFORM } from 'aurelia-pal';\n\nexport function configure(config: FrameworkConfiguration): void {\n  config.globalResources(PLATFORM.moduleName('resources/elements/inline-edit-field/inline-edit-field'));\n}\n","import { autoinject, bindable, computedFrom } from 'aurelia-framework';\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 { I18N } from 'aurelia-i18n';\n\nenum Mode {\n  /**\n   * works with edit/save/cancel buttons\n   */\n  Button = 'button',\n  /**\n   * works with in-/outside clicks\n   */\n  Click = 'click',\n  /**\n   * same as click, but without icon\n   */\n  Plain = 'plain',\n}\n\nenum InputType {\n  Text = 'text',\n  Number = 'number',\n}\n\n/**\n * @event {InlineEditSaveEvent} save-event\n * @event {InlineEditValidationFailedEvent} validation-failed-event\n */\n@autoinject()\nexport class InlineEditField {\n  /**\n   * FROM VIEW only\n   */\n  @bindable() public currentValue: string = '';\n  @bindable() public value: string | null = null;\n  @bindable() public showEditBorder = true;\n  @bindable() public placeholderTk: string | null = null;\n  @bindable() public mode: Mode = Mode.Button;\n  @bindable() public inputType: InputType = InputType.Text;\n  @bindable() public maxLength: number | null = null;\n  @bindable() public minLength: number | null = null;\n\n  public isEditing: boolean = false;\n\n  private domElement: HTMLElement;\n  private inputElement: HTMLElement | null = null;\n  private subscriptionManager: SubscriptionManager;\n\n  private Mode = Mode;\n\n  constructor(\n    element: Element,\n    subscriptionManagerService: SubscriptionManagerService,\n    private i18n: I18N,\n  ) {\n    this.subscriptionManager = subscriptionManagerService.createSubscriptionManager();\n    this.domElement = element as HTMLElement;\n  }\n\n  protected attached(): void {\n    assertNotNullOrUndefined(this.inputElement, 'Forgot to ref inputElement');\n    this.subscriptionManager.subscribeToKeyboardEvent(this.inputElement, KeyboardKey.Enter, () => {\n      this.saveChange();\n    });\n    this.subscriptionManager.subscribeToKeyboardEvent(this.inputElement, KeyboardKey.Escape, () => {\n      this.cancelEditing();\n    });\n\n    this.subscriptionManager.subscribeToDomEvent(this.inputElement, 'keypress', (event: Event) => this.handleInput(event as KeyboardEvent));\n    this.subscriptionManager.subscribeToDomEvent(this.inputElement, 'paste', (event: Event) => this.handlePaste(event as ClipboardEvent));\n  }\n\n  protected detached(): void {\n    this.subscriptionManager.disposeSubscriptions();\n  }\n\n  public startEditing(): void {\n    if(this.isEditing) return;\n\n    const wasPlaceholder = this.isPlaceholder;\n    this.isEditing = true;\n\n    if(wasPlaceholder) {\n      this.currentValue = '';\n    }\n\n    // timeout so `isEditing` render already happened\n    window.setTimeout(() => {\n      assertNotNullOrUndefined(this.inputElement, 'inputElement cannot be null or undefined');\n      // https://www.geeksforgeeks.org/how-to-set-cursor-position-in-content-editable-element-using-javascript/\n      // Creates range object\n      const range = document.createRange();\n      // Creates object for selection\n      const selection = window.getSelection();\n      // Set start position of range\n      range.setStart(this.inputTextNode, this.currentValue.length);\n      // Collapse range within its boundary points\n      // Returns boolean\n      range.collapse(true);\n      // Remove all ranges set\n      selection?.removeAllRanges();\n      // Add range with respect to range object.\n      selection?.addRange(range);\n      // Set cursor on focus\n      this.inputElement.focus();\n    });\n  }\n\n  public cancelEditing(): void {\n    this.isEditing = false;\n    /**\n     * Timeout is required, in order to allow cancel editing by pressing Escape\n     * Otherwise a timing issue occurs, and the textcontent bound to currentValue\n     * is overwritten with the invalid value from before.\n     * Also linter doesn't allow \"() => this.currentValue = this.value\" without {...;}\n     */\n    window.setTimeout(() => this.resetCurrentValue());\n  }\n\n  @computedFrom('value', 'placeholderTk', 'isEditing')\n  public get isPlaceholder(): boolean {\n    return !this.value && this.placeholderTk != null && !this.isEditing;\n  }\n\n  private get inputTextNode(): Node {\n    assertNotNullOrUndefined(this.inputElement, 'inputElement cannot be null or undefined');\n    // eslint-disable-next-line prefer-destructuring\n    const firstNode = this.inputElement.childNodes[0];\n    if(firstNode == null) {\n      const newTextNode = new Text('');\n      this.inputElement.appendChild(newTextNode);\n      return newTextNode;\n    }\n    return firstNode;\n  }\n\n  private placeholderTkChanged(): void {\n    this.resetCurrentValue();\n  }\n\n  private valueChanged(): void {\n    this.resetCurrentValue();\n  }\n\n  private saveChange(): void {\n    this.isEditing = false;\n    this.currentValue = this.currentValue.trim();\n\n    if(this.currentValue === this.value) return;\n    if(this.currentValue === '' && this.value == null) {\n      this.cancelEditing();\n      return;\n    }\n\n    if(this.minLength && this.currentValue.length < this.minLength) {\n      this.fireValidationFailedEvent();\n      this.cancelEditing();\n    } else {\n      this.cancelEditing();\n      DomUtils.fireEvent<InlineEditSaveEvent['detail']>(this.domElement, {\n        name: 'save-event',\n        bubbles: false,\n        detail: {\n          value: this.currentValue,\n        }\n      });\n    }\n  }\n\n  private resetCurrentValue(): void {\n    if(this.value != null) {\n      this.currentValue = this.value;\n    } else {\n      this.currentValue = '';\n    }\n  }\n\n  private handleInput(event: KeyboardEvent): void {\n    const isOrdinary = event.key.length === 1 || event.code === KeyboardKey.Backquote;\n    const isValidInput = this.inputType !== InputType.Number || /^\\d$/.test(event.key);\n    const exceedsMaxLength = this.maxLength != null && this.currentValue.length >= this.maxLength;\n\n    if(\n      (exceedsMaxLength || !isValidInput)\n      && isOrdinary\n      && !event.metaKey && !event.ctrlKey && !event.altKey\n    ) {\n      event.preventDefault();\n      this.fireValidationFailedEvent();\n    }\n  }\n\n  private handlePaste(event: ClipboardEvent): false {\n    const { clipboardData } = event;\n    const pastedData = clipboardData?.getData('text/plain') ?? '';\n    const isValidInput = this.inputType !== InputType.Number || /^\\d$/.test(pastedData);\n    event.preventDefault();\n\n    const selection = document.getSelection();\n    const range = selection?.getRangeAt(0);\n    const rangeLength = (range?.endOffset ?? 0) - (range?.startOffset ?? 0);\n    const exceedsMaxLength = this.maxLength != null && this.currentValue.length + (pastedData?.length ?? 0) - rangeLength > this.maxLength;\n\n    if(!pastedData || exceedsMaxLength || !isValidInput) {\n      if(this.maxLength != null) {\n        this.currentValue = this.currentValue.slice(0, this.maxLength);\n      }\n      this.fireValidationFailedEvent();\n      return false;\n    }\n\n    if(range) {\n      range.deleteContents();\n      const textNode = document.createTextNode(pastedData);\n      range.insertNode(textNode);\n      range.selectNodeContents(textNode);\n      range.collapse(false);\n      selection?.removeAllRanges();\n      selection?.addRange(range);\n    }\n    return false;\n  }\n\n  private fireValidationFailedEvent(): void {\n    DomUtils.fireEvent<InlineEditValidationFailedEvent['detail']>(this.domElement, {\n      name: 'validation-failed-event',\n      bubbles: false,\n      detail: {\n        value: this.currentValue,\n      }\n    });\n  }\n}\n\nexport type InlineEditSaveEvent = Event & { detail: { value: string } };\nexport type InlineEditValidationFailedEvent = Event & { detail: { value: string } };\n","import { autoinject, bindable } from 'aurelia-framework';\nimport { ContentEditingInsertModeSelector } from 'common/constants/content.editing.constants';\nimport { ContentElementType } from 'common/dto/content-editor/content.editor.types';\nimport { LayoutContainerDirection } from 'common/dto/course/layout.container.interface';\nimport {\n  ALL_DND_IDS,\n  DndActivityEventData,\n  DndContext,\n  DndDropzoneIdentifier,\n  DndManager,\n  DndManagerService,\n  DndOverrideTargetDisplay,\n  IDndDropzoneConfig,\n} from 'services/DndManagerService';\n\n@autoinject()\nexport class LayoutContainerInsideDropzoneWithTarget {\n  @bindable() public direction: LayoutContainerDirection = LayoutContainerDirection.Horizontal;\n  @bindable() public dndContext: DndContext = DndContext.Default;\n  @bindable() public dndActive = false;\n  @bindable() public overrideTargetDisplay = DndOverrideTargetDisplay.NoOverride;\n  @bindable() public dropzoneConfig: IDndDropzoneConfig = {\n    identifier: {\n      superId: 0,\n      subId: 0,\n    },\n    insertModeSelector: ContentEditingInsertModeSelector.Sequential,\n  };\n\n  private dndManager: DndManager<ContentElementType> | null = null;\n  private showTarget = false;\n  private isTargeted = false;\n\n  constructor(\n    private dndManagerService: DndManagerService,\n  ) { }\n\n  protected attached(): void {\n    this.dndManager = this.dndManagerService.getDndManager<ContentElementType>(this.dndContext);\n    const allSiblingsIdentifier: DndDropzoneIdentifier = {\n      superId: this.dropzoneConfig.identifier.superId,\n      subId: ALL_DND_IDS,\n    };\n    this.dndManager.subscribeToDragActivity(\n      this,\n      this.dndDragActivityHandler.bind(this),\n      [allSiblingsIdentifier],\n    );\n  }\n\n  private overrideTargetDisplayChanged(): void {\n    this.showTarget = this.overrideTargetDisplay === DndOverrideTargetDisplay.ForceShow\n      || this.overrideTargetDisplay === DndOverrideTargetDisplay.ForceTargeted;\n    this.isTargeted = this.overrideTargetDisplay === DndOverrideTargetDisplay.ForceTargeted;\n  }\n\n  private dndDragActivityHandler(dndInfo: DndActivityEventData<IDndDropzoneConfig>): void {\n    // don't react to cardinal, but allow ortho and sequential to be displayed at the same time\n    if(dndInfo.dropTarget.insertModeSelector !== ContentEditingInsertModeSelector.OrthoSequential\n      && dndInfo.dropTarget.insertModeSelector !== ContentEditingInsertModeSelector.Sequential) return;\n    this.showTarget = dndInfo.parentActiveInside ?? false;\n    if(dndInfo.dropTarget.identifier.subId === this.dropzoneConfig.identifier.subId\n      && dndInfo.dropTarget.insertModeSelector === this.dropzoneConfig.insertModeSelector\n      && dndInfo.dropTarget.sequentialOrder === this.dropzoneConfig.sequentialOrder) {\n      this.isTargeted = dndInfo.enterEvent;\n    }\n  }\n\n  private detached(): void {\n    this.dndManager?.disposeSubscriptions(this);\n    this.isTargeted = false;\n    this.showTarget = false;\n  }\n}\n","import { autoinject, bindable } from 'aurelia-framework';\nimport { ContentEditingInsertModeSelector } from 'common/constants/content.editing.constants';\nimport { ContentElementType } from 'common/dto/content-editor/content.editor.types';\nimport {\n  ALL_DND_IDS,\n  DndActivityEventData,\n  DndContext,\n  DndDropzoneIdentifier,\n  DndManager,\n  DndManagerService,\n  DndOverrideTargetDisplay,\n  IDndDropzoneConfig,\n} from 'services/DndManagerService';\n\n@autoinject()\nexport class LayoutContainerOutsideDropzoneWithTarget {\n  @bindable() public dndContext: DndContext = DndContext.Default;\n  @bindable() public dndActive = false;\n  @bindable() public overrideTargetDisplay = DndOverrideTargetDisplay.NoOverride;\n  @bindable() public dropzoneConfig: IDndDropzoneConfig = {\n    identifier: {\n      superId: 0,\n      subId: 0,\n    },\n    insertModeSelector: ContentEditingInsertModeSelector.Cardinal,\n  };\n\n  private dndManager: DndManager<ContentElementType> | null = null;\n  private showTarget = false;\n  private isTargeted = false;\n\n  constructor(\n    private dndManagerService: DndManagerService,\n  ) { }\n\n  protected attached(): void {\n    this.dndManager = this.dndManagerService.getDndManager<ContentElementType>(this.dndContext);\n    const allSiblingsIdentifier: DndDropzoneIdentifier = {\n      superId: this.dropzoneConfig.identifier.superId,\n      subId: ALL_DND_IDS,\n    };\n    this.dndManager.subscribeToDragActivity(\n      this,\n      this.dndDragActivityHandler.bind(this),\n      [allSiblingsIdentifier],\n    );\n  }\n\n  private overrideTargetDisplayChanged(): void {\n    this.showTarget = this.overrideTargetDisplay === DndOverrideTargetDisplay.ForceShow\n      || this.overrideTargetDisplay === DndOverrideTargetDisplay.ForceTargeted;\n    this.isTargeted = this.overrideTargetDisplay === DndOverrideTargetDisplay.ForceTargeted;\n  }\n\n  private dndDragActivityHandler(dndInfo: DndActivityEventData<IDndDropzoneConfig>): void {\n    if(dndInfo.dropTarget.insertModeSelector !== this.dropzoneConfig.insertModeSelector) return;\n    this.showTarget = dndInfo.parentActiveOutside ?? false;\n    if(dndInfo.dropTarget.identifier.subId === this.dropzoneConfig.identifier.subId) {\n      this.isTargeted = dndInfo.enterEvent;\n    }\n  }\n\n  private detached(): void {\n    this.dndManager?.disposeSubscriptions(this);\n    this.isTargeted = false;\n    this.showTarget = false;\n  }\n}\n","import { autoinject, bindable } from 'aurelia-framework';\nimport { LayoutContainerDirection } from 'common/dto/course/layout.container.interface';\nimport { DomUtils } from 'common/utils/DomUtils';\n\n/**\n * @event {HandleMousedownEvent} handle-mousedown\n * @event {ApplyClickedEvent} apply-clicked\n * @event {EditClickedEvent} edit-clicked\n */\n@autoinject()\nexport class LayoutContainerResizer {\n  @bindable() protected direction: LayoutContainerDirection = LayoutContainerDirection.Horizontal;\n  /**\n   * in some cases (e.g. another resize handle is dragged, while the\n   * mouse can still hover over this one here) the resize handle should\n   * be completely disabled. (including the hover-show-behavior)\n   */\n  @bindable() protected isDisabled = false;\n  /**\n   * resizer handle is shown based on hover state. in some\n   * cases it is necessary to keep displaying it, even when\n   * hover state has ended. this info must come from outside.\n   */\n  @bindable() protected showHandle = false;\n  /**\n   * a previous resizingState is available\n   * shows apply-icon\n   */\n  @bindable() protected canApply = false;\n  private showHandleClass = '';\n  private domElement: HTMLElement;\n\n  constructor(element: Element) {\n    this.domElement = element as HTMLElement;\n  }\n\n  private showHandleChanged(): void {\n    this.showHandleClass = this.showHandle ? '--show-handle' : '';\n  }\n\n  private handleHandleMousedown(): void {\n    DomUtils.fireEvent(this.domElement, {\n      bubbles: true,\n      name: 'handle-mousedown',\n    });\n  }\n\n  private handleEditIconClicked(): void {\n    DomUtils.fireEvent(this.domElement, {\n      bubbles: true,\n      name: 'edit-clicked',\n    });\n  }\n\n  private handleApplyIconClicked(): void {\n    DomUtils.fireEvent(this.domElement, {\n      bubbles: true,\n      name: 'apply-clicked',\n    });\n  }\n}\n\nexport type HandleMousedownEvent = CustomEvent<never>;\nexport type ApplyClickedEvent = CustomEvent<never>;\nexport type EditClickedEvent = CustomEvent<never>;\n","import { autoinject } from 'aurelia-framework';\nimport { StoreService } from 'common/aurelia-services/StoreService';\nimport { AppUtils } from 'common/utils/AppUtils';\nimport { IState } from '../../../store/state';\n\n@autoinject()\nexport class LayoutFooter {\n  private dataPrivacyAgreementLink = AppUtils.getDataPrivacyAgreementLink();\n  private legalNoticeLink: string | null = null;\n  \n  constructor(private storeService: StoreService<IState>) {}\n\n  protected attached(): void {\n    this.storeService.subscribeToDetailedChanges(this, [\n      state => state.language,\n    ], (_, language) => {\n      this.legalNoticeLink = AppUtils.getLegalNoticeLink(language);\n    });\n  }\n\n  protected detached(): void {\n    this.storeService.disposeAllSubscriptions(this);\n  }\n}\n","import { autoinject, bindable } from 'aurelia-framework';\nimport { ContentEditingInsertModeSelector } from 'common/constants/content.editing.constants';\nimport { ILearningJourneyCourseDto } from 'common/dto/learning-journey/management/learning-journey.management.interface';\nimport { DomUtils } from 'common/utils/DomUtils';\nimport {\n  DndActivityEventData,\n  DndContext,\n  DndDropEventData,\n  DndInsertModeSequential,\n  DndManager,\n  DndManagerService,\n  DndPayloadUniversal,\n  DndSimpleTargetDisplaySetting,\n  DndSubContext,\n  DndTargetDisplay,\n  IDndDropzoneConfig\n} from '../../../services/DndManagerService';\n\n/**\n * @event { LearningJourneyAddCourseEvent } learning-journey-add-course\n * @event { LearningJourneyEditCourseEvent } learning-journey-edit-course\n * @event { LearningJourneyMoveCourseEvent } learning-journey-move-course\n * @event { LearningJourneyRemoveCourseEvent } learning-journey-remove-course\n */\n@autoinject()\nexport class LearningJourneyCourseStructure {\n  @bindable() public courses: ILearningJourneyCourseDto[] = [];\n  private dndActive = false;\n  private domElement: HTMLElement;\n  private dndContext: DndContext = DndContext.LearningJourneyCourses;\n  private dndManager: DndManager<ILearningJourneyCourseDto> | null = null;\n  private dndPayload: DndPayloadUniversal<ILearningJourneyCourseDto> = null;\n  private dndLastEnteredInfo: IDndDropzoneConfig | null = null;\n  private dndTargetDisplaySettings: Array<DndSimpleTargetDisplaySetting> = [];\n  private dndTargetDisplaySettingsBuffer: Array<DndSimpleTargetDisplaySetting> = [];\n  private DndInsertModeSequential = DndInsertModeSequential;\n  private DndTargetDisplay = DndTargetDisplay;\n  private DndPayloadSource = DndSubContext;\n\n  constructor(\n    private dndManagerService: DndManagerService,\n    element: Element\n  ) {\n    this.domElement = element as HTMLElement;\n  }\n\n  public dndPayloadChangedHandler(payload: DndPayloadUniversal<ILearningJourneyCourseDto>): void {\n    this.dndPayload = payload;\n    this.dndActive = payload != null;\n\n    // perform initial target display setting calculation with dummy data\n    if(this.dndPayload != null && this.dndPayload.existingPayloadId != null) {\n      const dropTargetInfo: IDndDropzoneConfig = {\n        identifier: {\n          superId: this.dndPayload.existingPayloadId ?? 0,\n          subId: 0,\n        },\n        insertModeSelector: ContentEditingInsertModeSelector.Sequential,\n        insertModeSequential: DndInsertModeSequential.Before,\n      };\n      this.calcDndTargetDisplaySettings(dropTargetInfo, true);\n    } else this.resetTargetDisplaySettings();\n  }\n\n  public dndDragActivityHandler(dndInfo: DndActivityEventData<IDndDropzoneConfig>): void {\n    if(this.dndPayload == null) return;\n    const config = dndInfo.dropTarget;\n    if(dndInfo && this.dndLastEnteredInfo === null) this.dndLastEnteredInfo = dndInfo.dropTarget;\n    if(dndInfo.enterEvent && this.dndLastEnteredInfo) {\n      this.dndLastEnteredInfo = this.dndLastEnteredInfo !== dndInfo.dropTarget ? dndInfo.dropTarget : this.dndLastEnteredInfo;\n      this.calcDndTargetDisplaySettings(config, false);\n    } else {\n      // calculate display settings when all drop-zone area is left and thus the latest targets need to be reset\n      if(this.dndLastEnteredInfo && this.dndLastEnteredInfo === dndInfo.dropTarget) {\n        this.calcDndTargetDisplaySettings(config, true);\n      }\n    }\n  }\n\n  protected attached(): void {\n    this.dndManager = this.dndManagerService.getDndManager<ILearningJourneyCourseDto>(this.dndContext);\n    this.dndManager.subscribePayloadChanged(this, this.dndPayloadChangedHandler.bind(this));\n    this.dndManager.subscribeToDragActivity(this, this.dndDragActivityHandler.bind(this));\n    this.dndManager.subscribeDrop(this, this.onDrop.bind(this));\n  }\n\n  protected detached(): void {\n    this.dndManager?.disposeSubscriptions(this);\n    this.dndLastEnteredInfo = null;\n  }\n\n  private async onDrop(dropInfo: DndDropEventData<ILearningJourneyCourseDto>): Promise<void> {\n    this.dndActive = false;\n    if(dropInfo.dndPayload == null\n      || dropInfo.dndPayload.existingPayloadId == null\n      || dropInfo.dndPayload.existingPayloadId === dropInfo.dropTarget.identifier.superId\n      || dropInfo.dropTarget.sequentialOrder == null) return;\n\n    this.fireMoveCourseEvent(dropInfo.dndPayload.existingPayloadId, dropInfo.dropTarget.sequentialOrder);\n    this.dndActive = false;\n  }\n\n  private resetTargetDisplaySettings(): void {\n    this.dndTargetDisplaySettingsBuffer = [];\n    this.courses.forEach(() => {\n      this.dndTargetDisplaySettingsBuffer.push({\n        before: DndTargetDisplay.Hidden,\n        after: DndTargetDisplay.Hidden,\n      });\n    });\n    this.dndTargetDisplaySettings = this.dndTargetDisplaySettingsBuffer;\n  }\n\n  private getDropzoneConfig(\n    index: number,\n    insertMode: DndInsertModeSequential\n  ): IDndDropzoneConfig {\n    const config: IDndDropzoneConfig = {\n      identifier: {\n        superId: this.courses[index].learningJourneyCourseId ?? -1,\n        subId: 0,\n      },\n      insertModeSelector: ContentEditingInsertModeSelector.Sequential,\n      insertModeSequential: insertMode,\n      sequentialOrder: insertMode === DndInsertModeSequential.Before ? index + 1 : index + 2,\n    };\n    return config;\n  }\n\n  private calcDndTargetDisplaySettings(dndInfo: IDndDropzoneConfig, resetTargets: boolean): void {\n    const selectedCourseIds = this.courses.map(q => q.learningJourneyCourseId);\n\n    let targetDisplaySettingBefore = DndTargetDisplay.Hidden;\n    let targetDisplaySettingAfter = DndTargetDisplay.Hidden;\n    this.dndTargetDisplaySettingsBuffer = [];\n    selectedCourseIds.forEach((courseId, index) => {\n      targetDisplaySettingBefore = this.dndPayload == null\n        || courseId === this.dndPayload.existingPayloadId\n        || (index > 0 && selectedCourseIds[index - 1] === this.dndPayload.existingPayloadId)\n        ? DndTargetDisplay.Hidden\n        : DndTargetDisplay.Allowed;\n\n      if(targetDisplaySettingBefore === DndTargetDisplay.Allowed) {\n        targetDisplaySettingBefore = !resetTargets\n          && ((index > 0\n            && dndInfo.identifier.superId === selectedCourseIds[index - 1]\n            && dndInfo.insertModeSequential === DndInsertModeSequential.After)\n            || (dndInfo.identifier.superId === selectedCourseIds[index]\n              && dndInfo.insertModeSequential === DndInsertModeSequential.Before))\n          ? DndTargetDisplay.Target\n          : targetDisplaySettingBefore;\n      }\n\n      targetDisplaySettingAfter = this.dndPayload == null\n        || courseId === this.dndPayload.existingPayloadId\n        || (index < selectedCourseIds.length - 1 && selectedCourseIds[index + 1] === this.dndPayload.existingPayloadId)\n        ? DndTargetDisplay.Hidden\n        : DndTargetDisplay.Allowed;\n\n      if(targetDisplaySettingAfter === DndTargetDisplay.Allowed) {\n        targetDisplaySettingAfter = !resetTargets\n          && ((index < selectedCourseIds.length - 1\n            && dndInfo.identifier.superId === selectedCourseIds[index + 1]\n            && dndInfo.insertModeSequential === DndInsertModeSequential.Before)\n            || (dndInfo.identifier.superId === selectedCourseIds[index]\n              && dndInfo.insertModeSequential === DndInsertModeSequential.After))\n          ? DndTargetDisplay.Target\n          : targetDisplaySettingAfter;\n      }\n\n      this.dndTargetDisplaySettingsBuffer.push({\n        before: targetDisplaySettingBefore,\n        after: targetDisplaySettingAfter,\n      });\n    });\n\n    this.dndTargetDisplaySettings = this.dndTargetDisplaySettingsBuffer;\n  }\n\n  private handleAddCourseClick(): void {\n    DomUtils.fireEvent(this.domElement, {\n      detail: {},\n      name: 'learning-journey-add-course',\n    });\n  }\n\n  private fireMoveCourseEvent(learningJourneyCourseId: number, targetOrder: number): void {\n    DomUtils.fireEvent<LearningJourneyMoveCourseEvent['detail']>(this.domElement, {\n      detail: {\n        learningJourneyCourseId,\n        targetOrder\n      },\n      name: 'learning-journey-move-course',\n    });\n  }\n\n  private async handleShowCourseOptionsClick(course: ILearningJourneyCourseDto): Promise<void> {\n    DomUtils.fireEvent<LearningJourneyEditCourseEvent['detail']>(this.domElement, {\n      detail: {\n        course\n      },\n      name: 'learning-journey-edit-course',\n    });\n  }\n\n  private handleRemoveCourseClick(course: ILearningJourneyCourseDto): void {\n    DomUtils.fireEvent<LearningJourneyRemoveCourseEvent['detail']>(this.domElement, {\n      detail: {\n        learningJourneyCourseId: course.learningJourneyCourseId\n      },\n      name: 'learning-journey-remove-course',\n    });\n  }\n}\n\nexport type LearningJourneyAddCourseEvent = CustomEvent<never>;\nexport type LearningJourneyEditCourseEvent = CustomEvent<{ course: ILearningJourneyCourseDto }>;\nexport type LearningJourneyMoveCourseEvent = CustomEvent<{ learningJourneyCourseId: number; targetOrder: number }>;\nexport type LearningJourneyRemoveCourseEvent = CustomEvent<{ learningJourneyCourseId: number }>;\n","import { autoinject, bindable } from 'aurelia-framework';\n\n@autoinject\nexport class LectureButton {\n  @bindable private type: LectureButtonType = LectureButtonType.Simple;\n  @bindable private toggled = false;\n  @bindable private disabled = false;\n\n  private handleClick(event: MouseEvent): void {\n    if(this.disabled) event.stopPropagation();\n  }\n}\n\nenum LectureButtonType {\n  Fill = 'fill',\n  Outline = 'outline',\n  Simple = 'simple',\n  SmallToggle = 'small-toggle',\n}\n","import { autoinject, bindable, computedFrom } from 'aurelia-framework';\nimport partyPopperAnimation from 'common/animations/party-popper.json';\nimport { IAttendeeDto, IScoreBoardEntry } from 'common/dto/distinar/distinar.interface';\nimport { ContrastMode } from 'common/enum/quiz.attempt.status.enum';\nimport { DomUtils } from 'common/utils/DomUtils';\nimport { LectureQuizUtils } from 'common/utils/LectureQuizUtils';\n\n/**\n * @event {CloseQuizEvent} quiz-closed\n */\n@autoinject()\nexport class LectureQuizFinalResult {\n  @bindable() public showHeaderButton = true;\n  @bindable() public scores: IScoreBoardEntry[] = [];\n  @bindable() public players: IAttendeeDto[] = [];\n  @bindable() public contrastMode: ContrastMode = ContrastMode.Default;\n\n  private partyPopperAnimation = partyPopperAnimation;\n  private showPodium = { 1: false, 2: false, 3: false, };\n  private showPlayer = { 1: false, 2: false, 3: false, };\n\n  constructor(private element: Element) { }\n\n  @computedFrom('scores', 'players')\n  private get podiumItems(): IPodiumItem[] {\n    const allScoresWithPosition = LectureQuizUtils.getScoresWithPosition(this.scores, this.players.filter(p => p.online), 3);\n    return allScoresWithPosition.map<IPodiumItem>(s => ({\n      avatarSvgDownloadLink: s.avatarSvgDownloadLink,\n      nickname: s.nickname,\n      position: s.position,\n      points: s.score,\n    }));\n  }\n\n  protected attached(): void {\n    this.handleAnimations();\n  }\n\n  private async handleAnimations(): Promise<void> {\n    this.showPodium = { 1: false, 2: false, 3: false };\n    this.showPlayer = { 1: false, 2: false, 3: false };\n\n    LectureQuizUtils.runRevealTimer(0, this.podiumItems[2]?.position === 3, this.podiumItems[1]?.position === 2, (position, step) => {\n      if(step === 1) this.showPodium[position] = true;\n      if(step === 2) this.showPlayer[position] = true;\n    });\n  }\n\n  private closeQuiz(): void {\n    DomUtils.fireEvent(this.element as HTMLElement, {\n      name: 'quiz-closed',\n      bubbles: false,\n    });\n  }\n}\n\ninterface IPodiumItem {\n  nickname: string | null;\n  avatarSvgDownloadLink: string | null;\n  position: number;\n  points: number;\n}\n\nexport type CloseQuizEvent = CustomEvent<never>;\n","import { autoinject, bindable, computedFrom } from 'aurelia-framework';\nimport { IListStandardItem } from 'common/aurelia-components/list-standard/list-standard';\nimport { IAttendeeDto, IScoreBoardEntry } from 'common/dto/distinar/distinar.interface';\nimport { ContrastMode } from 'common/enum/quiz.attempt.status.enum';\nimport { DomUtils } from 'common/utils/DomUtils';\nimport { LectureQuizUtils } from 'common/utils/LectureQuizUtils';\nimport { StringUtils } from 'common/utils/StringUtils';\n\n/**\n * @event {NextQuestionEvent} next-question\n */\n@autoinject()\nexport class LectureQuizIntermediateResult {\n  @bindable() public questionIndex = 0;\n  @bindable() public questionCount = 0;\n  @bindable() public showHeaderButton = true;\n  @bindable() public scores: IScoreBoardEntry[] = [];\n  @bindable() public players: IAttendeeDto[] = [];\n  @bindable() public contrastMode: ContrastMode = ContrastMode.Default;\n\n  private element: HTMLElement;\n\n  constructor(element: Element) {\n    this.element = element as HTMLElement;\n  }\n\n  @computedFrom('scores', 'players')\n  private get scoreBoardItems(): IListStandardItem[] {\n    const allScoresWithPosition = LectureQuizUtils.getScoresWithPosition(this.scores, this.players.filter(p => p.online), 10);\n\n    return allScoresWithPosition.map<IListStandardItem>(s => ({\n      enumeration: s.position,\n      icons: [StringUtils.formatNumberGrouped(s.score)],\n      separatorAfter: true,\n      text: {\n        primary: s.nickname || {\n          textTk: 'pages.lectureQuiz.anonymousPlayer'\n        }\n      },\n      graphic: {\n        avatarSvgDownloadLink: s.avatarSvgDownloadLink ?? null,\n        disabled: false,\n        points: s.points,\n      }\n    }));\n  }\n\n  private handleHeaderButtonClicked(): void {\n    DomUtils.fireEvent(this.element, {\n      bubbles: true,\n      name: 'next-question',\n    });\n  }\n}\n\nexport type NextQuestionEvent = CustomEvent<never>;\n","import { bindable } from 'aurelia-framework';\nimport { ContrastMode } from 'common/enum/quiz.attempt.status.enum';\n\nexport class LectureQuizLoading {\n  @bindable() public questionCount = 0;\n  @bindable() public percentLoaded = 0;\n  @bindable() public progress: number | null = null;\n  @bindable() public contrastMode: ContrastMode = ContrastMode.Default;\n}\n","import { autoinject, bindable, computedFrom } from 'aurelia-framework';\nimport fillInTheBlankAnimationData from 'common/animations/quiz-fill-in-the-blank.json';\nimport multipleChoiceAnimationData from 'common/animations/quiz-multiple-choice.json';\nimport trueOrFalseAnimationData from 'common/animations/quiz-true-or-false.json';\nimport singleChoiceAnimationData from 'common/animations/quiz-single-choice.json';\nimport { IAnimationData, LottieAnimationPlayer } from 'common/aurelia-components/lottie-animation-player/lottie-animation-player';\nimport { QuestionType } from 'common/dto/course/content/course.types';\nimport { IQuizAnswerDto } from 'common/dto/course/quiz/quiz.answer.interface';\nimport { IQuizQuestionDto } from 'common/dto/course/quiz/quiz.question.interface';\nimport { ILectureQuizAnswerStats, LectureQuizState } from 'common/dto/distinar/distinar.interface';\nimport { assertNotNullOrUndefined } from 'common/utils/assert';\nimport { DomUtils } from 'common/utils/DomUtils';\nimport { OpenImageOverlayEvent } from 'common/utils/ExamMediaUtils';\nimport { QuestionUtils } from 'common/utils/QuestionUtils';\nimport { ContrastMode, QuizAnswerTime } from 'common/enum/quiz.attempt.status.enum';\n\n/**\n * @event {NextQuestionEvent} next-question\n * @event {ToggleHelpTextEvent} toggle-help-text\n */\n@autoinject()\nexport class LectureQuizQuestion {\n  @bindable() public question: IQuizQuestionDto | null = null;\n  @bindable() public questionIndex = 0;\n  @bindable() public questionCount = 0;\n  @bindable() public answerIndex = 0;\n  @bindable() public progress: number | null = null;\n  @bindable() public questionState: LectureQuizState = LectureQuizState.Intro;\n  @bindable() public showHelpText = false;\n  @bindable() public answerStats: ILectureQuizAnswerStats | null = null;\n  @bindable() public showHeaderButton = true;\n  @bindable() public answerTime: QuizAnswerTime = QuizAnswerTime.Medium;\n  @bindable() public contrastMode: ContrastMode = ContrastMode.Default;\n  @bindable() public playerCount = 0;\n\n  private LectureQuizState = LectureQuizState;\n  private QuestionType = QuestionType;\n\n  private element: Element;\n  private lottieAnimationPlayer: LottieAnimationPlayer | null = null;\n  private animationReady = false;\n\n  // Image Overlay\n  private overlayImageAltText: string | null = null;\n  private overlayBlinkImageToDisplay: string | null = null;\n  private displayImageOverlay = false;\n  private overlayImageToDisplay: string | null = null;\n\n  constructor(element: Element) {\n    this.element = element;\n  }\n\n  protected attached(): void {\n    this.tryPlayAnimation();\n  }\n\n  protected detached(): void {\n    this.animationReady = false;\n  }\n\n  @computedFrom('answerStats', 'playerCount')\n  private get timeoutCount(): number {\n    return Math.max(this.playerCount - ((this.answerStats?.correctCount ?? 0) + (this.answerStats?.wrongCount ?? 0)), 0);\n  }\n\n  @computedFrom('question', 'answerIndex')\n  private get questionText(): string | null {\n    if(this.question) {\n      if(this.question.type === QuestionType.LT) {\n        return QuestionUtils.parseFillInTheBlankQuestion(this.question?.text)[this.answerIndex] || null;\n      }\n\n      return this.question.text;\n    }\n\n    return null;\n  }\n\n  @computedFrom('question', 'answerIndex')\n  private get helpText(): string | null {\n    const helpTexts = QuestionUtils.parseFillInTheBlankHelpText(this.question?.helpText ?? '');\n    return helpTexts[this.answerIndex] ?? null;\n  }\n\n  @computedFrom('question', 'questionState', 'answerIndex')\n  private get answersToDisplay(): IQuizAnswerDto | null {\n    if(!this.question) return null;\n\n    // when 'Getting Ready' for the next subquestion of a Single Choice Series, we still have to display the last set of answers, as they fade out\n    return this.questionState === LectureQuizState.GetReady && this.answerIndex > 0\n      ? this.question.answers[this.answerIndex - 1]\n      : this.question.answers[this.answerIndex];\n  }\n\n  @computedFrom('question.type')\n  private get animationData(): IAnimationData {\n    switch(this.question?.type) {\n      case QuestionType.LT:\n        return fillInTheBlankAnimationData;\n      case QuestionType.SC:\n      case QuestionType.SCP:\n        return singleChoiceAnimationData;\n      case QuestionType.TF:\n        return trueOrFalseAnimationData;\n      default:\n        return multipleChoiceAnimationData;\n    }\n  }\n\n  @computedFrom('questionState')\n  private get headerButtonTk(): string | null {\n    if(\n      this.questionState === LectureQuizState.Result\n      || (this.questionState === LectureQuizState.Answer && this.answerTime === QuizAnswerTime.Manual)\n    ) {\n      return 'elements.lectureQuizQuestion.nextQuestion';\n    }\n\n    if(this.questionState === LectureQuizState.GetReady || this.questionState === LectureQuizState.Answer) {\n      return 'elements.lectureQuizQuestion.skip';\n    }\n\n    return null;\n  }\n\n  @computedFrom('answerTime', 'questionState')\n  private get showProgress(): boolean {\n    return !(this.answerTime === QuizAnswerTime.Manual && this.questionState === LectureQuizState.Answer);\n  }\n\n  private onAnimationReady(): void {\n    this.animationReady = true;\n    this.tryPlayAnimation();\n  }\n\n  private tryPlayAnimation(): void {\n    if(!this.animationReady) { return; }\n    assertNotNullOrUndefined(this.lottieAnimationPlayer, 'forgot to ref lottieAnimationPlayer');\n\n    switch(this.questionState) {\n      case LectureQuizState.Intro:\n        this.lottieAnimationPlayer.playSegment('start-appear', 'end-appear');\n        break;\n      case LectureQuizState.Outro:\n        this.lottieAnimationPlayer.playSegment('start-disappear', 'end-disappear');\n        break;\n      default:\n        this.lottieAnimationPlayer.goToAndStop('end-appear');\n    }\n  }\n\n  private questionStateChanged(): void {\n    this.tryPlayAnimation();\n  }\n\n  private openImageOverlay(event: OpenImageOverlayEvent): void {\n    if(!this.question) { return; }\n\n    this.overlayImageAltText = this.question.text;\n    this.displayImageOverlay = true;\n    this.overlayImageToDisplay = event.detail.imgSrc;\n    if(event.detail.blinkImgSrc) {\n      this.overlayBlinkImageToDisplay = event.detail.blinkImgSrc;\n    } else {\n      this.overlayBlinkImageToDisplay = null;\n    }\n  }\n\n  private closeImageOverlay(): void {\n    this.displayImageOverlay = false;\n    this.overlayImageToDisplay = null;\n  }\n\n  private handleHelpTextButtonClicked(): void {\n    DomUtils.fireEvent(this.element as HTMLElement, {\n      bubbles: true,\n      name: 'toggle-help-text',\n    });\n  }\n\n  private handleHeaderButtonClicked(): void {\n    DomUtils.fireEvent(this.element as HTMLElement, {\n      bubbles: true,\n      name: 'next-question',\n    });\n  }\n}\n\nexport type NextQuestionEvent = CustomEvent<never>;\nexport type ToggleHelpTextEvent = CustomEvent<never>;\n","import { autoinject, bindable } from 'aurelia-framework';\nimport { ResourcePreloader, ResourceType } from 'common/aurelia-classes/ResourcePreloader';\nimport { ProgressBar } from 'common/aurelia-components/progress-bar/progress-bar';\nimport { DistinarWebSocketService } from 'common/aurelia-services/DistinarWebSocketService';\nimport { StoreService } from 'common/aurelia-services/StoreService';\nimport { SubscriptionManager, SubscriptionManagerService } from 'common/aurelia-services/SubscriptionManagerService';\nimport { IQuizAttemptContent } from 'common/dto/course/quiz/quiz.response';\nimport { IAttendeeDto, ILectureQuizConfig, ILectureQuizStatusDto, ILectureStatusDto, LectureQuizState, LecturerEmitMap, LecturerListenerMap } from 'common/dto/distinar/distinar.interface';\nimport { LecturerEmitName } from 'common/enum/distinar.enum';\nimport { QuizAnswerTime } from 'common/enum/quiz.attempt.status.enum';\nimport { AnimationUtils } from 'common/utils/AnimationUtils';\nimport { assertNotNullOrUndefined } from 'common/utils/assert';\nimport { FunctionUtils } from 'common/utils/FunctionUtils';\nimport { LectureQuizUtils } from 'common/utils/LectureQuizUtils';\nimport { QuestionUtils } from 'common/utils/QuestionUtils';\nimport { DistinarApiService } from 'services/api/DistinarApiService';\nimport { DistinarService } from 'services/DistinarService';\nimport { IState } from 'store/state';\n\n@autoinject()\nexport class LectureQuizSlide {\n  public static GET_READY_TOLERANCE_TIMEOUT_MS = 200; // synced to the playback time for the 3-2-1 lottie animation + tolerance\n  public static QUESTION_PRELOAD_TIMEOUT_MS = 5000;\n\n  @bindable() public lectureStatus: ILectureStatusDto | null = null;\n  @bindable() public showHeaderButtons = true;\n\n  private domElement: HTMLElement;\n  private subscriptionManager: SubscriptionManager;\n\n  private LectureQuizState = LectureQuizState;\n\n  private quizContainer: HTMLElement | null = null;\n  private resourcePreloader = new ResourcePreloader();\n\n  private currentConferenceAttendees: IAttendeeDto[] = [];\n  private allConferenceAttendees: IAttendeeDto[] = [];\n  private quizContent: IQuizAttemptContent | null = null;\n  private percentLoaded = 0;\n  private progress: number | null = null;\n  private scaleFactor = 1;\n  private onCancelStateTransition: (() => void) | null = null;\n  private quizConfig: ILectureQuizConfig | null = null;\n  private continuousScaleUpdateInterval: number | null = null;\n  private quizAnswerTime: QuizAnswerTime = QuizAnswerTime.Medium;\n\n  constructor(\n    element: Element,\n    subscriptionManagerService: SubscriptionManagerService,\n    private storeService: StoreService<IState>,\n    private distinarService: DistinarService,\n    private distinarWebSocket: DistinarWebSocketService<LecturerListenerMap, LecturerEmitMap>,\n    private distinarApiService: DistinarApiService,\n  ) {\n    this.domElement = element as HTMLElement;\n    this.subscriptionManager = subscriptionManagerService.createSubscriptionManager();\n  }\n\n  protected attached(): void {\n    this.storeService.subscribeToDetailedChanges(this, [\n      state => state.distinar.attendees,\n    ], state => {\n      this.currentConferenceAttendees = state.distinar.attendees.filter(a => a.online);\n      this.allConferenceAttendees = state.distinar.attendees;\n    });\n\n    this.storeService.subscribeToDetailedChanges(this, [\n      state => state.distinar.lectureStatus?.quiz?.answerTime,\n    ], state => {\n      this.quizAnswerTime = state.distinar.lectureStatus?.quiz?.answerTime ?? QuizAnswerTime.Medium;\n    });\n\n    this.subscriptionManager.subscribeToElementResize(this.domElement, this.updateScaleFactor.bind(this));\n    // This is a bit hacky, but the fullscreenchange event fires before the relevant elements have their new sizes.\n    // So for some amount of time after a fullscreen change event, we just refresh continuously. Feel free to improve ;)\n    this.subscriptionManager.subscribeToFullscreenChange(() => {\n      this.continuousScaleUpdateInterval = window.setInterval(this.updateScaleFactor.bind(this), 30);\n      setTimeout(() => this.continuousScaleUpdateInterval != null && window.clearInterval(this.continuousScaleUpdateInterval), 300);\n    });\n\n    FunctionUtils.timeout(0, () => this.updateScaleFactor());\n  }\n\n  protected detached(): void {\n    this.cancelStateTransition();\n    this.storeService.disposeAllSubscriptions(this);\n    this.subscriptionManager.disposeSubscriptions();\n    this.resourcePreloader.free();\n    if(this.continuousScaleUpdateInterval != null) window.clearInterval(this.continuousScaleUpdateInterval);\n  }\n\n  private lectureStatusChanged(newLectureStatus: ILectureStatusDto | undefined, oldLectureStatus: ILectureStatusDto | undefined): void {\n    this.handleQuizStatusUpdate(newLectureStatus?.quiz, oldLectureStatus?.quiz);\n  }\n\n  private async updateQuizContent(quizSessionId: number): Promise<void> {\n    const response = await this.distinarApiService.getQuizContent(quizSessionId);\n    this.quizContent = response.content;\n    this.quizConfig = response.config;\n    const quizState = this.lectureStatus?.quiz?.state;\n    if(quizState) {\n      this.handleStateTransition(quizState);\n    }\n  }\n\n  private handleQuizStatusUpdate(newQuizState?: ILectureQuizStatusDto | null, oldQuizState?: ILectureQuizStatusDto | null): void {\n    if(oldQuizState && !newQuizState) {\n      this.cancelStateTransition();\n    }\n\n    if(newQuizState && newQuizState.quizSessionId !== oldQuizState?.quizSessionId) {\n      this.updateQuizContent(newQuizState.quizSessionId);\n    }\n\n    if(newQuizState?.state && newQuizState.state !== oldQuizState?.state && this.quizContent) {\n      this.handleStateTransition(newQuizState.state);\n    }\n\n    // reset transition if view changes\n    if(oldQuizState && newQuizState\n      && oldQuizState?.quizSessionId === newQuizState?.quizSessionId\n      && newQuizState?.state === oldQuizState?.state\n    ) {\n      this.handleStateTransition(newQuizState.state);\n    }\n  }\n\n  private handleStateTransition(loadedState: LectureQuizState): void {\n    this.cancelStateTransition();\n\n    switch(loadedState) {\n      case LectureQuizState.Ready:\n        return this.handleTransitionToReadyState();\n      case LectureQuizState.Loading:\n        return this.handleTransitionToLoadingState();\n      case LectureQuizState.Intro:\n        return this.handleTransitionToIntroState();\n      case LectureQuizState.GetReady:\n        return this.handleTransitionToGetReadyState();\n      case LectureQuizState.Answer:\n        return this.handleTransitionToAnswerState();\n      case LectureQuizState.Outro:\n        return this.handleTransitionToOutroState();\n      case LectureQuizState.Result:\n        return this.handleTransitionToResultState();\n      case LectureQuizState.IntermediateResult:\n        return this.handleTransitionToIntermediateResultState();\n      case LectureQuizState.FinalResult:\n        return this.handleTransitionToFinalResultState();\n    }\n  }\n\n  private setStateTransitionCancelCallback(callback: () => void): void {\n    this.onCancelStateTransition = callback;\n  }\n\n  private cancelStateTransition(): void {\n    this.onCancelStateTransition?.();\n    this.onCancelStateTransition = null;\n  }\n\n  private handleTransitionToReadyState(): void {\n    this.progress = null;\n  }\n\n  private handleTransitionToLoadingState(): void {\n    const questions = this.quizContent?.questions;\n    const config = this.quizConfig;\n    assertNotNullOrUndefined(questions, 'Quiz content was not yet loaded');\n    assertNotNullOrUndefined(config, 'Quiz config was not yet loaded');\n    let canceled = false;\n    let animationFrame: { cancel: () => void } | undefined;\n\n    (async () => {\n      this.progress = 0;\n      this.percentLoaded = 0;\n      const imagePaths = QuestionUtils.extractResourcePathsFromQuestions(questions, null).images;\n      await this.resourcePreloader.loadResources(ResourceType.Image, imagePaths, {\n        onProgress: p => {\n          this.percentLoaded = Math.floor((p.failedCount + p.successCount) * 100 / imagePaths.length);\n          this.progress = this.percentLoaded;\n        }\n      });\n\n      if(canceled) return;\n\n      const getReadyTimeoutMs = config.getReadyTimeoutSeconds * 1000;\n      animationFrame = AnimationUtils.setAnimationFrameInterval(elapsedMs => {\n        this.progress = -100 + ProgressBar.MAX_PROGRESS * (elapsedMs / getReadyTimeoutMs);\n        if(this.progress >= 0) {\n          this.progress = 0;\n          this.cancelStateTransition();\n          void this.distinarWebSocket.connection.emit(LecturerEmitName.SetQuizState, {\n            newState: LectureQuizState.Intro,\n            questionIndex: 0,\n            subQuestionIndex: 0,\n          });\n        }\n      }, { invokeImmediately: true });\n    })();\n\n    this.setStateTransitionCancelCallback(() => {\n      canceled = true;\n      animationFrame?.cancel();\n    });\n  }\n\n  private handleTransitionToIntroState(): void {\n    this.progress = null;\n\n    const timeout = window.setTimeout(async () => {\n      await this.distinarService.requestQuestionLoadedSync(LectureQuizSlide.QUESTION_PRELOAD_TIMEOUT_MS);\n      void this.distinarWebSocket.connection.emit(LecturerEmitName.SetQuizState, {\n        newState: LectureQuizState.GetReady,\n      });\n    }, 1400);\n\n    this.setStateTransitionCancelCallback(() => window.clearTimeout(timeout));\n  }\n\n  private handleTransitionToGetReadyState(): void {\n    const config = this.quizConfig;\n    assertNotNullOrUndefined(config, 'Quiz config was not yet loaded');\n    const getReadyTimeoutMs = config.getReadyTimeoutSeconds * 1000 + LectureQuizSlide.GET_READY_TOLERANCE_TIMEOUT_MS;\n    const animationFrame = AnimationUtils.setAnimationFrameInterval(elapsedMs => {\n      this.progress = ProgressBar.MAX_PROGRESS * (elapsedMs / getReadyTimeoutMs);\n      if(this.progress >= ProgressBar.MAX_PROGRESS) {\n        this.progress = ProgressBar.MAX_PROGRESS;\n        this.cancelStateTransition();\n        void this.distinarWebSocket.connection.emit(LecturerEmitName.SetQuizState, {\n          newState: LectureQuizState.Answer,\n        });\n      }\n    }, { invokeImmediately: true });\n\n    this.setStateTransitionCancelCallback(() => animationFrame.cancel());\n  }\n\n  private handleTransitionToAnswerState(): void {\n    assertNotNullOrUndefined(this.quizConfig, 'quizConfig is undefined');\n\n    const answerTimeLimitMs = LectureQuizUtils.getAnswerTimeLimitMs(this.quizAnswerTime, this.quizConfig);\n\n    let continued = false;\n    const timeupOrAllAnswersSubmittedCallback = (): void => {\n      if(continued || this.quizAnswerTime === QuizAnswerTime.Manual) return;\n      continued = true;\n\n      this.cancelStateTransition();\n      this.continue();\n    };\n\n    const animationFrame = AnimationUtils.setAnimationFrameInterval(elapsedMs => {\n      this.progress = -ProgressBar.MAX_PROGRESS + ProgressBar.MAX_PROGRESS * (elapsedMs / answerTimeLimitMs);\n      if(this.progress >= 0) {\n        this.progress = 0;\n        timeupOrAllAnswersSubmittedCallback();\n      }\n    }, { invokeImmediately: true });\n\n    this.distinarService.requestAnswerSubmittedSync().then(() => timeupOrAllAnswersSubmittedCallback());\n\n    this.setStateTransitionCancelCallback(() => animationFrame.cancel());\n  }\n\n  private handleTransitionToResultState(): void {\n    this.progress = null;\n  }\n\n  private handleTransitionToOutroState(): void {\n    this.progress = null;\n    const timeout = window.setTimeout(() => {\n      assertNotNullOrUndefined(this.lectureStatus?.quiz, 'Need quiz status to go to next question');\n      assertNotNullOrUndefined(this.quizContent, 'Need quiz content to go to next question');\n\n      const nextQuestionIndex = this.lectureStatus.quiz.questionIndex + 1;\n      if(nextQuestionIndex >= this.quizContent.questions.length) {\n        void this.distinarWebSocket.connection.emit(LecturerEmitName.SetQuizState, {\n          newState: LectureQuizState.FinalResult,\n        });\n      } else {\n        void this.distinarWebSocket.connection.emit(LecturerEmitName.SetQuizState, {\n          newState: LectureQuizState.IntermediateResult,\n        });\n      }\n    }, 1000);\n\n    this.setStateTransitionCancelCallback(() => window.clearTimeout(timeout));\n  }\n\n  private handleTransitionToIntermediateResultState(): void {\n    this.progress = null;\n  }\n\n  private handleTransitionToFinalResultState(): void {\n    this.progress = null;\n  }\n\n  private handleContinueButtonClicked(): void {\n    this.continue();\n  }\n\n  private handleHelpTextButtonClicked(): void {\n    this.toggleHelpText();\n  }\n\n  private continue(): void {\n    assertNotNullOrUndefined(this.lectureStatus?.quiz, 'Need quiz status to go to next question');\n    assertNotNullOrUndefined(this.quizContent, 'Need quiz content to go to next question');\n\n    const quizState = this.lectureStatus.quiz.state;\n    const currentQuestionIndex = this.lectureStatus.quiz.questionIndex;\n    const nextSubQuestionIndex = this.lectureStatus.quiz.subQuestionIndex + 1;\n    const subQuestionCount = this.quizContent.questions[currentQuestionIndex].answers.length;\n\n    if(quizState === LectureQuizState.IntermediateResult) {\n      const nextQuestionIndex = this.lectureStatus.quiz.questionIndex + 1;\n      void this.distinarWebSocket.connection.emit(LecturerEmitName.SetQuizState, {\n        newState: LectureQuizState.Intro,\n        questionIndex: nextQuestionIndex,\n        subQuestionIndex: 0,\n      });\n    } else if(nextSubQuestionIndex >= subQuestionCount) {\n      if(quizState === LectureQuizState.Result) {\n        void this.distinarWebSocket.connection.emit(LecturerEmitName.SetQuizState, {\n          newState: LectureQuizState.Outro,\n        });\n      } else if([LectureQuizState.Answer, LectureQuizState.GetReady].includes(quizState)) {\n        void this.distinarWebSocket.connection.emit(LecturerEmitName.SetQuizState, {\n          newState: LectureQuizState.Result,\n          questionIndex: currentQuestionIndex,\n          subQuestionIndex: 0,\n        });\n      }\n    } else {\n      void this.distinarWebSocket.connection.emit(LecturerEmitName.SetQuizState, {\n        newState: quizState === LectureQuizState.Result ? LectureQuizState.Result : LectureQuizState.GetReady,\n        questionIndex: currentQuestionIndex,\n        subQuestionIndex: nextSubQuestionIndex,\n      });\n    }\n  }\n\n  private async startQuiz(): Promise<void> {\n    await this.distinarWebSocket.connection.emit(LecturerEmitName.StartQuiz, {});\n    void this.distinarWebSocket.connection.emit(LecturerEmitName.SetQuizState, {\n      newState: LectureQuizState.Loading,\n    });\n  }\n\n  private async closeQuiz(): Promise<void> {\n    await this.distinarWebSocket.connection.emit(LecturerEmitName.StopQuiz, {});\n  }\n\n  private async toggleHelpText(): Promise<void> {\n    await this.distinarWebSocket.connection.emit(LecturerEmitName.ToggleQuizShowHelpText, {});\n  }\n\n  private updateScaleFactor(): void {\n    if(this.quizContainer) {\n      const width = parseFloat(window.getComputedStyle(this.quizContainer).width);\n      this.scaleFactor = width / 1440; // 16/9 width of Zeplin designs with height 810px\n    }\n  }\n\n  private shouldShowQuizQuestion(quizState: LectureQuizState): boolean {\n    return LectureQuizUtils.quizStateHasQuestion(quizState);\n  }\n}\n","import { autoinject, bindable } from 'aurelia-framework';\nimport { IListStandardItem } from 'common/aurelia-components/list-standard/list-standard';\nimport { IQuizAttemptContent } from 'common/dto/course/quiz/quiz.response';\nimport { IAttendeeDto } from 'common/dto/distinar/distinar.interface';\nimport { ContrastMode } from 'common/enum/quiz.attempt.status.enum';\nimport { DomUtils } from 'common/utils/DomUtils';\n\n/**\n * @event {StartQuizEvent} quiz-started\n */\n@autoinject()\nexport class LectureQuizStart {\n  @bindable() public quizContent: IQuizAttemptContent | null = null;\n  @bindable() public players: IAttendeeDto[] = [];\n  @bindable() public showHeaderButton = true;\n  @bindable() public contrastMode: ContrastMode = ContrastMode.Default;\n\n  private playersListItems: IListStandardItem[] = [];\n\n  constructor(private element: Element) { }\n\n  private playersChanged(): void {\n    this.playersListItems = this.players.map(p => ({\n      graphic: {\n        avatarSvgDownloadLink: p.avatarSvgDownloadLink,\n        disabled: false,\n        points: p.points,\n      },\n      icons: [],\n      separatorAfter: true,\n      text: {\n        primary: p.nickname ?? {\n          textTk: 'pages.lectureQuiz.anonymousPlayer'\n        }\n      },\n    }));\n  }\n\n  private handleHeaderButtonClicked(): void {\n    DomUtils.fireEvent(this.element as HTMLElement, {\n      name: 'quiz-started',\n      bubbles: false,\n    });\n  }\n}\n\nexport type StartQuizEvent = CustomEvent<never>;\n","import { PLATFORM } from 'aurelia-pal';\nimport { FrameworkConfiguration } from 'aurelia-framework';\n\nexport function configure(config: FrameworkConfiguration): void {\n  config.globalResources(PLATFORM.moduleName('resources/elements/lecture-side-menu/lecture-side-menu'));\n  config.globalResources(PLATFORM.moduleName('resources/elements/lecture-side-menu/lecture-side-menu-attendee/lecture-side-menu-attendee'));\n  config.globalResources(PLATFORM.moduleName('resources/elements/lecture-side-menu/lecture-side-menu-attendee-list/lecture-side-menu-attendee-list'));\n  config.globalResources(PLATFORM.moduleName('resources/elements/lecture-side-menu/lecture-side-menu-chat/lecture-side-menu-chat'));\n}\n","import { autoinject, bindable } from 'aurelia-framework';\nimport { StoreService } from 'common/aurelia-services/StoreService';\nimport { IEventDto } from 'common/dto/tenant/event/tenant.event.interface';\nimport { DistinarService } from '../../../../services/DistinarService';\nimport { selectAttendeeIdsSortedByName } from '../../../../store/distinar.selectors';\nimport { IState } from '../../../../store/state';\n\n@autoinject()\nexport class LectureSideMenuAttendeeList {\n  @bindable() public activeLecture: IEventDto<string> | null = null;\n  @bindable() public isConferenceActive = false;\n\n  private participatingAttendeeIds: Array<number> = [];\n  private notParticipatingAttendeeIds: Array<number> = [];\n\n  private tenantId: number | null = null;\n  private allAudioLocked = false;\n  private allVideoLocked = false;\n\n  constructor(\n    private storeService: StoreService<IState>,\n    private distinarService: DistinarService,\n  ) { }\n\n  protected attached(): void {\n    this.storeService.subscribeToDetailedChanges(this, [\n      state => state.currentRole?.tenantId ?? null,\n    ], (_state, tenantId) => {\n      this.tenantId = tenantId;\n    });\n\n    this.storeService.subscribeMemoized(\n      this,\n      selectAttendeeIdsSortedByName,\n      attendeeIds => {\n        this.participatingAttendeeIds = attendeeIds;\n      },\n      { participating: true },\n    );\n\n    this.storeService.subscribeMemoized(\n      this,\n      selectAttendeeIdsSortedByName,\n      attendeeIds => {\n        this.notParticipatingAttendeeIds = attendeeIds;\n      },\n      { participating: false },\n    );\n  }\n\n  protected detached(): void {\n    this.storeService.disposeAllSubscriptions(this);\n    this.participatingAttendeeIds = [];\n    this.notParticipatingAttendeeIds = [];\n    this.tenantId = null;\n  }\n\n  private async deactivateAndLockAllAudio(): Promise<void> {\n    await this.distinarService.deactivateAndLockAudioForAll();\n    this.allAudioLocked = true;\n  }\n\n  private async deactivateAndLockAllVideo(): Promise<void> {\n    await this.distinarService.deactivateAndLockVideoForAll();\n    this.allVideoLocked = true;\n  }\n\n  private async unlockAllAudio(): Promise<void> {\n    await this.distinarService.unlockAudioForAll();\n    this.allAudioLocked = false;\n  }\n\n  private async unlockAllVideo(): Promise<void> {\n    await this.distinarService.unlockVideoForAll();\n    this.allVideoLocked = false;\n  }\n}\n","import { autoinject, bindable, computedFrom } from 'aurelia-framework';\nimport { StoreService } from 'common/aurelia-services/StoreService';\nimport { IAttendeeDto } from 'common/dto/distinar/distinar.interface';\nimport { AttendeeInputMediaState } from 'common/enum/distinar.enum';\nimport { assertNotNullOrUndefined } from 'common/utils/assert';\nimport { LogUtils } from 'common/utils/LogUtils';\nimport { EventsApiService } from '../../../../services/api/EventsApiService';\nimport { CustomDialogService } from '../../../../services/CustomDialogService';\nimport { DistinarService } from '../../../../services/DistinarService';\nimport { ToastService } from '../../../../services/ToastService';\nimport { selectAttendeeById } from '../../../../store/distinar.selectors';\nimport { IState } from '../../../../store/state';\n\n@autoinject()\nexport class LectureSideMenuAttendee {\n  @bindable() private attendeeId: number | null = null;\n  @bindable() private tenantId: number | null = null;\n  @bindable() private lectureId: number | null = null;\n  @bindable() private showMediaButtons = false;\n\n  private attendee: IAttendeeDto | null = null;\n\n  constructor(\n    private storeService: StoreService<IState>,\n    private eventsApi: EventsApiService,\n    private customDialogService: CustomDialogService,\n    private toastService: ToastService,\n    private distinarService: DistinarService,\n  ) {}\n\n  protected attached(): void {\n    this.subscribeToState();\n  }\n\n  protected detached(): void {\n    this.storeService.disposeAllSubscriptions(this);\n    this.attendee = null;\n  }\n\n  private attendeeIdChanged(): void {\n    this.subscribeToState();\n  }\n\n  private subscribeToState(): void {\n    this.storeService.disposeAllSubscriptions(this);\n    if(this.attendeeId == null) return;\n\n    this.storeService.subscribeToDetailedChanges(this, [\n      selectAttendeeById(this.attendeeId),\n    ], (_state, attendee) => {\n      this.attendee = attendee;\n    });\n  }\n\n  @computedFrom('attendee')\n  private get audioStateIcon(): AudioStateIcon {\n    switch(this.attendee?.conferenceAudioState) {\n      case AttendeeInputMediaState.INACTIVE:\n      case AttendeeInputMediaState.LOCKED:\n        return AudioStateIcon.MicOff;\n      default: return AudioStateIcon.Mic;\n    }\n  }\n\n  @computedFrom('attendee')\n  private get videoStateIcon(): VideoStateIcon {\n    switch(this.attendee?.conferenceVideoState) {\n      case AttendeeInputMediaState.INACTIVE:\n      case AttendeeInputMediaState.LOCKED:\n        return VideoStateIcon.VideoOff;\n      default: return VideoStateIcon.Video;\n    }\n  }\n\n  @computedFrom('attendee')\n  private get isPhysicallyPresent(): boolean {\n    return this.attendee?.online === true && this.attendee?.isPhysicallyPresent === true;\n  }\n\n  private async handleVideoButtonClicked(): Promise<void> {\n    assertNotNullOrUndefined(this.attendee, 'attendee cannot be null or undefined');\n    if(this.attendee?.conferenceVideoState === AttendeeInputMediaState.LOCKED) {\n      await this.distinarService.unlockVideoForUser(this.attendee.id);\n    } else {\n      await this.distinarService.deactivateAndLockVideoForUser(this.attendee.id);\n    }\n  }\n\n  private async handleAudioButtonClicked(): Promise<void> {\n    assertNotNullOrUndefined(this.attendee, 'attendee cannot be null or undefined');\n    if(this.attendee.conferenceAudioState === AttendeeInputMediaState.LOCKED) {\n      await this.distinarService.unlockAudioForUser(this.attendee.id);\n    } else {\n      await this.distinarService.deactivateAndLockAudioForUser(this.attendee.id);\n    }\n  }\n\n  private async handleLowerHandButtonClicked(): Promise<void> {\n    assertNotNullOrUndefined(this.attendee, 'attendee cannot be null or undefined');\n    await this.distinarService.lowerUserHand(this.attendee.id);\n  }\n\n  private async showManageAttendanceDialog(): Promise<void> {\n    assertNotNullOrUndefined(this.attendee, 'attendee cannot be null or undefined');\n    assertNotNullOrUndefined(this.tenantId, 'tenantId cannot be null or undefined');\n    assertNotNullOrUndefined(this.lectureId, 'lectureId cannot be null or undefined');\n\n    const userWithAttendence = await this.eventsApi.getAttendee(this.tenantId, this.lectureId, this.attendee.id);\n    const modifiedAttendanceData = await this.customDialogService.showManageAttendanceDialog(userWithAttendence);\n    if(modifiedAttendanceData) {\n      try {\n        await this.eventsApi.editEventAttendance(this.lectureId, this.attendee.id, modifiedAttendanceData);\n        this.toastService.success('elements.lectureSideMenu.editAttendanceSuccess');\n      } catch(e) {\n        LogUtils.exception(e);\n        this.toastService.error('elements.lectureSideMenu.editAttendanceError');\n      }\n    }\n  }\n}\n\nenum AudioStateIcon {\n  Mic = 'Distinar/LecturerMic',\n  MicOff = 'Distinar/LecturerMicOff',\n}\n\nenum VideoStateIcon {\n  Video = 'Distinar/LecturerCam',\n  VideoOff = 'Distinar/LecturerCamOff',\n}\n","import { autoinject, bindable } from 'aurelia-framework';\nimport { ChatCacheService } from 'common/aurelia-services/ChatCacheService';\nimport { StoreService } from 'common/aurelia-services/StoreService';\nimport { IChatRoomIdWithUsersDto } from 'common/dto/user/chat/chat.interface';\nimport { markDistinarChatConversationAsRead } from 'common/store/mutations/distinar.mutations';\nimport { DistinarService } from 'services/DistinarService';\nimport { IState } from 'store/state';\n\n@autoinject()\nexport class LectureSideMenuChat {\n  @bindable() public currentUserId: number | null = null;\n\n  private messagingAllowed = true;\n  private chatRoom: IChatRoomIdWithUsersDto<string> | null = null;\n\n  constructor(\n    private storeService: StoreService<IState>,\n    private chatCacheService: ChatCacheService,\n    private distinarService: DistinarService,\n  ) {}\n\n  protected attached(): void {\n    this.storeService.subscribeToDetailedChanges(this, [\n      state => state.distinar.activeLecture?.chatRoomId,\n    ], state => {\n      if(state.distinar.activeLecture?.chatRoomId) {\n        this.chatCacheService.ensureChatRoomIdWithUsersInCache(state.distinar.activeLecture.chatRoomId);\n      }\n    });\n\n    this.storeService.subscribeToDetailedChanges(this, [\n      state => state.distinar.activeLecture?.chatRoomId,\n      state => state.distinar.activeLecture?.chatRoomId != null\n        ? (state.cache.chatRoomIdWithUsersByChatRoomId[state.distinar.activeLecture.chatRoomId] ?? null)\n        : null,\n    ], (_state, chatRoomId, chatRoomIdWithUsersByChatRoomId) => {\n      if(chatRoomId) {\n        this.chatRoom = chatRoomIdWithUsersByChatRoomId;\n        // 🚨 distinar side-effect\n        this.storeService.dispatch(markDistinarChatConversationAsRead);\n      } else {\n        this.chatRoom = null;\n      }\n    });\n\n    this.distinarService.addMessageSentListener(this, () => {\n      if(!this.chatRoom) return;\n      this.chatCacheService.ensureLatestConversationInCache(this.chatRoom.id);\n      // 🚨 distinar side-effect\n      this.storeService.dispatch(markDistinarChatConversationAsRead);\n    });\n  }\n\n  protected detached(): void {\n    this.distinarService.removeListenersByScope(this);\n    this.storeService.disposeAllSubscriptions(this);\n  }\n}\n","import { autoinject, computedFrom } from 'aurelia-framework';\nimport { StoreService } from 'common/aurelia-services/StoreService';\nimport { IRoleAssignment } from 'common/dto/auth/auth.interface';\nimport { IEventDto } from 'common/dto/tenant/event/tenant.event.interface';\nimport Moment from 'moment';\nimport { IState } from 'store/state';\nimport { EventsApiService } from '../../../services/api/EventsApiService';\nimport { CustomDialogService } from '../../../services/CustomDialogService';\nimport { ToastService } from '../../../services/ToastService';\nimport { closeDistinarSideMenu, setDistinarSideMenuSection } from '../../../store/lecturer.distinar.mutations';\n\nexport enum LectureSideMenuSectionName {\n  AttendeeList = 'attendee-list',\n  Chat = 'chat',\n}\n\ninterface ILectureSideMenuTab {\n  titleTk: string;\n  hasUpdates?: boolean;\n}\n\n@autoinject()\nexport class LectureSideMenu {\n  private activeLecture: IEventDto<string> | null = null;\n  private currentUserId: number | null = null;\n  private currentRole: IRoleAssignment<string> | null = null;\n  private isConferenceActive = false;\n  private isOpen: boolean | null = null;\n  private isQuickDistinar: boolean = false;\n\n  private domElement: HTMLElement;\n\n  private LectureSideMenuSectionName = LectureSideMenuSectionName;\n\n  private tabs: Record<LectureSideMenuSectionName, ILectureSideMenuTab> = {\n    [LectureSideMenuSectionName.Chat]: { titleTk: 'elements.lectureSideMenu.chat' },\n    [LectureSideMenuSectionName.AttendeeList]: { titleTk: 'elements.lectureSideMenu.attendeeList' },\n  };\n  private activeTab: LectureSideMenuSectionName = LectureSideMenuSectionName.Chat;\n\n  constructor(\n    element: Element,\n    private storeService: StoreService<IState>,\n    private customDialogService: CustomDialogService,\n    private toastService: ToastService,\n    private api: EventsApiService,\n  ) {\n    this.domElement = element as HTMLElement;\n  }\n\n  private attached(): void {\n    this.storeService.subscribeToDetailedChanges(this, [\n      state => state.distinar.activeLecture,\n      state => state.user.id,\n      state => state.currentRole,\n      state => !!state.distinar.lectureStatus?.conference.active,\n      state => state.distinar.sideMenu.open,\n    ], (_state, activeLecture, userId, currentRole, conferenceActive, sideMenuOpen) => {\n      this.activeLecture = activeLecture;\n      this.currentUserId = userId;\n      this.currentRole = currentRole;\n      this.isConferenceActive = conferenceActive;\n      this.isOpen = sideMenuOpen;\n      this.isQuickDistinar = !!(activeLecture?.isQuickQuiz || activeLecture?.isQuickDistinar);\n    });\n\n    this.storeService.subscribeToDetailedChanges(this, [\n      state => state.distinar.chatRoomLastRead,\n      state => state.distinar.chatRoomLastMessageReceived,\n      state => state.distinar.sideMenu.activeSideMenuSection,\n    ], state => {\n      const lastMessageReceived = state.distinar.chatRoomLastMessageReceived;\n      const lastRead = state.distinar.chatRoomLastRead ?? undefined;\n\n      const showChatIconIndicator = !!(\n        state.distinar.activeLecture?.chatRoomId\n        && lastMessageReceived\n        && (!lastRead || Moment(lastMessageReceived).isAfter(lastRead))\n      );\n\n      this.refreshTabStates(showChatIconIndicator);\n\n      if(state.distinar.sideMenu.activeSideMenuSection) {\n        this.activeTab = state.distinar.sideMenu.activeSideMenuSection;\n      }\n    });\n  }\n\n  protected detached(): void {\n    this.storeService.disposeAllSubscriptions(this);\n  }\n\n  private refreshTabStates(showChatIconIndicator: boolean): void {\n    this.tabs = {\n      ...this.tabs,\n      [LectureSideMenuSectionName.Chat]: {\n        ...this.tabs[LectureSideMenuSectionName.Chat],\n        hasUpdates: showChatIconIndicator,\n      }\n    };\n  }\n\n  private handleTabClicked(tabName: LectureSideMenuSectionName): void {\n    this.storeService.dispatch(setDistinarSideMenuSection, tabName);\n  }\n\n  private closeSideMenu(): void {\n    this.isOpen = false;\n    this.storeService.dispatch(closeDistinarSideMenu);\n  }\n\n  @computedFrom('isQuickDistinar')\n  private get shownTabs(): LectureSideMenuSectionName[] {\n    return this.isQuickDistinar\n      ? [LectureSideMenuSectionName.AttendeeList]\n      : [LectureSideMenuSectionName.Chat, LectureSideMenuSectionName.AttendeeList];\n  }\n}\n","import { autoinject, bindable } from 'aurelia-framework';\nimport { Router } from 'aurelia-router';\nimport { StoreService } from 'common/aurelia-services/StoreService';\nimport { EventType } from 'common/dto/course/content/course.types';\nimport { FilterOperationType } from 'common/dto/filter/filter.enum';\nimport { IFilterDto } from 'common/dto/filter/filter.interface';\nimport { IEventDto } from 'common/dto/tenant/event/tenant.event.interface';\nimport { LectureUtils } from 'common/utils/LectureUtils';\nimport { LogUtils } from 'common/utils/LogUtils';\nimport { EventsApiService } from '../../../services/api/EventsApiService';\nimport { CustomDialogService } from '../../../services/CustomDialogService';\nimport { DateService } from '../../../services/DateService';\nimport { DistinarService } from '../../../services/DistinarService';\nimport { SeparateWindowService } from '../../../services/SeparateWindowService';\nimport { StudentService } from '../../../services/StudentService';\nimport { ToastService } from '../../../services/ToastService';\nimport { IState } from '../../../store/state';\nimport { ENavigationTab } from '../../pages/student-invitations-page/student-invitations-page';\nimport { ActionsCell } from '../custom-table/actions-cell/actions-cell';\nimport { ButtonCell } from '../custom-table/button-cell/button-cell';\nimport { GetShownEntriesDelegate } from '../custom-table/custom-table';\nimport { CustomTableCell } from '../custom-table/custom-table-cell/custom-table-cell';\nimport {\n  createPrepareServersideSortingFunction,\n  deepPropertyString,\n  prepareServersideFiltering,\n  prepareServersideRangeFiltering,\n  VirtualFilterField\n} from '../custom-table/custom-table-helpers';\nimport { IColumnDefinition } from '../custom-table/custom-table-interfaces';\n\n@autoinject()\nexport class LecturerEventsTable {\n  @bindable() public fetchTableDataDelegate!: GetShownEntriesDelegate;\n  @bindable() public hideFooter: boolean = false;\n  @bindable() public unbounded: boolean = false;\n  @bindable() public tableRefreshVersion: number = 0;\n  @bindable() public preventFetching: boolean = false;\n  @bindable() public showFilters: boolean = false;\n  @bindable() public allowSorting: boolean = false;\n  @bindable() public filter: IFilterDto<IEventDto<string>> = {};\n\n  private tenantId: number | null = null;\n  private hasActiveLecture: boolean = false;\n\n  private tableColumns: Array<IColumnDefinition<CustomTableCell<IEventDto<string>> | ActionsCell<IEventDto<string>> | ButtonCell<IEventDto<string>>>> = [\n    {\n      id: 'eventTitle',\n      nameTk: 'pages.lecturerEvents.eventTitle',\n      cellViewModel: CustomTableCell,\n      cellOptions: {\n        transformer: deepPropertyString('title')\n      },\n      prepareSorting: createPrepareServersideSortingFunction(() => this.filter, 'title'),\n      filter: {\n        placeholderTk: 'globals.filter',\n        prepare: v => prepareServersideFiltering(this.filter, 'title', v),\n      },\n      wrap: true\n    },\n    {\n      id: 'type',\n      nameTk: 'pages.lecturerEvents.type',\n      cellViewModel: CustomTableCell,\n      cellOptions: {\n        tk: lecture => `globals.eventTypes.${lecture.type}`\n      },\n      prepareSorting: createPrepareServersideSortingFunction(() => this.filter, 'type'),\n      filter: {\n        placeholderTk: 'globals.filter',\n        prepare: v => prepareServersideFiltering(this.filter, 'type' as VirtualFilterField, v, FilterOperationType.Eq),\n        options: Object.values(EventType).map(eT => ({ keyTk: `globals.eventTypes.${eT}`, value: eT }))\n      }\n    },\n    {\n      id: 'status',\n      nameTk: 'pages.lecturerEvents.status',\n      cellViewModel: CustomTableCell,\n      cellOptions: {\n        tk: event => {\n          const status = LectureUtils.getLecturerEventStatus(event);\n          if(status == null) return '';\n          return `pages.lecturerEvents.statuses.${status}`;\n        },\n        spanClasses: event => {\n          const status = LectureUtils.getLecturerEventStatus(event);\n          if(status == null) return '';\n\n          return `lecturer-events-table__status lecturer-events-table__status--${status}`;\n        },\n      },\n      wrap: true\n    },\n    {\n      id: 'from',\n      nameTk: 'globals.from',\n      cellViewModel: CustomTableCell,\n      cellOptions: {\n        transformer: event => this.dateService.formatDateTime(event.plannedDateFrom)\n      },\n      prepareSorting: createPrepareServersideSortingFunction(() => this.filter, 'plannedDateFrom'),\n      filter: {\n        placeholderTk: 'globals.filter',\n        daterange: true,\n        prepare: v => prepareServersideRangeFiltering(this.filter, 'plannedDateFrom', v),\n        initialValue: new Date(),\n      },\n    },\n    {\n      id: 'to',\n      nameTk: 'globals.to',\n      cellViewModel: CustomTableCell,\n      cellOptions: {\n        transformer: event => this.dateService.formatTime(event.plannedDateTo)\n      },\n      prepareSorting: createPrepareServersideSortingFunction(() => this.filter, 'plannedDateTo'),\n      filter: {\n        placeholderTk: 'globals.filter',\n        daterange: true,\n        prepare: v => prepareServersideRangeFiltering(this.filter, 'plannedDateTo', v),\n      },\n    },\n    {\n      id: 'course',\n      nameTk: 'globals.course',\n      cellViewModel: CustomTableCell,\n      cellOptions: {\n        transformer: event => event.courses.map(course => course.title).join(', ')\n      },\n      prepareSorting: createPrepareServersideSortingFunction(() => this.filter, 'courseTitle' as VirtualFilterField),\n      filter: {\n        placeholderTk: 'globals.filter',\n        prepare: v => prepareServersideFiltering(this.filter, 'courseTitle' as VirtualFilterField, v, FilterOperationType.Like),\n      },\n      wrap: true\n    },\n    {\n      id: 'group',\n      nameTk: 'globals.group',\n      cellViewModel: CustomTableCell,\n      cellOptions: {\n        transformer: event => event.courseGroups.map(course => course.name).join(', ')\n      },\n      prepareSorting: createPrepareServersideSortingFunction(() => this.filter, 'courseGroupName' as VirtualFilterField),\n      filter: {\n        placeholderTk: 'globals.filter',\n        prepare: v => prepareServersideFiltering(this.filter, 'courseGroupName' as VirtualFilterField, v, FilterOperationType.Like),\n      },\n      wrap: true\n    },\n    {\n      id: 'button',\n      nameTk: 'globals.students',\n      cellViewModel: ButtonCell,\n      shrink: true,\n      cellOptions: {\n        buttonText: () => ({\n          tk: 'globals.showStudentList'\n        }),\n        clickCallback: async e => this.displayStudentsDialog(e.id)\n      }\n    },\n    {\n      id: 'actions',\n      nameTk: '',\n      cellViewModel: ActionsCell,\n      shrink: true,\n      cellOptions: {\n        entries: [\n          {\n            labelTk: 'globals.manageAttendees',\n            clickCallback: lecture => { this.router.navigateToRoute('event-attendants/id', { eventId: lecture.id }); }\n          },\n          {\n            labelTk: 'pages.lecturerEvents.lectureSettings',\n            clickCallback: lecture => this.showLectureSettingsDialog(lecture),\n            showCallback: event => event.type === EventType.Lecture\n          },\n        ],\n      }\n    },\n    {\n      id: 'button',\n      nameTk: '',\n      cellViewModel: ButtonCell,\n      shrink: true,\n      visible: lecture => !this.separateWindowService.getSeparateWindowType() && lecture != null && LectureUtils.isEventStartOrJoinableLecture(lecture),\n      cellOptions: {\n        buttonText: () => ({\n          tk: 'globals.start'\n        }),\n        clickCallback: async lecture => {\n          try {\n            if(lecture.actualDateFrom) {\n              await this.reconnectLecture(lecture);\n            } else {\n              await this.startLecture(lecture);\n            }\n          } catch(error) {\n            await this.distinarService.showStartLectureErrorDialog(error, lecture);\n          }\n        }\n      }\n    },\n  ];\n\n  constructor(\n    private toastService: ToastService,\n    private dateService: DateService,\n    private customDialogService: CustomDialogService,\n    private router: Router,\n    private api: EventsApiService,\n    private distinarService: DistinarService,\n    private separateWindowService: SeparateWindowService,\n    private storeService: StoreService<IState>,\n    private studentService: StudentService\n  ) { }\n\n  protected attached(): void {\n    this.storeService.subscribeToDetailedChanges(this, [\n      state => state.currentRole?.tenantId ?? null,\n      state => !!state.distinar.activeLecture,\n    ], (_state, tenantId, hasActiveLecture) => {\n      this.tenantId = tenantId;\n      this.hasActiveLecture = hasActiveLecture;\n    });\n  }\n\n  protected detached(): void {\n    this.storeService.disposeAllSubscriptions(this);\n  }\n\n  private async startLecture(lecture: IEventDto<string>): Promise<void> {\n    if(this.hasActiveLecture) {\n      this.toastService.error('pages.lecturerEvents.startLectureAlreadyActiveError');\n      return;\n    }\n    await this.distinarService.connect(lecture.id);\n  }\n\n  private async reconnectLecture(lecture: IEventDto<string>): Promise<void> {\n    await this.distinarService.connect(lecture.id);\n  }\n\n  private async showLectureSettingsDialog(lecture: IEventDto<string>): Promise<void> {\n    if(!this.tenantId) return;\n    const modifiedLecture = await this.customDialogService.showLectureSettingsDialog(\n      {\n        plannedDateFrom: lecture.plannedDateFrom,\n        plannedDateTo: lecture.plannedDateTo,\n        description: lecture.description,\n        title: lecture.title,\n      }\n    );\n\n    if(modifiedLecture && modifiedLecture.plannedDateFrom && modifiedLecture.plannedDateTo) {\n      try {\n        await this.api.updateLecture(this.tenantId, lecture.id, modifiedLecture);\n        this.toastService.success('pages.lecturerEvents.saveSuccess');\n        this.tableRefreshVersion++;\n      } catch(e) {\n        LogUtils.exception(e);\n        this.toastService.error('pages.lecturerEvents.saveError');\n      }\n    }\n    this.tableRefreshVersion++;\n  }\n\n  private async displayStudentsDialog(eventId: number): Promise<void> {\n    const userId = await this.customDialogService.showStudentsDialog({ eventId });\n    if(!userId) return;\n    this.studentService.navigateToStudentInvitationsPage(userId, ENavigationTab.EVENTS);\n  }\n}\n","import { autoinject, bindable } from 'aurelia-framework';\nimport { ILectureStatusDto } from 'common/dto/distinar/distinar.interface';\nimport { assertNotNullOrUndefined } from 'common/utils/assert';\nimport { DomUtils } from 'common/utils/DomUtils';\nimport { FunctionUtils } from 'common/utils/FunctionUtils';\nimport { FullscreenableContainer } from 'common/aurelia-components/fullscreenable-container/fullscreenable-container';\n\n@autoinject()\nexport class LecturerLectureQuiz {\n  public static MOUSE_MOVED_RECENTLY_TIMEOUT = 1000;\n\n  @bindable()\n  public lectureStatus!: ILectureStatusDto;\n\n  private fullscreenableContainer: FullscreenableContainer | null = null;\n  private isFullscreen = false;\n  private mouseMovedRecently = false;\n  private mouseInsideControlElement = false;\n  private debouncedResetMouseMoved = FunctionUtils.debounce(() => { this.mouseMovedRecently = false; }, LecturerLectureQuiz.MOUSE_MOVED_RECENTLY_TIMEOUT);\n\n  protected detached(): void {\n    this.debouncedResetMouseMoved.cancel();\n    this.mouseMovedRecently = false;\n  }\n\n  public requestFullscreen(): void {\n    assertNotNullOrUndefined(this.fullscreenableContainer, 'can\\'t LecturerCourseLectureSlide.handleMaximizeClick without a contentWrapper');\n    this.fullscreenableContainer.requestFullscreen();\n  }\n\n  private handleMinimizeClick(): void {\n    assertNotNullOrUndefined(this.fullscreenableContainer, 'can\\'t LecturerCourseLectureSlide.handleMinimizeClick without a contentWrapper');\n    this.fullscreenableContainer.exitFullscreen();\n  }\n\n  private handleMousemove(event: MouseEvent): void {\n    assertNotNullOrUndefined(event.target, 'event without a target in LecturerCourseLectureSlide.handleMousemove');\n    this.mouseInsideControlElement = this.elementIsInsideControlElement(event.target as HTMLElement);\n\n    this.mouseMovedRecently = true;\n    this.debouncedResetMouseMoved();\n  }\n\n  private elementIsInsideControlElement(element: HTMLElement): boolean {\n    return DomUtils.isInsideElementWithDataAttribute(element, 'controlElement');\n  }\n}\n","import { computedFrom } from 'aurelia-framework';\nimport { Router } from 'aurelia-router';\nimport { connectTo, Store } from 'aurelia-store';\nimport { IRoleAssignment } from 'common/dto/auth/auth.interface';\nimport { RoleName } from 'common/enum/role.enums';\nimport { Config } from 'common/fe-config/config';\nimport { LogUtils } from 'common/utils/LogUtils';\nimport { NicknameUtils } from 'common/utils/NicknameUtils';\nimport { AuthApiService } from '../../../services/api/AuthApiService';\nimport { logoutAction } from '../../../store/actions';\nimport { IState } from '../../../store/state';\n\n@connectTo()\nexport class LoginStatusWidget {\n  private showMenu = false;\n  private state!: IState;\n  private readonly PROVIDER_NAME = 'KFV';\n  private accountLinksEnabled = Config().accountLinks.enabled;\n\n  constructor(\n    private router: Router,\n    private store: Store<IState>,\n    private authApiService: AuthApiService,\n  ) { }\n\n  @computedFrom('state.roles', 'state.currentRole')\n  private get otherRoles(): IRoleAssignment<string>[] {\n    return this.state.roles.filter(r =>\n      (r.role === RoleName.Provider && this.state.currentRole?.role !== RoleName.Provider) ||\n      (!r.pending && r.tenantId !== this.state.currentRole?.tenantId && r.isTenantEnabled)\n    );\n  }\n\n  @computedFrom('state.user')\n  private get tenantUserName(): string {\n    if(this.state == null) return '';\n    const name = NicknameUtils.getFullName(this.state.user);\n    return this.state.user.schoolName ? `${name} (${this.state.user.schoolName})` : name;\n  }\n\n  private toggleMenu(): void {\n    this.showMenu = !this.showMenu;\n  }\n\n  private changeRole(userTenantId: number): void {\n    this.authApiService.changeRole(userTenantId);\n    this.showMenu = false;\n  }\n\n  private async signOutClickedHandler(): Promise<void> {\n    try {\n      await this.authApiService.logout();\n    } catch(e) {\n      LogUtils.exception(e);\n    }\n    this.store.dispatch(logoutAction);\n    this.router.navigateToRoute('login');\n  }\n}\n","import { bindable } from 'aurelia-framework';\n\nexport class Markdownarea {\n  @bindable\n  protected value: string = '';\n  protected showPreview = true;\n}\n","import { autoinject, bindable } from 'aurelia-framework';\nimport { CheckedState } from '../tri-state-checkbox/tri-state-checkbox';\n\nexport interface IMatrixTableStatusFlair {\n  iconName: string;\n  iconClass: string;\n  checkedState: CheckedState;\n}\n\nexport interface IMatrixTableRowConfig<TColumn, TRow> {\n  labelTransformer: (dataRow: TRow) => string;\n  avatarSvgDownloadLink: (dataRow: TRow) => string | null;\n  labelClickCallback: (dataRow: TRow) => void;\n  dataClickCallback: (dataColumn: TColumn, dataRow: TRow) => void;\n  countableTransformer: (dataColumn: TColumn, dataRow: TRow) => boolean;\n  statusFlairTransformer: (dataColumn: TColumn, dataRow: TRow) => IMatrixTableStatusFlair;\n}\n\nexport interface IMatrixTableColumnConfig<TColumn> {\n  labelTransformer: (dataColumn: TColumn) => string;\n  iconKeyTransformer: (dataColumn: TColumn) => string;\n}\n\n@autoinject()\nexport class MatrixTable<TColumn, TRow> {\n  @bindable() public columnData: TColumn[] = [];\n  @bindable() public rowData: TRow[] = [];\n  @bindable() public rowConfig: IMatrixTableRowConfig<TColumn, TRow> | null = null;\n  @bindable() public columnConfig: IMatrixTableColumnConfig<TColumn> | null = null;\n  @bindable() public refreshVersion = 0;\n\n  private editModeActive: boolean = false;\n  private CheckedState = CheckedState;\n\n  private toggleEditMode(): void {\n    this.editModeActive = !this.editModeActive;\n  }\n\n  private getRowCount(rowData: TRow): number {\n    return this.columnData.reduce((total, column) => (this.rowConfig?.countableTransformer(column, rowData) ? total + 1 : total), 0);\n  }\n\n  private getColumnCount(columnData: TColumn): number {\n    return this.rowData.reduce((total, row) => (this.rowConfig?.countableTransformer(columnData, row) ? total + 1 : total), 0);\n  }\n\n  private getCheckedState(column: TColumn, row: TRow): CheckedState {\n    return this.rowConfig?.statusFlairTransformer(column, row).checkedState ?? CheckedState.UNCHECKED;\n  }\n}"],"names":[],"sourceRoot":""}