{"version":3,"file":"app-c8c525c4.f584a1446b2fc034d810.bundle.js","mappings":"AAGA","sources":["webpack://lernplattform-admin-frontend/./src/resources/pages/media-share-page/media-share-page.html","webpack://lernplattform-admin-frontend/./src/resources/pages/messages-page/messages-page.html","webpack://lernplattform-admin-frontend/./src/resources/pages/page-help-edit-page/page-help-edit-page.html","webpack://lernplattform-admin-frontend/./src/resources/pages/page-help-overview-page/page-help-overview-page.html","webpack://lernplattform-admin-frontend/./src/resources/pages/password-reset-page/password-reset-page.html","webpack://lernplattform-admin-frontend/./src/resources/pages/presentation-overview-page/presentation-overview-page.html","webpack://lernplattform-admin-frontend/./src/resources/pages/presentation-print-page/presentation-print-page.html","webpack://lernplattform-admin-frontend/./src/resources/pages/presentation-slides-page/presentation-slides-page.html","webpack://lernplattform-admin-frontend/./src/resources/pages/quiz-attempt-page/quiz-attempt-page.html","webpack://lernplattform-admin-frontend/./src/resources/pages/quiz-template-page/quiz-template-page.html","webpack://lernplattform-admin-frontend/./src/resources/pages/quiz-templates-page/quiz-templates-page.html","webpack://lernplattform-admin-frontend/./src/resources/pages/media-share-page/media-share-page.ts","webpack://lernplattform-admin-frontend/./src/resources/pages/messages-page/index.ts","webpack://lernplattform-admin-frontend/./src/resources/pages/messages-page/messages-page.ts","webpack://lernplattform-admin-frontend/./src/resources/pages/page-help-edit-page/page-help-edit-page.ts","webpack://lernplattform-admin-frontend/./src/resources/pages/page-help-overview-page/page-help-overview-page.ts","webpack://lernplattform-admin-frontend/./src/resources/pages/password-reset-page/password-reset-page.ts","webpack://lernplattform-admin-frontend/./src/resources/pages/presentation-overview-page/presentation-overview-page.ts","webpack://lernplattform-admin-frontend/./src/resources/pages/presentation-print-page/index.ts","webpack://lernplattform-admin-frontend/./src/resources/pages/presentation-print-page/presentation-print-page.ts","webpack://lernplattform-admin-frontend/./src/resources/pages/presentation-slides-page/presentation-slides-page.ts","webpack://lernplattform-admin-frontend/./src/resources/pages/quiz-attempt-page/quiz-attempt-page.ts","webpack://lernplattform-admin-frontend/./src/resources/pages/quiz-template-page/quiz-template-page.ts","webpack://lernplattform-admin-frontend/./src/resources/pages/quiz-templates-page/quiz-templates-page.ts"],"sourcesContent":["// Module\nvar code = \"<template> <div class=\\\"page-header\\\"> <h1 class=\\\"page-title\\\" t=\\\"globals.shares\\\"></h1> <div class=\\\"ml-auto\\\"> <share-media-button table-refresh-version.two-way=\\\"tableRefreshVersion\\\"></share-media-button> </div> </div> <div class=\\\"row row-cards row-deck\\\"> <div class=\\\"col-12\\\"> <media-share-table table-refresh-version.two-way=\\\"tableRefreshVersion\\\" for-overview-only.to-view=\\\"true\\\"></media-share-table> </div> </div> </template>\";\n// Exports\nexport default code;","// Module\nvar code = \"<template class=\\\"messages-page\\\"> <div class=\\\"page-header messages-page__page-header\\\"> <h1 class=\\\"page-title\\\" t=\\\"pages.messages.title\\\"></h1> <button-lightswitch t=\\\"[label]pages.messages.distinarSwitchLabel\\\" label-before.to-view=\\\"true\\\" theme=\\\"admin\\\" value.to-view=\\\"includeDistinarChats\\\" value-changed.trigger=\\\"handleIncludeDistinarChatsChanged()\\\"></button-lightswitch> </div> <div class=\\\"messages-page__wrapper\\\"> <div class=\\\"card messages-page__contact-card\\\"> <div class=\\\"card-header messages-page__contact-card-header\\\"> <search-input class=\\\"messages-page__search mr-2\\\" t=\\\"[placeholder]pages.messages.searchPlaceholder\\\" search-string.from-view=\\\"searchString\\\" search-active.from-view=\\\"searchActive\\\"></search-input> <button-standard click.trigger=\\\"handleSendMultiMessage()\\\" icon-name=\\\"System/Tabler/MessagePlus\\\" btn-color=\\\"primary\\\" btn-fill-mode=\\\"fill-width\\\"></button-standard> </div> <list-standard if.to-view=\\\"chatList.length > 0\\\" class=\\\"messages-page__contact-list\\\" items.to-view=\\\"chatList\\\" list-theme=\\\"manager-chat\\\"></list-standard> <div if.to-view=\\\"chatList.length === 0\\\" class=\\\"messages-page__empty-section\\\"> <span class=\\\"messages-page__empty-text\\\" t=\\\"pages.messages.chatsEmptyPageDescription\\\"></span> </div> <pagination-widget class=\\\"messages-page__pagination\\\" limit.to-view=\\\"filter.limit\\\" offset.to-view=\\\"filter.offset\\\" total.to-view=\\\"chatRoomsTotal\\\" new-offset-callback.to-view=\\\"newOffsetCallback\\\" num-page-quick-links.to-view=\\\"2\\\"></pagination-widget> </div> <div class=\\\"card messages-page__chat-card\\\"> <div class=\\\"messages-page__chat-wrapper\\\"> <div if.to-view=\\\"chatRoom != null\\\" class=\\\"card-header messages-page__chat-header\\\"> <div class=\\\"messages-page__chat-title-wrapper\\\"> <div class=\\\"messages-page__chat-title\\\" click.delegate=\\\"onGoToStudent()\\\">${chatRoomTitle}</div> <div class=\\\"messages-page__chat-subtitle\\\">${chatRoomSubtitle}</div> </div> <ul class=\\\"messages-page__course-intersection-list\\\"> <li repeat.for=\\\"intersection of courseIntersectionList\\\">${intersection}</li> </ul> </div> <chat-view if.to-view=\\\"chatRoom != null\\\" class=\\\"messages-page__chat\\\" chat-room.to-view=\\\"chatRoom\\\" input-value.two-way=\\\"rawChatInputValue\\\" input-actions.to-view=\\\"inputActions\\\" current-user-id.to-view=\\\"currentUserId\\\" messaging-allowed.to-view=\\\"!chatRoom.event\\\"></chat-view> <div if.to-view=\\\"chatRoom == null\\\" class=\\\"messages-page__empty-section\\\"> <span class=\\\"messages-page__empty-text\\\" t=\\\"pages.messages.noChatSelectedText\\\"></span> </div> </div> </div> </div> </template>\";\n// Exports\nexport default code;","// Module\nvar code = \"<template> <a route-href=\\\"route: page-help\\\"> <button type=\\\"button\\\" class=\\\"btn btn-secondary btn-sm page-help-edit-page__icon-btn\\\"> <safe-icon icon-name=\\\"System/Arrow - Left\\\" class=\\\"page-help-edit-page__icon\\\"></safe-icon> <span t=\\\"globals.back\\\"></span> </button> </a> <div class=\\\"page-header\\\"> <h1 class=\\\"page-title\\\" t=\\\"pages.pageHelp.editTitle\\\"></h1> </div> <section class=\\\"row\\\"> <div class=\\\"col-12\\\"> <div class=\\\"card\\\"> <div class=\\\"card-body\\\"> <div class=\\\"row\\\"> <div class=\\\"col-md-10\\\"> <div class=\\\"form-group\\\"> <label class=\\\"form-label required-label\\\" t=\\\"pages.pageHelp.path\\\"></label> <select class=\\\"form-control custom-select\\\" disabled.to-view=\\\"mode === Mode.Edit\\\" change.delegate=\\\"refreshRoute()\\\" value.bind=\\\"path\\\"> <option repeat.for=\\\"p of paths\\\" model.bind=\\\"p\\\"> ${p} </option> </select> </div> </div> <div class=\\\"col-md-2\\\"> <div class=\\\"form-group\\\"> <label class=\\\"form-label required-label\\\" t=\\\"globals.language\\\"></label> <select class=\\\"form-control custom-select\\\" disabled.to-view=\\\"mode === Mode.Edit\\\" change.delegate=\\\"refreshRoute()\\\" value.bind=\\\"language\\\"> <option repeat.for=\\\"lang of languages\\\" model.bind=\\\"lang\\\" t=\\\"globals.languageOptions.${lang}\\\"> </option> </select> </div> </div> </div> <div class=\\\"row\\\"> <div class=\\\"col-md-12\\\"> <page-help-text class=\\\"page-help-edit-page__help-text\\\" path.to-view=\\\"path\\\" language.to-view=\\\"language\\\" editor-context=\\\"page-help-edit-page\\\" mode.from-view=\\\"mode\\\"></page-help-text> </div> </div> </div> </div> </div> </section> </template>\";\n// Exports\nexport default code;","// Module\nvar code = \"<template> <div class=\\\"page-header\\\"> <h1 class=\\\"page-title\\\" t=\\\"pages.pageHelp.title\\\"></h1> <button-standard class=\\\"ml-auto\\\" t=\\\"[btnText]pages.pageHelp.addPageHelp\\\" click.delegate=\\\"addPageHelp()\\\" icon-name=\\\"System/Tabler/Plus\\\" btn-color=\\\"secondary\\\"></button-standard> </div> <div class=\\\"row row-cards row-deck\\\"> <div class=\\\"col-12\\\"> <custom-table columns.to-view=\\\"columns\\\" extendable=\\\"true\\\" table-id=\\\"page-help\\\" current-sort-column.two-way=\\\"sortColumn\\\" get-shown-entries-data.call=\\\"fetchTableData(limit, offset)\\\" refresh-version.to-view=\\\"tableRefreshVersion\\\"></custom-table> </div> </div> </template>\";\n// Exports\nexport default code;","// Module\nvar code = \"<template> <div if.to-view=\\\"isFrp\\\" class=\\\"text-center mb-6\\\"> <img class=\\\"login__logo-img\\\" src.to-view=\\\"logo\\\"> </div> <div class=\\\"col-login mx-auto\\\"> <alert-message if.to-view=\\\"errorTk\\\" message.to-view=\\\"{ tk: errorTk }\\\" message-style=\\\"alert-danger\\\"></alert-message> <alert-message if.to-view=\\\"passwordErrorMessage\\\" message.to-view=\\\"passwordErrorMessage\\\" message-style=\\\"alert-danger\\\"></alert-message> <form show.to-view=\\\"showSuccess && code\\\" class=\\\"card\\\"> <div class=\\\"card-body p-6 alert-success\\\" t=\\\"pages.passwordReset.successText\\\"> </div> <div class=\\\"card-footer\\\"> <button type=\\\"submit\\\" class=\\\"btn btn-block btn-secondary\\\" t=\\\"pages.passwordReset.toLogin\\\" click.delegate=\\\"handleBackToLoginClick()\\\"></button> </div> </form> <form show.to-view=\\\"!showSuccess && code\\\" class=\\\"card col-login\\\" autocomplete=\\\"off\\\"> <div class=\\\"card-body p-6\\\"> <h1 class=\\\"password-reset-page__title\\\" t=\\\"${ isNew ? 'pages.passwordReset.setPassword' : 'pages.passwordReset.title' }\\\"></h1> <div class=\\\"form-group\\\"> <label class=\\\"form-label\\\" for=\\\"email\\\" t=\\\"globals.email\\\"></label> <input type=\\\"email\\\" id=\\\"email\\\" value.two-way=\\\"email\\\" input.trigger=\\\"resetErrors()\\\" class=\\\"form-control\\\" autocomplete=\\\"email\\\"> </div> <password-strength-indicator class=\\\"password-reset-page__strength-indicator\\\" password.to-view=\\\"password1\\\" strength.from-view=\\\"passwordStrength\\\"></password-strength-indicator> <div class=\\\"form-group\\\"> <label class=\\\"form-label\\\" for=\\\"new-password\\\" t=\\\"pages.passwordReset.password\\\"></label> <div class=\\\"input-group\\\"> <input class=\\\"form-control\\\" id=\\\"new-password\\\" value.two-way=\\\"password1\\\" input.trigger=\\\"resetErrors()\\\" type.to-view=\\\"passwordInputType\\\" autocomplete=\\\"new-password\\\"> <div class=\\\"input-group-append\\\"> <button-standard click.trigger=\\\"togglePasswordVisibility()\\\" icon-name.to-view=\\\"passwordInputType === 'password' ? 'System/Tabler/Eye' : 'System/Tabler/EyeOff'\\\" btn-color=\\\"secondary\\\"></button-standard> </div> </div> </div> <div class=\\\"form-group\\\"> <label class=\\\"form-label\\\" for=\\\"new-password-retype\\\" t=\\\"pages.passwordReset.retype\\\"></label> <input type=\\\"password\\\" id=\\\"new-password-retype\\\" value.two-way=\\\"password2\\\" input.trigger=\\\"resetErrors()\\\" class=\\\"form-control\\\" autocomplete=\\\"new-password-retype\\\"> </div> <div class=\\\"form-footer\\\"> <button class=\\\"btn btn-primary btn-block\\\" disabled.to-view=\\\"!canSave\\\" t=\\\"${ isNew ? 'pages.passwordReset.setPassword' : 'pages.passwordReset.reset' }\\\" click.delegate=\\\"resetPassword()\\\"></button> </div> </div> </form> <div if.to-view=\\\"externalLoginConfigurations.length\\\"> <div class=\\\"text-center text-muted\\\" t=\\\"pages.passwordReset.externalProviders\\\"></div> <div class=\\\"password-reset-page__external-provider\\\"> <external-login external-login-config.to-view=\\\"externalLoginConfigurations\\\"></external-login> </div> </div> </div> </template> \";\n// Exports\nexport default code;","// Module\nvar code = \"<template> <div class=\\\"page-header\\\"> <h1 class=\\\"page-title\\\" t=\\\"pages.presentation-overview.title\\\"></h1> </div> <div class=\\\"presentation-overview-page__table-controls\\\"> <div class=\\\"form-group\\\"> <label class=\\\"custom-control custom-checkbox custom-control-inline\\\"> <input class=\\\"custom-control-input\\\" type=\\\"checkbox\\\" checked.two-way=\\\"showArchived\\\"> <span class=\\\"custom-control-label\\\" t=\\\"globals.showArchived\\\"></span> </label> </div> <div style=\\\"flex:1\\\"></div> <button-standard if.to-view=\\\"selectedPresentations.length > 0\\\" class=\\\"ml-auto d-inline-block presentation-overview-page__archive-btn\\\" t=\\\"[btnText]${archiveTextTK}\\\" disabled.to-view=\\\"archivedToggleDisabled\\\" click.delegate=\\\"toggleArchived()\\\" icon-name=\\\"System/Tabler/Folder\\\" btn-color=\\\"secondary\\\"></button-standard> <label if.to-view=\\\"isProvider\\\" class=\\\"mb-2 d-inline-block presentation-overview-page__action-btn\\\"> <input type=\\\"file\\\" accept=\\\".lpfp\\\" style=\\\"display:none\\\" files.two-way=\\\"selectedImportFiles\\\" change.delegate=\\\"importPresentation()\\\"> <div class=\\\"btn btn-secondary presentation-overview-page__import-btn-content\\\"> <safe-icon class=\\\"presentation-overview-page__import-icon\\\" icon-name=\\\"System/Tabler/Upload\\\"></safe-icon> <span t=\\\"pages.presentation-overview.importPresentation\\\"></span> </div> </label> <button-standard class=\\\"ml-auto d-inline-block\\\" t=\\\"[btnText]pages.presentation-overview.addPresentation\\\" click.delegate=\\\"addPresentation()\\\" icon-name=\\\"System/Tabler/Plus-Circle\\\" btn-color=\\\"primary\\\"></button-standard> </div> <div class=\\\"row row-cards row-deck\\\"> <div class=\\\"col-12\\\"> <custom-table columns.to-view=\\\"tableColumns\\\" current-sort-column.two-way=\\\"sortColumn\\\" selectable.to-view=\\\"true\\\" table-id=\\\"presentation-overview-page\\\" selected.two-way=\\\"selectedPresentations\\\" refresh-version.to-view=\\\"tableRefreshVersion\\\" get-shown-entries-data.call=\\\"fetchTableData(limit, offset)\\\"></custom-table> </div> </div> <template if.to-view=\\\"!isProvider\\\"> <span t=\\\"pages.presentation-overview.sharedPresentations\\\"></span> <div class=\\\"row row-cards row-deck\\\"> <div class=\\\"col-12\\\"> <custom-table columns.to-view=\\\"sharedTableColumns\\\" current-sort-column.two-way=\\\"sharedSortColumn\\\" table-id=\\\"presentation-overview-page-shared\\\" refresh-version.to-view=\\\"sharedTableRefreshVersion\\\" get-shown-entries-data.call=\\\"fetchSharedTableData(limit, offset)\\\"></custom-table> </div> </div> </template> </template>\";\n// Exports\nexport default code;","// Module\nvar code = \"<template class=\\\"presentation-print-page\\\"> <div repeat.for=\\\"slide of slides\\\" if.to-view=\\\"$index <= staggeredRenderIndex\\\" class=\\\"presentation-print-page__slide-container\\\" style.to-view=\\\"{ width: pageWidth, height: pageHeight }\\\"> <content-editor-presentation-slide class=\\\"presentation-print-page__slide\\\" context-identifier.to-view=\\\"contextIdentifier\\\" slide-id.to-view=\\\"slide.id\\\"></content-editor-presentation-slide> </div> </template> \";\n// Exports\nexport default code;","// Module\nvar code = \"<template> <presentation-viewer class=\\\"presentation-slides-page__slides\\\" context-identifier.to-view=\\\"contextIdentifier\\\" presentation-id.to-view=\\\"presentationId\\\"></presentation-viewer> </template> \";\n// Exports\nexport default code;","// Module\nvar code = \"<template> <button-standard click.trigger=\\\"back()\\\" class=\\\"mb-2\\\" t=\\\"[btnText]globals.back\\\" icon-name=\\\"System/Tabler/Chevron-Left\\\" btn-size=\\\"btn-sm\\\" btn-color=\\\"secondary\\\"></button-standard> <div class=\\\"bg-white border\\\"> <testexam-result-overview result.to-view=\\\"result\\\"></testexam-result-overview> <div class=\\\"lp-site-content-small\\\"> <button class=\\\"btn btn-outline-primary w-100 mt-5 text-uppercase\\\" click.delegate=\\\"showCorrectQuestions = !showCorrectQuestions\\\" t=\\\"globals.${showCorrectQuestions ? 'hideCorrectQuestions' : 'showCorrectQuestions'}\\\"> </button> <exam-result-legend></exam-result-legend> <template repeat.for=\\\"questionsGroup of questionsGrouped\\\"> <div if.to-view=\\\"questionsGroup.questions.length\\\" class=\\\"border-top border-bottom lp-site-content-small\\\"> <h1 class=\\\"badge badge-primary my-2\\\">${questionsGroup.courseTitle}</h1> </div> <quiz-result-question-block repeat.for=\\\"question of questionsGroup.questions\\\" question.to-view=\\\"question\\\" question-index.to-view=\\\"$index\\\" show.bind=\\\"!question.isCorrect || showCorrectQuestions\\\"> </quiz-result-question-block> </template> </div> </div> </template> \";\n// Exports\nexport default code;","// Module\nvar code = \"<template> <div class=\\\"row\\\"> <div class=\\\"col-9\\\"> <button-standard click.trigger=\\\"navigateToBack()\\\" t=\\\"[btnText]globals.back\\\" icon-name=\\\"System/Tabler/Chevron-Left\\\" btn-size=\\\"btn-sm\\\" btn-color=\\\"secondary\\\"></button-standard> <div class=\\\"page-header\\\"> <h1 class=\\\"page-title\\\">${quizTemplate.name}</h1> <div class=\\\"ml-auto d-flex\\\"> <button-standard if.to-view=\\\"releaseable\\\" class=\\\"mb-2 d-inline-block mr-2\\\" click.delegate=\\\"releaseQuizTemplate()\\\" disabled.to-view=\\\"!!indicatorService.waitingClients\\\" t=\\\"[btnText]pages.quiz-template.${ quizTemplate.released || quizTemplate.shared ? 'unrelease' : 'release' }\\\" btn-color=\\\"primary\\\"></button-standard> <button-standard class=\\\"mb-2 d-inline-block mr-2\\\" click.delegate=\\\"editQuizTemplate()\\\" disabled.to-view=\\\"!!indicatorService.waitingClients\\\" t=\\\"[btnText]globals.edit\\\" icon-name=\\\"System/Tabler/Edit\\\" btn-color=\\\"secondary\\\"></button-standard> <button-standard class=\\\"mb-2 d-inline-block\\\" click.delegate=\\\"deleteQuizTemplate()\\\" disabled.to-view=\\\"!!indicatorService.waitingClients\\\" t=\\\"[btnText]globals.delete\\\" icon-name=\\\"System/Tabler/Trash\\\" btn-color=\\\"danger\\\"></button-standard> </div> </div> <question-list-widget if.to-view=\\\"course\\\" selected-questions-ids.to-view=\\\"selectedQuestionsIds\\\" list-mode=\\\"quiz\\\" course-id.to-view=\\\"course.id\\\" learn-chapters-enabled.to-view=\\\"course.learnChaptersInQuizEnabled\\\" show-edit.to-view=\\\"isProvider\\\" add-question.trigger=\\\"addQuestionToQuiz($event)\\\"></question-list-widget> </div> <div class=\\\"col-3\\\"> <selected-questions-overview selected-questions.two-way=\\\"selectedQuestions\\\" disabled.to-view=\\\"!!indicatorService.waitingClients\\\" quiz-template-move-question.trigger=\\\"moveQuestionInQuiz($event)\\\" quiz-template-remove-question.trigger=\\\"removeQuestionFromQuiz($event)\\\"></selected-questions-overview> </div> </div> </template>\";\n// Exports\nexport default code;","// Module\nvar code = \"<template> <div class=\\\"page-header\\\"> <h1 class=\\\"page-title\\\" t=\\\"pages.quiz-templates.pageTitle\\\"></h1> <div class=\\\"ml-auto\\\"> <button-standard if.to-view=\\\"canStartQuickQuiz\\\" class=\\\"mb-2 d-inline-block\\\" click.delegate=\\\"handleQuickQuizPlay()\\\" t=\\\"[btnText]pages.quiz-templates.createQuickQuiz\\\" icon-name=\\\"System/Tabler/Play\\\" btn-color=\\\"secondary\\\"></button-standard> <button-standard class=\\\"mb-2 d-inline-block\\\" click.delegate=\\\"createQuizTemplate()\\\" t=\\\"[btnText]pages.quiz-templates.createQuiz\\\" icon-name=\\\"System/Tabler/Plus\\\" btn-color=\\\"primary\\\"></button-standard> </div> </div> <div class=\\\"row\\\"> <div class=\\\"col-md-6\\\"> <div class=\\\"form-group\\\"> <label class=\\\"form-label\\\" t=\\\"globals.courses\\\"></label> <multi-select-combo-box total-items.to-view=\\\"availableCourses\\\" label-property-name=\\\"title\\\" selected-items.to-view=\\\"selectedCourses\\\" selected-items-changed.trigger=\\\"updateQuizTemplates()\\\"></multi-select-combo-box> </div> </div> <div class=\\\"col-md-6\\\"> <div class=\\\"form-group\\\"> <label class=\\\"form-label\\\" t=\\\"pages.quiz-templates.quizName\\\"></label> <search-input t=\\\"[placeholder]pages.quiz-templates.searchPlaceholder\\\" search-string.from-view=\\\"searchString\\\" search-active.from-view=\\\"searchActive\\\"></search-input> </div> </div> </div> <div class=\\\"row\\\"> <section class=\\\"quiz-templates-page__section-grid mt-4 col-12\\\"> <quiz-template-card repeat.for=\\\"quizTemplate of quizTemplates\\\" disabled.to-view=\\\"!!indicatorService.waitingClients\\\" release-clicked.trigger=\\\"handleQuizTemplateReleaseClick(quizTemplate)\\\" edit-clicked.trigger=\\\"handleQuizTemplateEdit(quizTemplate)\\\" play-clicked.trigger=\\\"handleQuizTemplatePlay(quizTemplate)\\\" editable.to-view=\\\"isQuizTemplateEditable(quizTemplate)\\\" playable.to-view=\\\"isQuizTemplatePlayable(quizTemplate)\\\" releaseable.to-view=\\\"isQuizTemplateReleasable(quizTemplate)\\\" quiz-template.to-view=\\\"quizTemplate\\\"></quiz-template-card> </section> </div> </template>\";\n// Exports\nexport default code;","export class MediaSharePage {\n  private tableRefreshVersion: number = 0;\n}\n","import { PLATFORM } from 'aurelia-pal';\nimport { FrameworkConfiguration } from 'aurelia-framework';\n\nexport function configure(config: FrameworkConfiguration): void {\n  config.globalResources(PLATFORM.moduleName('resources/pages/messages-page/messages-page'));\n}\n","import { autoinject, computedFrom, observable } from 'aurelia-framework';\nimport { Router } from 'aurelia-router';\nimport { SiteThemer } from 'common/aurelia-classes/SiteThemer';\nimport { IChatInputAction } from 'common/aurelia-components/chat-input/chat-input';\nimport { IListStandardItem, ListItemMaxWidth } from 'common/aurelia-components/list-standard/list-standard';\nimport { IconSize } from 'common/aurelia-components/x-icon/x-icon';\nimport { ChatApiService } from 'common/aurelia-services/ChatApiService';\nimport { ChatCacheService } from 'common/aurelia-services/ChatCacheService';\nimport { ChatHelperService } from 'common/aurelia-services/ChatHelperService';\nimport { CourseIntersectionCacheService } from 'common/aurelia-services/CourseIntersectionCacheService';\nimport { NotificationWebsocketService } from 'common/aurelia-services/NotificationWebsocketService';\nimport { StoreService } from 'common/aurelia-services/StoreService';\nimport { FilterOperationType } from 'common/dto/filter/filter.enum';\nimport { IFilterDto } from 'common/dto/filter/filter.interface';\nimport { IChatRoomIdWithUsersDto, IUnreadChatRoomDto } from 'common/dto/user/chat/chat.interface';\nimport { ICourseIntersection } from 'common/dto/user/user.management.interface';\nimport { ChatRoomType } from 'common/enum/chat.room.enum';\nimport { NotificationType } from 'common/enum/notification.type.enum';\nimport { AnyNotificationPayload } from 'common/notification/notification.payload';\nimport { readConversationWithChatRoomId } from 'common/store/actions/chat.actions';\nimport { assertNotNullOrUndefined } from 'common/utils/assert';\nimport { FunctionUtils } from 'common/utils/FunctionUtils';\nimport Moment from 'moment';\nimport { CourseCacheService } from '../../../services/CourseCacheService';\nimport { CustomDialogService } from '../../../services/CustomDialogService';\nimport { IState } from '../../../store/state';\nimport { FilterUtils } from '../../../utils/FilterUtils';\nimport { VirtualFilterField } from '../../elements/custom-table/custom-table-helpers';\nimport { ENavigationTab } from '../student-invitations-page/student-invitations-page';\n\n@autoinject()\nexport class MessagesPage {\n  private chatRoomId: number | null = null;\n  private courseIntersectionContext: string = '_courseIntersection';\n\n  private chatRooms: Array<IChatRoomIdWithUsersDto<string>> = [];\n  private unreadChatRooms: Array<IUnreadChatRoomDto> = [];\n  private chatRoom: IChatRoomIdWithUsersDto<string> | null = null;\n  private filter: IFilterDto<IChatRoomIdWithUsersDto<string>> = {\n    limit: 10,\n    offset: 0\n  };\n  private chatRoomsTotal: number = 0;\n  private includeDistinarChats: boolean = false;\n  private debouncedUpdateChatRooms = FunctionUtils.debounce(() => { this.updateChatRooms(); }, 250);\n  @observable() private searchString = '';\n  @observable() private searchActive = false;\n\n  private currentUserId: number | null = null;\n  private inputActions: Array<IChatInputAction> = [{\n    callback: () => this.handleLinkPage(),\n    icon: 'System/Tabler/Book',\n    ariaLabelTk: 'linkContent'\n  }];\n  private rawChatInputValue = '';\n  private courseIntersection: Array<ICourseIntersection> = [];\n  private themer = new SiteThemer();\n\n  constructor(\n    private storeService: StoreService<IState>,\n    private router: Router,\n    private chatCacheService: ChatCacheService,\n    private notificationWebsocketService: NotificationWebsocketService,\n    private chatHelperService: ChatHelperService,\n    private courseCacheService: CourseCacheService,\n    private customDialogService: CustomDialogService,\n    private courseIntersectionCacheService: CourseIntersectionCacheService,\n    private chatApiService: ChatApiService\n  ) { }\n\n  protected activate(params: IParams): void {\n    if(params.chatRoomId != null) {\n      this.chatRoomId = parseInt(params.chatRoomId, 10);\n      this.courseIntersection = [];\n      this.chatCacheService.ensureChatRoomIdWithUsersInCache(this.chatRoomId, undefined, true);\n    }\n\n    this.chatCacheService.updateAllChatRoomsInCache();\n  }\n\n  protected attached(): void {\n    this.storeService.subscribeToDetailedChanges(this, [\n      state => state.user.id,\n      state => this.chatRoomId == null\n        ? null\n        : state.cache.chatRoomIdWithUsersByChatRoomId[this.chatRoomId] ?? null,\n    ], (_, currentUserId, chatRoom) => {\n      this.currentUserId = currentUserId;\n      this.chatRoom = chatRoom;\n      if(this.chatRoom != null) {\n        this.setChatRead(this.chatRoom.id);\n        this.loadCourseIntersection(this.chatRoom);\n      }\n    });\n\n    this.storeService.subscribeToDetailedChanges(this, [\n      state => state.chat.unreadChatRooms,\n    ], (_, unreadChatRooms) => {\n      this.unreadChatRooms = unreadChatRooms;\n    });\n\n    this.courseCacheService.ensureAllTenantCoursesInCache();\n    this.notificationWebsocketService.addNewNotificationListener(this, this.handleNotificationReceived.bind(this));\n    this.updateChatRooms();\n  }\n\n  protected detached(): void {\n    this.storeService.disposeAllSubscriptions(this);\n    this.notificationWebsocketService.removeListenersByScope(this);\n  }\n\n  @computedFrom('chatRoom')\n  private get chatRoomTitle(): string | null {\n    if(this.chatRoom == null) return null;\n\n    if(this.chatRoom.event != null) return this.chatRoom.event.title;\n    const participant = this.chatHelperService.getOneToOneParticipant(this.chatRoom);\n    const fullname = this.chatHelperService.getParticipantFullname(participant);\n\n    return fullname ?? null;\n  }\n\n  @computedFrom('chatRoom')\n  private get chatRoomSubtitle(): string | null {\n    if(this.chatRoom == null) return null;\n\n    if(this.chatRoom.event != null) return Moment(this.chatRoom.event.plannedDateFrom).format('lll');\n    const participant = this.chatHelperService.getOneToOneParticipant(this.chatRoom);\n\n    return participant?.nickname ?? null;\n  }\n\n  @computedFrom('chatRooms', 'unreadChatRooms')\n  private get chatList(): Array<IListStandardItem> {\n    return this.chatRooms.map(chat => ({\n      icons: [{\n        name: 'System/Arrow - Right',\n        size: IconSize.normal,\n        type: 'custom',\n      }],\n      click: {\n        callback: () => this.openChat(chat),\n      },\n      text: {\n        primary: this.chatHelperService.getChatRoomPrimaryTextAsManager(chat),\n        secondary: this.chatHelperService.getChatRoomSecondaryTextAsManager(chat) ?? undefined,\n        tertiary: this.chatHelperService.getChatRoomTertiaryTextAsManager(chat) ?? undefined\n      },\n      graphic: {\n        avatarSvgDownloadLink: this.chatHelperService.getChatRoomAvatarSvgDownloadLink(chat),\n        disabled: false,\n        points: null,\n        online: this.chatHelperService.showChatRoomAsOnline(chat),\n      },\n      separatorAfter: true,\n      maxWidth: ListItemMaxWidth.ContentSmall,\n      unread: this.unreadChatRooms.find(unreadChat => chat.id === unreadChat.id) != null,\n    }));\n  }\n\n  @computedFrom('courseIntersection')\n  private get courseIntersectionList(): Array<string> {\n    return this.courseIntersection.map(intersection => {\n      const tenantName = intersection.tenant == null\n        ? ''\n        : `${intersection.tenant.name} · `;\n      const respectiveCourseGroupName =\n        intersection.respectiveCourseGroup == null\n          ? ''\n          : ` · ${intersection.respectiveCourseGroup.name}`;\n      return `${tenantName}${intersection.course.title}${respectiveCourseGroupName}`;\n    });\n  }\n\n  private openChat(chatRoom: IChatRoomIdWithUsersDto<string>): void {\n    this.router.navigateToRoute('messages/id', { chatRoomId: chatRoom.id });\n  }\n\n  private async handleNotificationReceived(data: AnyNotificationPayload): Promise<void> {\n    if(\n      this.chatRoom\n      && data.notificationType === NotificationType.ChatMessage\n      && this.chatRoom.id === data.payload.chatRoomId\n    ) {\n      await this.chatCacheService.ensureLatestConversationInCache(this.chatRoom.id);\n      await this.setChatRead(this.chatRoom.id);\n    }\n  }\n\n  private async handleLinkPage(): Promise<void> {\n    assertNotNullOrUndefined(this.chatRoom, 'chatRoom cannot be null or undefined');\n    const participant = this.chatHelperService.getOneToOneParticipant(this.chatRoom);\n    assertNotNullOrUndefined(participant, 'chat participant cannot be null or undefined');\n\n    const link = await this.customDialogService.showChatLinkSearchDialog(participant.id);\n    if(link == null) return;\n\n    if('page' in link) {\n      this.rawChatInputValue = `${this.rawChatInputValue.trimRight()} ${this.chatHelperService.createPageLinkChatString(link.course, link.page)}`;\n    } else {\n      this.rawChatInputValue = `${this.rawChatInputValue.trimRight()} ${this.chatHelperService.createQuestionLinkChatString(link.course, link.question)}`;\n    }\n  }\n\n  private async setChatRead(chatRoomId: number): Promise<void> {\n    this.storeService.dispatch(readConversationWithChatRoomId, chatRoomId);\n  }\n\n  private async loadCourseIntersection(chatRoom: IChatRoomIdWithUsersDto<string>): Promise<void> {\n    this.storeService.disposeSubscriptionsForContext(this, this.courseIntersectionContext);\n\n    if(chatRoom.type === ChatRoomType.OneToOne) {\n      const otherUser = this.chatHelperService.getOneToOneParticipant(chatRoom);\n      if(otherUser != null) {\n        this.storeService.subscribeToDetailedChanges(this, [\n          state => state.cache.courseIntersectionByUserId[otherUser.id],\n        ], (_, courseIntersection) => {\n          this.courseIntersection = courseIntersection ?? [];\n        }, false, this.courseIntersectionContext);\n        this.courseIntersectionCacheService.ensureCourseIntersectionWithUserInCache(otherUser.id);\n      }\n    }\n  }\n\n  private getThemeColorForIntersection(intersection: ICourseIntersection): string | null {\n    if(intersection.tenant == null) return null;\n    return this.themer.getThemeColor(intersection.tenant.primaryColor);\n  }\n\n  private getThemeColorContrastForIntersection(intersection: ICourseIntersection): string | null {\n    if(intersection.tenant == null) return null;\n    return this.themer.getThemeColorContrast(intersection.tenant.primaryColor);\n  }\n\n  private onGoToStudent(): void {\n    assertNotNullOrUndefined(this.chatRoom, 'cannot go to student without selected chatRoom');\n    const participant = this.chatHelperService.getOneToOneParticipant(this.chatRoom);\n    assertNotNullOrUndefined(participant, 'cannot go to student without participant');\n    this.router.navigateToRoute('student-invitations/id/tab', { id: participant.id, tab: ENavigationTab.OVERVIEW });\n  }\n\n  private async updateChatRooms(): Promise<void> {\n    const rooms = await this.chatApiService.getChatRoomsPaginated(this.filter);\n    this.chatRoomsTotal = rooms.count;\n    this.chatRooms = rooms.rows;\n  }\n\n  private handleIncludeDistinarChatsChanged(): void {\n    this.includeDistinarChats = !this.includeDistinarChats;\n    if(this.includeDistinarChats) {\n      this.filter = FilterUtils.removeDuplicateFilterFields(\n        this.filter,\n        {\n          field: 'type',\n          type: FilterOperationType.Eq,\n          value: ChatRoomType.OneToOne\n        }\n      );\n    } else {\n      this.filter = FilterUtils.removeFilterForField(this.filter, 'type');\n    }\n    this.filter.offset = 0;\n    this.updateChatRooms();\n  }\n\n  private newOffsetCallback = (offset: number): void => {\n    this.filter.offset = offset;\n    this.updateChatRooms();\n  };\n\n  private searchActiveChanged(): void {\n    if(!this.searchActive) {\n      this.updateChatRooms();\n    }\n  }\n\n  private searchStringChanged(): void {\n    const cleanSearchString: string = this.searchString.trim().toLocaleLowerCase();\n    if(cleanSearchString.length) {\n      this.filter = FilterUtils.removeDuplicateFilterFields(\n        this.filter,\n        {\n          field: 'search' as VirtualFilterField,\n          type: FilterOperationType.Eq,\n          value: cleanSearchString\n        }\n      );\n    } else {\n      this.filter = FilterUtils.removeFilterForField(this.filter, 'search' as VirtualFilterField);\n    }\n    this.filter.offset = 0;\n    this.debouncedUpdateChatRooms();\n  }\n\n  private async handleSendMultiMessage(): Promise<void> {\n    const result = await this.customDialogService.showSendMessageToStudentsDialog({});\n    if(result) {\n      this.updateChatRooms();\n      if(this.chatRoomId && result.chatRoomIds.includes(this.chatRoomId)) {\n        await this.chatCacheService.ensureLatestConversationInCache(this.chatRoomId);\n      }\n    }\n  }\n}\n\ninterface IParams {\n  chatRoomId?: string;\n}\n","import { Router } from 'aurelia-router';\nimport { autoinject } from 'aurelia-framework';\nimport { Language } from 'common/dto/other/language.enum';\nimport { PageHelpTextMode } from 'resources/elements/page-help/page-help-text/page-help-text';\nimport { routes } from '../../../router/routes';\n\n@autoinject()\nexport class PageHelpEditPage {\n  private static HomePath = '/';\n\n  private path = PageHelpEditPage.HomePath;\n  private language = Language.De;\n  private mode = PageHelpTextMode.Render;\n\n  private paths = routes.map(({ name, route }) => {\n    const [path] = route;\n    if(name === 'home') return PageHelpEditPage.HomePath;\n    return `/${path}`;\n  }).sort((a, b) => a.localeCompare(b));\n  private languages = Object.values(Language);\n\n  private Mode = PageHelpTextMode;\n\n  constructor(\n    private router: Router,\n  ) { }\n\n  protected async activate(params: Record<string, string>): Promise<void> {\n    if(params.path != null) this.path = params.path;\n    if(params.language != null) this.language = params.language as Language;\n  }\n  \n  private refreshRoute(): void {\n    this.router.navigateToRoute('page-help-edit', { path: this.path, language: this.language });\n  }\n}","import { autoinject } from 'aurelia-framework';\nimport { Router } from 'aurelia-router';\nimport { ColumnSortDirection, FilterOperationType } from 'common/dto/filter/filter.enum';\nimport { IFilterDto } from 'common/dto/filter/filter.interface';\nimport { IPageHelpTextWithIdDto } from 'common/dto/help/help.interface';\nimport { HelpApiService } from 'services/api/HelpApiService';\nimport { ActionsCell } from '../../elements/custom-table/actions-cell/actions-cell';\nimport { CustomTableCell } from '../../elements/custom-table/custom-table-cell/custom-table-cell';\nimport { createPrepareServersideSortingFunction, prepareServersideFiltering, VirtualFilterField } from '../../elements/custom-table/custom-table-helpers';\nimport { IColumnDefinition, SortNulls } from '../../elements/custom-table/custom-table-interfaces';\n\n@autoinject()\nexport class PageHelpPage {\n  private filter: IFilterDto<IPageHelpTextWithIdDto> = {\n    offset: 0,\n    limit: 10,\n    order: [\n      {\n        field: 'path',\n        dir: ColumnSortDirection.ASC,\n      }\n    ],\n  };\n\n  private columns: Array<IColumnDefinition<CustomTableCell<IPageHelpTextWithIdDto> | ActionsCell<IPageHelpTextWithIdDto>>> = [\n    {\n      id: 'path',\n      nameTk: 'pages.pageHelp.path',\n      cellViewModel: CustomTableCell,\n      cellOptions: {\n        property: 'path',\n      },\n      prepareSorting: createPrepareServersideSortingFunction(this.filter, 'path'),\n      filter: {\n        placeholderTk: 'globals.filter',\n        prepare: v => prepareServersideFiltering(this.filter, 'path', v),\n      }\n    },\n    {\n      id: 'language',\n      nameTk: 'pages.pageHelp.language',\n      cellViewModel: CustomTableCell,\n      sortNulls: SortNulls.Last,\n      cellOptions: {\n        property: 'language',\n      },\n      prepareSorting: createPrepareServersideSortingFunction(this.filter, 'language' as VirtualFilterField),\n      filter: {\n        placeholderTk: 'globals.filter',\n        prepare: v => prepareServersideFiltering(this.filter, 'language' as VirtualFilterField, v, FilterOperationType.Like),\n      },\n    },\n    {\n      id: 'actions',\n      nameTk: '',\n      cellViewModel: ActionsCell,\n      shrink: true,\n      cellOptions: {\n        entries: [\n          {\n            labelTk: 'globals.edit',\n            icon: 'fe-edit',\n            clickCallback: e => { this.router.navigateToRoute('page-help-edit', { path: e.path, language: e.language }); },\n          },\n          {\n            labelTk: 'globals.delete',\n            icon: 'fe-trash-2',\n            additionalClasses: 'text-danger',\n            clickCallback: async e => {\n              await this.helpApi.deletePageHelpText(e.path, e.language);\n              this.tableRefreshVersion++;\n            },\n          },\n        ],\n      }\n    },\n  ];\n\n  private tableRefreshVersion = 1;\n  private sortColumn = this.columns[0];\n\n  constructor(\n    private helpApi: HelpApiService,\n    private router: Router,\n  ) { }\n\n  private addPageHelp(): void {\n    this.router.navigateToRoute('page-help-edit', { path: '/' });\n  }\n\n  private async fetchTableData(limit: number, offset: number): Promise<{ total: number; data: Array<IPageHelpTextWithIdDto> }> {\n    this.filter.limit = limit;\n    this.filter.offset = offset;\n    const results = await this.helpApi.getPageHelp(this.filter);\n    return { data: results.rows, total: results.count };\n  }\n}\n","import { autoinject, computedFrom } from 'aurelia-framework';\nimport { I18N } from 'aurelia-i18n';\nimport { Router } from 'aurelia-router';\nimport { IExternalLoginConfiguration } from 'common/aurelia-components/external-login/external-login';\nimport { LoginProvider } from 'common/enum/auth.enum';\nimport { ErrorType } from 'common/enum/errortype.enum';\nimport { IText } from 'common/interfaces/translations.interface';\nimport { AppUtils as CommonAppUtils } from 'common/utils/AppUtils';\nimport { LogUtils } from 'common/utils/LogUtils';\nimport { ValidationUtils as CommonValidationUtils } from 'common/utils/ValidationUtils';\nimport { AuthApiService } from '../../../services/api/AuthApiService';\nimport { AppUtils } from '../../../utils/AppUtils';\nimport { IErrorTk, ValidationUtils } from '../../../utils/ValidationUtils';\n\n@autoinject()\nexport class PasswordResetPage {\n  private code = '';\n  private email = '';\n  private emailProvided = false;\n  private isNew = false;\n\n  private password1 = '';\n  private password2 = '';\n  private passwordStrength = 0;\n  private passwordError: IErrorTk | null = null;\n  private passwordInputType: 'password' | 'text' = 'password';\n\n  private errorTk: string | null = null;\n  private showSuccess = false;\n  private externalLoginConfigurations: IExternalLoginConfiguration[] = [];\n\n  private isFrp = CommonAppUtils.isFrp;\n  private logo = AppUtils.getLogo(true);\n\n  constructor(\n    private authApiService: AuthApiService,\n    private router: Router,\n    private i18n: I18N,\n  ) { }\n\n  protected activate(params: Record<string, string>): void {\n    this.code = params.code;\n    this.email = params.email;\n    this.emailProvided = !!params.email;\n    this.isNew = parseInt(params.new, 10) === 1;\n\n    if(!this.code) {\n      this.errorTk = 'pages.passwordReset.errors.invalidCode';\n      return;\n    }\n    this.updateExternalLoginProviders();\n  }\n\n  protected attached(): void {\n    if(this.isNew) {\n      document.title = this.i18n.tr('pages.passwordReset.setPassword');\n    }\n  }\n\n  private async resetPassword(): Promise<void> {\n    this.resetErrors();\n    this.errorTk = CommonValidationUtils.checkMandatoryEmail(this.email);\n    this.passwordError = ValidationUtils.checkPasswords(this.password1, this.password2, this.passwordStrength);\n    if(this.errorTk || this.passwordError) return;\n    try {\n      await this.authApiService.resetPassword(this.code, this.email.trim(), this.password1);\n      this.showSuccess = true;\n    } catch(e) {\n      if(e.message === ErrorType.PASSWORD_RESET_CODE_INVALID) {\n        this.errorTk = 'pages.passwordReset.errors.invalidCode';\n      } else {\n        LogUtils.exception(e);\n      }\n    }\n  }\n\n  private async linkExternalProvider(provider: LoginProvider): Promise<void> {\n    this.resetErrors();\n    this.errorTk = CommonValidationUtils.checkMandatoryEmail(this.email);\n    if(this.errorTk) return;\n    try {\n      await this.authApiService.oneTimeLogin(this.email.trim(), this.code);\n      const url = await this.authApiService.getAuthUrl(provider, true);\n      window.location.href = url;\n    } catch(e) {\n      if(e.message === ErrorType.PASSWORD_RESET_CODE_INVALID) {\n        this.errorTk = 'pages.passwordReset.errors.invalidCode';\n      } else {\n        LogUtils.exception(e);\n        this.errorTk = 'pages.passwordReset.errors.generalLinkError';\n      }\n    }\n  }\n\n  private handleBackToLoginClick(): void {\n    this.router.navigate('login');\n  }\n\n  private resetErrors(): void {\n    this.errorTk = null;\n    this.passwordError = null;\n  }\n\n  private async updateExternalLoginProviders(): Promise<void> {\n    const availableExternalLoginProviders = await this.authApiService.getExternalProviders();\n    this.externalLoginConfigurations = availableExternalLoginProviders.map(provider => ({\n      color: 'secondary1',\n      provider,\n      callback: () => this.linkExternalProvider(provider)\n    }));\n  }\n\n  @computedFrom('passwordError')\n  private get passwordErrorMessage(): IText | null {\n    if(!this.passwordError) return null;\n    return {\n      tk: this.passwordError.errorTk,\n      tkParams: this.passwordError.errorTkParams,\n    };\n  }\n\n  @computedFrom('password1', 'password2')\n  private get canSave(): boolean {\n    return !!this.password1.length && this.password2 === this.password1;\n  }\n\n  private togglePasswordVisibility(): void {\n    this.passwordInputType = this.passwordInputType === 'password' ? 'text' : 'password';\n  }\n}\n","import { autoinject, computedFrom, observable } from 'aurelia-framework';\nimport { Router } from 'aurelia-router';\nimport { StoreService } from 'common/aurelia-services/StoreService';\nimport { IPresentationEditResponseDto, IPresentationSelectInfoDto } from 'common/dto/course/management/presentation.management.interface';\nimport { ColumnSortDirection, FilterOperationType } from 'common/dto/filter/filter.enum';\nimport { IFilterDto } from 'common/dto/filter/filter.interface';\nimport { RoleName } from 'common/enum/role.enums';\nimport { LogUtils } from 'common/utils/LogUtils';\nimport { CustomDialogService } from '../../../services/CustomDialogService';\nimport { DateService } from '../../../services/DateService';\nimport { ExportImportService } from '../../../services/ExportImportService';\nimport { ToastService } from '../../../services/ToastService';\nimport { CourseExportApiService } from '../../../services/api/CourseExportApiService';\nimport { CourseImportApiService } from '../../../services/api/CourseImportApiService';\nimport { CourseManagementApiService } from '../../../services/api/CourseManagementApiService';\nimport { PresentationManagementApiService } from '../../../services/api/PresentationManagementApiService';\nimport { IState } from '../../../store/state';\nimport { ButtonCell } from '../../elements/custom-table/button-cell/button-cell';\nimport { ICustomTablePaginationData } from '../../elements/custom-table/custom-table';\nimport { CustomTableCell } from '../../elements/custom-table/custom-table-cell/custom-table-cell';\nimport { createPrepareServersideSortingFunction, prepareServersideFiltering } from '../../elements/custom-table/custom-table-helpers';\nimport { IColumnDefinition } from '../../elements/custom-table/custom-table-interfaces';\nimport { TaggedTextTableCell } from '../../elements/custom-table/tagged-text-cell/tagged-text-cell';\nimport { LinkBehavior } from '../../elements/toast-controller/toasts/default-toast/default-toast';\n\ntype TableColumn = IColumnDefinition<CustomTableCell<IPresentationEditResponseDto>\n  | TaggedTextTableCell<IPresentationEditResponseDto>\n  | ButtonCell<IPresentationEditResponseDto>\n>;\n\nconst OperationContext = Symbol();\n\n@autoinject()\nexport class PresentationOverviewPage {\n  private tenantId: number | null = null;\n  private isProvider = false;\n  private tableRefreshVersion = 0;\n  private sharedTableRefreshVersion = 0;\n  private selectedPresentations: Array<IPresentationEditResponseDto> = [];\n  private selectedImportFiles: FileList | null = null;\n  private filter: IFilterDto<IPresentationEditResponseDto> = {\n    offset: 0,\n    limit: 10,\n    order: [{\n      field: 'title',\n      dir: ColumnSortDirection.ASC,\n    }],\n    filter: [],\n  };\n\n  private sharedFilter: IFilterDto<IPresentationSelectInfoDto> = {\n    offset: 0,\n    limit: 10,\n    order: [{\n      field: 'title',\n      dir: ColumnSortDirection.ASC,\n    }],\n    filter: [],\n  };\n\n  @observable() private showArchived = false;\n\n  @computedFrom('isProvider')\n  private get tableColumns(): Array<TableColumn> {\n    return [\n      {\n        id: 'title',\n        nameTk: 'globals.title',\n        cellViewModel: TaggedTextTableCell,\n        cellOptions: {\n          transformer: e => e.title,\n          tagTk: e => e.isArchived ? 'globals.archived' : undefined,\n          callback: e => { this.router.navigateToRoute('content-editor-presentation', { presentationId: e.id }); },\n          clickable: () => true,\n        },\n        prepareSorting: createPrepareServersideSortingFunction(this.filter, 'title'),\n        filter: {\n          placeholderTk: 'globals.filter',\n          prepare: v => prepareServersideFiltering(this.filter, 'title', v, FilterOperationType.Like),\n        },\n      },\n      {\n        id: 'slides',\n        nameTk: 'pages.presentation-overview.slides',\n        cellViewModel: CustomTableCell,\n        cellOptions: {\n          transformer: e => `${e.slideCount}`\n        },\n        shrink: true,\n      },\n      {\n        id: 'editedAt',\n        nameTk: 'pages.presentation-overview.editedAt',\n        cellViewModel: CustomTableCell,\n        prepareSorting: createPrepareServersideSortingFunction(this.filter, 'editedAt'),\n        cellOptions: {\n          transformer: e => this.dateService.formatDate(e.editedAt)\n        },\n      },\n      ...(this.isProvider ? [\n        {\n          id: 'export',\n          nameTk: '',\n          cellViewModel: ButtonCell,\n          shrink: true,\n          cellOptions: {\n            buttonText: () => ({\n              tk: 'globals.export',\n            }),\n            clickCallback: presentation => this.exportPresentation(presentation.id),\n          }\n        } as TableColumn,\n      ] : []),\n      {\n        id: 'duplicate',\n        nameTk: '',\n        cellViewModel: ButtonCell,\n        shrink: true,\n        cellOptions: {\n          buttonText: () => ({\n            tk: 'globals.duplicate',\n          }),\n          clickCallback: presentation => this.duplicatePresentation(presentation.id),\n        }\n      }\n    ];\n  }\n  private sortColumn = this.tableColumns[0];\n\n  private sharedTableColumns: Array<\n    IColumnDefinition<CustomTableCell<IPresentationSelectInfoDto>\n      | TaggedTextTableCell<IPresentationSelectInfoDto>\n      | ButtonCell<IPresentationSelectInfoDto>\n    >> = [\n      {\n        id: 'title',\n        nameTk: 'globals.title',\n        cellViewModel: TaggedTextTableCell,\n        cellOptions: {\n          transformer: e => e.title,\n        },\n        prepareSorting: createPrepareServersideSortingFunction(this.sharedFilter, 'title'),\n        filter: {\n          placeholderTk: 'globals.filter',\n          prepare: v => prepareServersideFiltering(this.sharedFilter, 'title', v, FilterOperationType.Like)\n        },\n      },\n      {\n        id: 'duplicate',\n        nameTk: '',\n        cellViewModel: ButtonCell,\n        shrink: true,\n        cellOptions: {\n          buttonText: () => ({\n            tk: 'globals.duplicate',\n          }),\n          clickCallback: presentation => this.duplicatePresentation(presentation.id),\n        }\n      },\n    ];\n  private sharedSortColumn = this.sharedTableColumns[0];\n\n  constructor(\n    private router: Router,\n    private api: PresentationManagementApiService,\n    private dateService: DateService,\n    private customDialogService: CustomDialogService,\n    private courseManagementApi: CourseManagementApiService,\n    private toastService: ToastService,\n    private storeService: StoreService<IState>,\n    private courseExportApiService: CourseExportApiService,\n    private courseImportApiService: CourseImportApiService,\n    private dialogService: CustomDialogService,\n    private exportImportService: ExportImportService,\n  ) { }\n\n  protected attached(): void {\n    this.storeService.subscribeToDetailedChanges(this, [\n      state => state.currentRole?.tenantId ?? null,\n      state => state.currentRole?.role === RoleName.Provider,\n    ], (_state, tenantId, isProvider) => {\n      this.tenantId = tenantId;\n      this.isProvider = isProvider;\n\n      // refresh tables\n      this.tableRefreshVersion++;\n      this.sharedTableRefreshVersion++;\n    });\n  }\n\n  protected detached(): void {\n    this.storeService.disposeAllSubscriptions(this);\n  }\n\n  @computedFrom('selectedPresentations.length')\n  private get archivedToggleDisabled(): boolean {\n    return this.selectedPresentations.length === 0 || !this.selectedPresentations.every((presentation, _idx, presentations) => presentation.isArchived === presentations[0].isArchived);\n  }\n\n  @computedFrom('selectedPresentations.length')\n  private get archiveTextTK(): string {\n    if(this.selectedPresentations.every(presentation => presentation.isArchived)) {\n      return 'globals.unarchive';\n    }\n    if(this.selectedPresentations.every(presentation => !presentation.isArchived)) {\n      return 'globals.archive';\n    }\n\n    return 'globals.toggleMixedUp';\n  }\n\n  private async toggleArchived(): Promise<void> {\n    for(const presentation of this.selectedPresentations) {\n      if(presentation.isArchived) {\n        await this.api.unarchive(presentation.id);\n      } else {\n        await this.api.archive(presentation.id);\n      }\n    }\n    this.tableRefreshVersion++;\n  }\n\n  private showArchivedChanged(): void {\n    if(this.showArchived) {\n      // don't set `isArchived` to `true` as only the archived presentations\n      // would be fetched\n      this.filter.filter = [];\n    } else {\n      this.filter.filter = [{\n        field: 'isArchived',\n        type: FilterOperationType.Eq,\n        value: false,\n      }];\n    }\n\n    this.tableRefreshVersion++;\n  }\n\n  private async importPresentation(): Promise<void> {\n    const file = this.selectedImportFiles?.[0];\n    if(file == null) return;\n\n    try {\n      const importId = await this.courseImportApiService.uploadCourseContent(file);\n      await this.exportImportService.trackImportOperation(OperationContext, importId, file.name);\n\n      await this.dialogService.showOperationStatusDialog<number>({\n        operationContext: OperationContext,\n        title: { textTk: 'dialogs.operationStatus.presentationImport' },\n      });\n\n      this.exportImportService.untrackOperations(OperationContext);\n\n      this.tableRefreshVersion++;\n    } catch(e) {\n      this.toastService.error('elements.courseImport.importStartError', {}, e);\n      LogUtils.exception(e);\n    }\n\n    this.selectedImportFiles = null;\n  }\n\n  private async addPresentation(): Promise<void> {\n    await this.customDialogService.showAddPresentationDialog();\n  }\n\n  private async fetchTableData(limit: number, offset: number): Promise<ICustomTablePaginationData<IPresentationEditResponseDto>> {\n    this.filter.limit = limit;\n    this.filter.offset = offset;\n\n    const paginated = await this.api.getPresentations(this.filter);\n    return {\n      data: paginated.rows,\n      total: paginated.count,\n    };\n  }\n\n  private async fetchSharedTableData(limit: number, offset: number): Promise<ICustomTablePaginationData<IPresentationSelectInfoDto>> {\n    if(this.tenantId == null) return { data: [], total: 0 };\n\n    this.sharedFilter.limit = limit;\n    this.sharedFilter.offset = offset;\n\n    const paginated = await this.api.getSharedPresentationsForTenant(this.tenantId, this.sharedFilter);\n    return {\n      data: paginated.rows,\n      total: paginated.count,\n    };\n  }\n\n  private async exportPresentation(presentationId: number): Promise<void> {\n    try {\n      const exportId = await this.courseExportApiService.exportContent(presentationId);\n      await this.exportImportService.trackExportOperation(OperationContext, exportId);\n\n      await this.dialogService.showOperationStatusDialog({\n        operationContext: OperationContext,\n        title: { textTk: 'dialogs.operationStatus.presentationExport' },\n      });\n\n      this.exportImportService.untrackOperation(exportId);\n    } catch(e) {\n      this.toastService.error('elements.courseExport.exportError', {}, e);\n      LogUtils.exception(e);\n    }\n  }\n\n  private async duplicatePresentation(presentationId: number): Promise<void> {\n    try {\n      const presentationCopyId = await this.courseManagementApi.copyCourse(presentationId);\n      this.tableRefreshVersion++;\n      this.toastService.success('pages.presentation-overview.copySuccess', {}, {\n        textTk: 'pages.presentation-overview.copyLink',\n        linkHref: this.router.generate('content-editor-presentation', { presentationId: presentationCopyId }),\n        behavior: LinkBehavior.Navigate,\n      });\n    } catch(error) {\n      LogUtils.exception(error);\n      this.toastService.error('pages.presentation-overview.copyError');\n    }\n  }\n}\n","import { FrameworkConfiguration, PLATFORM } from 'aurelia-framework';\n\nexport function configure(config: FrameworkConfiguration): void {\n  config.globalResources(PLATFORM.moduleName('resources/pages/presentation-print-page/presentation-print-page'));\n}\n","import { autoinject, computedFrom } from 'aurelia-framework';\nimport { selectState } from 'common/aurelia-decorators/select-state';\nimport { PresentationApiService } from 'common/aurelia-services/PresentationApiService';\nimport { StoreService } from 'common/aurelia-services/StoreService';\nimport { SubscriptionManager, SubscriptionManagerService } from 'common/aurelia-services/SubscriptionManagerService';\nimport { PRINT_PRESENTATION_CONTEXT } from 'common/constants/content.editor.constants';\nimport { PDF_CONTENT_LOADED_CBNAME } from 'common/constants/print.constants';\nimport { IPresentationContentDto } from 'common/dto/course/content/course.interface';\nimport { IPresentationSlideDto } from 'common/dto/course/content/slide.interface';\nimport * as PresentationContentMutations from 'common/store/mutations/presentation.content.mutations';\nimport * as PresentationContextMutations from 'common/store/mutations/presentation.context.mutations';\nimport { presentationContentSelectorCreator, slideIdSelectorCreator } from 'common/store/selectors/presentation.context.selectors';\nimport { ICommonState } from 'common/store/store.interface';\nimport { ArrayUtils } from 'common/utils/ArrayUtils';\nimport { HtmlUtils } from '../../../utils/HtmlUtils';\n\n@autoinject()\nexport class PresentationPrintPage {\n  public contextIdentifier = PRINT_PRESENTATION_CONTEXT;\n  @selectState(PresentationPrintPage, presentationContentSelectorCreator, ['contextIdentifier']) private content?: IPresentationContentDto | null;\n  @selectState(PresentationPrintPage, slideIdSelectorCreator, ['contextIdentifier']) private slideId?: number | null;\n\n  private element: HTMLElement;\n  private subscriptionManager: SubscriptionManager;\n  private pageStyleTag: HTMLStyleElement | undefined;\n  private printStyleTag: HTMLStyleElement | undefined;\n\n  /**\n   * NOTE: Making sure that the page size and the slide size are the same and fixed sizes is necessary,\n   * because the aspect ratio container and potentially other components use JS to compute their sizes.\n   * When switching to the print view, there is no recomputation of the sizes, so whatever is affected by pure CSS will change,\n   * but computed sizes will not update for the print view.\n   *\n   * This page is currently intended primarily for printing to PDF.\n   * If we ever want to be more flexible and print to paper, we might have to provide our own page configuration overlay\n   * instead of relying on the native solutions ;_;\n   */\n  private pageWidth = '1920px';\n  private pageHeight = '1080px';\n  private pageMargin = '0';\n  private from: number | undefined = undefined;\n  private to: number | undefined = undefined;\n  private staggeredRenderIndex = 0;\n  private loadingInitiated = false;\n\n  constructor(\n    private storeService: StoreService<ICommonState>,\n    private presentationApi: PresentationApiService,\n    subscriptionManagerService: SubscriptionManagerService,\n    element: Element,\n  ) {\n    this.element = element as HTMLElement;\n    this.subscriptionManager = subscriptionManagerService.createSubscriptionManager();\n  }\n\n  protected activate(params: { presentationId: string; from?: string; to?: string }): void {\n    if(params.from) this.from = parseInt(params.from, 10);\n    if(params.to) this.to = parseInt(params.to, 10);\n    this.loadPresentation(Number(params.presentationId));\n  }\n\n  protected attached(): void {\n    this.updatePageStyleTag();\n    this.subscriptionManager.subscribeToWindowEvent('beforeprint', () => this.setPrintStyleTag());\n    this.subscriptionManager.subscribeToWindowEvent('afterprint', () => this.printStyleTag?.remove());\n  }\n\n  protected detached(): void {\n    this.subscriptionManager.disposeSubscriptions();\n    this.pageStyleTag?.remove();\n    this.printStyleTag?.remove();\n  }\n\n  private pageWidthChanged(): void {\n    this.updatePageStyleTag();\n  }\n\n  private pageHeightChanged(): void {\n    this.updatePageStyleTag();\n  }\n\n  private pageMarginChanged(): void {\n    this.updatePageStyleTag();\n  }\n\n  @computedFrom('content', 'from', 'to')\n  private get slides(): IPresentationSlideDto[] {\n    return ArrayUtils.immutableSort(this.content?.slides ?? [], (a, b) => a.order - b.order).slice(this.from, this.to);\n  }\n\n  private async loadPresentation(presentationId: number | null): Promise<void> {\n    if(presentationId != null && !this.loadingInitiated) {\n      this.loadingInitiated = true;\n      const presentationContent = await this.presentationApi.getPresentationContent(presentationId);\n\n      await this.storeService.pipe(PresentationContentMutations.setContent, presentationContent)\n        .pipe(PresentationContextMutations.setInitialState, this.contextIdentifier, presentationId)\n        .pipe(PresentationContextMutations.setSlideId, this.contextIdentifier, null)\n        .dispatch();\n\n      const interval = window.setInterval(() => {\n        if(!this.element.isConnected) {\n          window.clearInterval(interval);\n          return;\n        }\n\n        this.staggeredRenderIndex++;\n        if(this.staggeredRenderIndex >= ((this.to ?? this.content?.slides.length ?? 0) - (this.from ?? 0))) {\n          if(PDF_CONTENT_LOADED_CBNAME in window && typeof window[PDF_CONTENT_LOADED_CBNAME] === 'function') window[PDF_CONTENT_LOADED_CBNAME]();\n          window.clearInterval(interval);\n        }\n      }, 500);\n    }\n  }\n\n  private updatePageStyleTag(): void {\n    if(!this.element.isConnected) return;\n    this.pageStyleTag?.remove();\n    this.pageStyleTag = HtmlUtils.createAndAppendStyleTag(`@page {\n      size: ${this.pageWidth} ${this.pageHeight};\n      margin: ${this.pageMargin};\n    }`);\n  }\n\n  private setPrintStyleTag(): void {\n    if(!this.element.isConnected) return;\n    this.printStyleTag?.remove();\n    this.printStyleTag = HtmlUtils.createAndAppendStyleTag(`html {\n      overflow: unset;\n    }\n\n    body > :not(.presentation-print-page__slide-container) {\n      display: none;\n    }`);\n  }\n}\n","import { autoinject } from 'aurelia-framework';\nimport { StoreService } from 'common/aurelia-services/StoreService';\nimport { VIEWER_PRESENTATION_CONTEXT } from 'common/constants/content.editor.constants';\nimport { IState } from 'store/state';\nimport * as ContentContextMutations from 'common/store/mutations/content.context.mutations';\n\ninterface IParams {\n  presentationId?: string;\n}\n\n@autoinject()\nexport class PresentationSlidesPage {\n  private contextIdentifier = VIEWER_PRESENTATION_CONTEXT;\n  private presentationId: number | null = null;\n\n  constructor(\n    private storeService: StoreService<IState>,\n  ) { }\n\n  protected activate(params: IParams): void {\n    if(params.presentationId != null) this.presentationId = parseInt(params.presentationId, 10);\n  }\n\n  protected async attached(): Promise<void> {\n    await this.storeService.dispatch(ContentContextMutations.destroyContext, this.contextIdentifier);\n  }\n}\n","import { autoinject } from 'aurelia-framework';\nimport { QuizQuestionsResponse } from 'common/dto/course/quiz/quiz.response';\nimport { LogUtils } from 'common/utils/LogUtils';\nimport { TestexamApiService } from '../../../services/api/TestexamApiService';\nimport { PageLoadingIndicatorService } from '../../../services/PageLoadingIndicatorService';\nimport { ToastService } from '../../../services/ToastService';\nimport { IQuizQuestionDto } from 'common/dto/course/quiz/quiz.question.interface';\nimport { ArrayUtils } from 'common/utils/ArrayUtils';\n\ninterface IParams {\n  quizAttemptId: string;\n}\n\n@autoinject()\nexport class QuizAttemptPage {\n  private quizAttemptId: number | null = null;\n  private result: QuizQuestionsResponse | null = null;\n  private questionsGrouped: { courseTitle: string; questions: IQuizQuestionDto[] }[] = [];\n  private showCorrectQuestions = false;\n\n  constructor(\n    private api: TestexamApiService,\n    private toastService: ToastService,\n    private indicatorService: PageLoadingIndicatorService,\n  ) { }\n\n  public async activate(params: IParams): Promise<void> {\n    this.quizAttemptId = parseInt(params.quizAttemptId);\n    await this.fetchData();\n  }\n\n  private async fetchData(): Promise<void> {\n    if(!this.quizAttemptId) return;\n    this.indicatorService.start();\n    try {\n      this.result = await this.api.getResult(this.quizAttemptId);\n      const questionsGrouped = [];\n      if(this.result.courseTitle && !this.result.subQuizSessions?.length) {\n        questionsGrouped.push({ courseTitle: this.result.courseTitle, questions: this.result.questions });\n      } else {\n        for(const result of this.result?.subQuizSessions ?? []) {\n          if(result.courseTitle) {\n            questionsGrouped.push({ courseTitle: result.courseTitle, questions: result.questions });\n          }\n        }\n      }\n      this.questionsGrouped = ArrayUtils.immutableSort(questionsGrouped, (a, b) => a.courseTitle.localeCompare(b.courseTitle));\n    } catch(error) {\n      this.toastService.error('pages.quiz-attempts-page.fetchError');\n      LogUtils.exception(error);\n    }\n    this.indicatorService.stop();\n  }\n\n  private back(): void {\n    window.history.back();\n  }\n}\n","import { autoinject, computedFrom } from 'aurelia-framework';\nimport { Router } from 'aurelia-router';\nimport { StoreService } from 'common/aurelia-services/StoreService';\nimport { ABSOLUTE_MAX_QUESTIONS } from 'common/constants/quiz.constants';\nimport { IQuizTemplateQuestionDto } from 'common/dto/course/content/question.interface';\nimport { ICourseEditResponseDto } from 'common/dto/course/management/course.management.interface';\nimport { IQuizTemplateWithQuestionIdsDto } from 'common/dto/course/quiz/template/quiz.template.interface';\nimport { ErrorType } from 'common/enum/errortype.enum';\nimport { RoleName } from 'common/enum/role.enums';\nimport { LogUtils } from 'common/utils/LogUtils';\nimport { assertNotNullOrUndefined } from 'common/utils/assert';\nimport { CustomDialogService } from '../../../services/CustomDialogService';\nimport { PageLoadingIndicatorService } from '../../../services/PageLoadingIndicatorService';\nimport { ToastService } from '../../../services/ToastService';\nimport { CourseManagementApiService } from '../../../services/api/CourseManagementApiService';\nimport { QuizTemplateApiService } from '../../../services/api/QuizTemplateApiService';\nimport { TenantApiService } from '../../../services/api/TenantApiService';\nimport { IState } from '../../../store/state';\nimport { AddQuestionEvent } from '../../elements/question-list-widget/question-list-widget';\nimport { QuizTemplateMoveQuestionEvent, QuizTemplateRemoveQuestionEvent } from '../../elements/selected-questions-overview/selected-questions-overview';\n\n@autoinject()\nexport class QuizTemplatePage {\n  private isProvider: boolean = false;\n  private isOperator: boolean = false;\n  private tenantId: number | null = null;\n  private quizTemplateId: number | null = null;\n  private quizTemplate: IQuizTemplateWithQuestionIdsDto | null = null;\n  private selectedQuestions: IQuizTemplateQuestionDto[] = [];\n  private course: ICourseEditResponseDto | null = null;\n\n  constructor(\n    private quizApi: QuizTemplateApiService,\n    private indicatorService: PageLoadingIndicatorService,\n    private toastService: ToastService,\n    private router: Router,\n    private customDialogService: CustomDialogService,\n    private storeService: StoreService<IState>,\n    private courseManagementApiService: CourseManagementApiService,\n    private tenantApiService: TenantApiService,\n  ) { }\n\n  protected attached(): void {\n    this.storeService.subscribeToDetailedChanges(this, [\n      state => state.currentRole,\n    ], (_, currentRole) => {\n      this.isProvider = currentRole?.role === RoleName.Provider;\n      this.isOperator = currentRole?.role === RoleName.Operator;\n      this.tenantId = currentRole?.tenantId ?? null;\n\n      if(!this.quizTemplate) {\n        this.updateQuizTemplate();\n      }\n    });\n  }\n\n  protected detached(): void {\n    this.storeService.disposeAllSubscriptions(this);\n  }\n\n  protected activate(params: Record<string, string>): void {\n    this.quizTemplateId = parseInt(params.quizTemplateId);\n    this.updateQuizTemplate();\n  }\n\n  @computedFrom('isOperator', 'isProvider', 'quizTemplate', 'tenantId')\n  private get releaseable(): boolean {\n    if(!this.quizTemplate) return false;\n    return this.isProvider\n      || (this.isOperator && this.quizTemplate.tenantId === this.tenantId);\n  }\n\n  @computedFrom('selectedQuestions')\n  private get selectedQuestionsIds(): number[] {\n    return this.selectedQuestions.map(question => question.id);\n  }\n\n  private async updateQuizTemplate(): Promise<void> {\n    if(!this.quizTemplateId) return;\n    this.quizTemplate = await this.quizApi.getQuizTemplate(this.quizTemplateId);\n    this.selectedQuestions = await this.quizApi.getQuizTemplateQuestions(this.quizTemplateId);\n    this.course = this.isProvider\n      ? await this.courseManagementApiService.getCourse(this.quizTemplate.courseId)\n      : await this.tenantApiService.getCourse(this.quizTemplate.courseId);\n  }\n\n  private async addQuestionToQuiz(event: AddQuestionEvent): Promise<void> {\n    assertNotNullOrUndefined(this.quizTemplateId, 'can\\'t add question without quizTemplateId');\n    const { questionId } = event.detail;\n\n    if(this.quizTemplate?.questionIds.includes(questionId)) {\n      this.toastService.success('pages.quiz-template.questionAlreadySaved');\n      return;\n    }\n    if(this.quizTemplate?.questionIds && this.quizTemplate.questionIds.length >= ABSOLUTE_MAX_QUESTIONS) {\n      this.toastService.error('pages.quiz-template.saveQuestionLimitExceededError', { limit: ABSOLUTE_MAX_QUESTIONS.toString() });\n      return;\n    }\n\n    this.indicatorService.start();\n    try {\n      await this.quizApi.addQuestionToQuiz(this.quizTemplateId, { questionId });\n      this.toastService.success('pages.quiz-template.saveQuestionSuccess');\n      await this.updateQuizTemplate();\n    } catch(e) {\n      if(e.message === ErrorType.QUIZ_TEMPLATE_QUESTION_LIMIT) {\n        this.toastService.error('pages.quiz-template.saveQuestionLimitExceededError', { limit: ABSOLUTE_MAX_QUESTIONS.toString() });\n      } else {\n        this.toastService.error('pages.quiz-template.saveQuestionError');\n        LogUtils.exception(e);\n      }\n    }\n    this.indicatorService.stop();\n  }\n\n  private async removeQuestionFromQuiz(event: QuizTemplateRemoveQuestionEvent): Promise<void> {\n    assertNotNullOrUndefined(this.quizTemplateId, 'can\\'t remove question without quizTemplateId');\n\n    this.indicatorService.start();\n    try {\n      await this.quizApi.removeQuestionFromQuiz(this.quizTemplateId, event.detail.questionId);\n      this.toastService.success('pages.quiz-template.removeQuestionSuccess');\n      await this.updateQuizTemplate();\n    } catch(e) {\n      this.toastService.error('pages.quiz-template.removeQuestionError');\n      LogUtils.exception(e);\n    }\n    this.indicatorService.stop();\n  }\n\n  private async moveQuestionInQuiz(event: QuizTemplateMoveQuestionEvent): Promise<void> {\n    assertNotNullOrUndefined(this.quizTemplateId, 'can\\'t remove question without quizTemplateId');\n\n    this.indicatorService.start();\n    try {\n      await this.quizApi.moveQuestionInQuiz(this.quizTemplateId, event.detail.questionId, { order: event.detail.targetOrder });\n      this.toastService.success('pages.quiz-template.editQuizTemplateSuccess');\n      await this.updateQuizTemplate();\n    } catch(e) {\n      this.toastService.error('pages.quiz-template.editQuizTemplateError');\n      LogUtils.exception(e);\n    }\n    this.indicatorService.stop();\n  }\n\n  private async editQuizTemplate(): Promise<void> {\n    assertNotNullOrUndefined(this.quizTemplate, 'can\\'t edit without quizTemplate');\n\n    const dialogValue = await this.customDialogService.showQuizTemplateEditDialog(this.quizTemplate);\n    if(!dialogValue) return;\n\n    this.indicatorService.start();\n    try {\n      await this.quizApi.editQuizTemplate(this.quizTemplate.id, dialogValue);\n      this.toastService.success('pages.quiz-template.editQuizTemplateSuccess');\n      await this.updateQuizTemplate();\n    } catch(e) {\n      this.toastService.error('pages.quiz-template.editQuizTemplateError');\n      LogUtils.exception(e);\n    }\n    this.indicatorService.stop();\n  }\n\n  private async deleteQuizTemplate(): Promise<void> {\n    assertNotNullOrUndefined(this.quizTemplate, 'can\\'t delete without quizTemplate');\n\n    const confirmation = await this.customDialogService.confirmDeletion();\n    if(!confirmation) return;\n\n    this.indicatorService.start();\n    try {\n      await this.quizApi.deleteQuizTemplate(this.quizTemplate.id);\n      this.toastService.success('pages.quiz-template.deleteQuizTemplateSuccess');\n      this.router.navigateToRoute('quiz-templates');\n    } catch(e) {\n      this.toastService.error('pages.quiz-template.deleteQuizTemplateError');\n      LogUtils.exception(e);\n    }\n    this.indicatorService.stop();\n  }\n\n  private async releaseQuizTemplate(): Promise<void> {\n    assertNotNullOrUndefined(this.quizTemplate, 'can\\'t release without quizTemplate');\n\n    this.indicatorService.start();\n    try {\n      if(this.quizTemplate.shared || this.quizTemplate.released) {\n        await this.quizApi.unreleaseQuizTemplate(this.quizTemplate.id);\n        this.toastService.success('pages.quiz-template.unreleaseQuizTemplateSuccess');\n      } else {\n        await this.quizApi.releaseQuizTemplate(this.quizTemplate.id);\n        this.toastService.success('pages.quiz-template.releaseQuizTemplateSuccess');\n      }\n      await this.updateQuizTemplate();\n    } catch(e) {\n      this.toastService.error('pages.quiz-template.releaseQuizTemplateError');\n      LogUtils.exception(e);\n    }\n    this.indicatorService.stop();\n  }\n\n  private navigateToBack(): void {\n    this.router.navigateBack();\n  }\n}\n","import { autoinject, computedFrom, observable } from 'aurelia-framework';\nimport { Router } from 'aurelia-router';\nimport { StoreService } from 'common/aurelia-services/StoreService';\nimport { ICourseEditResponseDto } from 'common/dto/course/management/course.management.interface';\nimport { IQuizTemplateWithQuestionCountDto } from 'common/dto/course/quiz/template/quiz.template.interface';\nimport { ColumnSortDirection, FilterOperationType } from 'common/dto/filter/filter.enum';\nimport { FilterField, IFilterDto } from 'common/dto/filter/filter.interface';\nimport { RoleName } from 'common/enum/role.enums';\nimport { FunctionUtils } from 'common/utils/FunctionUtils';\nimport { LogUtils } from 'common/utils/LogUtils';\nimport { assertNotNullOrUndefined } from 'common/utils/assert';\nimport { CustomDialogService } from '../../../services/CustomDialogService';\nimport { EventService } from '../../../services/EventService';\nimport { PageLoadingIndicatorService } from '../../../services/PageLoadingIndicatorService';\nimport { ToastService } from '../../../services/ToastService';\nimport { CourseManagementApiService } from '../../../services/api/CourseManagementApiService';\nimport { QuizTemplateApiService } from '../../../services/api/QuizTemplateApiService';\nimport { TenantApiService } from '../../../services/api/TenantApiService';\nimport { IQuickQuizData, IState } from '../../../store/state';\nimport { FilterUtils } from '../../../utils/FilterUtils';\n\n@autoinject()\nexport class QuizTemplatesPage {\n  private quizTemplates: IQuizTemplateWithQuestionCountDto[] = [];\n  private filter: IFilterDto<IQuizTemplateWithQuestionCountDto> = {\n    filter: [],\n    order: [],\n  };\n  private availableCourses: ICourseEditResponseDto[] = [];\n  private selectedCourses: ICourseEditResponseDto[] = [];\n  private debouncedUpdateQuizTemplates = FunctionUtils.debounce(() => { this.updateQuizTemplates(); }, 250);\n  @observable() private searchString = '';\n  @observable() private searchActive = false;\n\n  private isProvider: boolean = false;\n  private isOperator: boolean = false;\n  private userId: number | null = null;\n  private tenantId: number | null = null;\n  private showQuickEvents: boolean = false;\n\n  constructor(\n    private courseApi: CourseManagementApiService,\n    private customDialogService: CustomDialogService,\n    private indicatorService: PageLoadingIndicatorService,\n    private quizTemplateApi: QuizTemplateApiService,\n    private router: Router,\n    private storeService: StoreService<IState>,\n    private tenantApi: TenantApiService,\n    private toastService: ToastService,\n    private eventService: EventService,\n  ) { }\n\n  protected attached(): void {\n    this.storeService.subscribeToDetailedChanges(this, [\n      state => state.currentRole,\n    ], (_, currentRole) => {\n      this.isProvider = currentRole?.role === RoleName.Provider;\n      this.isOperator = currentRole?.role === RoleName.Operator;\n      this.tenantId = currentRole?.tenantId ?? null;\n      this.updateAvailableCourses();\n      this.updateQuizTemplates();\n    });\n\n    this.storeService.subscribeToDetailedChanges(this, [\n      state => state.user.id,\n      state => state.currentTenant\n    ], (_, userId, currentTenant) => {\n      this.userId = userId;\n      this.showQuickEvents = !!currentTenant?.showQuickEvents;\n    });\n  }\n\n  protected detached(): void {\n    this.storeService.disposeAllSubscriptions(this);\n  }\n\n  private async updateAvailableCourses(): Promise<void> {\n    this.availableCourses = this.isProvider\n      ? await this.courseApi.getAvailableRegularCourses()\n      : await this.tenantApi.getAvailableRegularCourses();\n  }\n\n  private async updateQuizTemplates(): Promise<void> {\n    this.filter = FilterUtils.removeFilterForField(this.filter, 'courseId');\n    const courseFilter: FilterField<IQuizTemplateWithQuestionCountDto>[] = this.selectedCourses.length ? [{\n      field: 'courseId',\n      value: this.selectedCourses.map(c => c.id),\n      type: FilterOperationType.In,\n    }] : [];\n\n    this.filter.order = [\n      {\n        field: 'released',\n        dir: this.isProvider ? ColumnSortDirection.DESC : ColumnSortDirection.ASC\n      },\n      {\n        field: 'shared',\n        dir: this.isProvider ? ColumnSortDirection.DESC : ColumnSortDirection.ASC\n      },\n    ];\n    this.filter.filter = [\n      ...(this.filter.filter ?? []),\n      ...courseFilter,\n    ];\n\n    this.quizTemplates = await this.quizTemplateApi.getQuizTemplates(this.filter);\n  }\n\n  private searchActiveChanged(): void {\n    if(!this.searchActive) {\n      this.updateQuizTemplates();\n    }\n  }\n\n  private searchStringChanged(): void {\n    const cleanSearchString: string = this.searchString.trim().toLocaleLowerCase();\n    if(cleanSearchString.length) {\n      this.filter = FilterUtils.removeDuplicateFilterFields(\n        this.filter,\n        {\n          field: 'name',\n          type: FilterOperationType.Like,\n          value: `%${cleanSearchString}%`\n        }\n      );\n    } else {\n      this.filter = FilterUtils.removeFilterForField(this.filter, 'name');\n    }\n    this.debouncedUpdateQuizTemplates();\n  }\n\n  private async createQuizTemplate(): Promise<void> {\n    const quizTemplate = await this.customDialogService.showQuizTemplateCreateDialog(this.availableCourses);\n\n    if(!quizTemplate) return;\n    this.indicatorService.start();\n    try {\n      await this.quizTemplateApi.createQuizTemplate(quizTemplate);\n      this.toastService.success('pages.quiz-templates.createQuizTemplateSuccess');\n      await this.updateQuizTemplates();\n    } catch(e) {\n      this.toastService.error('pages.quiz-templates.createQuizTemplateError');\n      LogUtils.exception(e);\n    }\n    this.indicatorService.stop();\n  }\n\n  private handleQuizTemplateEdit(quizTemplate: IQuizTemplateWithQuestionCountDto): void {\n    if(!this.isQuizTemplateEditable(quizTemplate)) return;\n\n    this.router.navigateToRoute('quiz-template/id', { quizTemplateId: quizTemplate.id });\n  }\n\n  private async handleQuizTemplatePlay(quizTemplate: IQuizTemplateWithQuestionCountDto): Promise<void> {\n    const selection = await this.customDialogService.showStartTemplateQuizDialog();\n    if(!selection) return;\n    const data: IQuickQuizData = {\n      quickQuiz: null,\n      templateQuiz: {\n        quizTemplateId: quizTemplate.id,\n        answerTime: selection.answerTime\n      }\n    };\n    await this.createQuickQuizDistinar(data);\n  }\n\n  private async handleQuickQuizPlay(): Promise<void> {\n    const selection = await this.customDialogService.showStartQuickQuizDialog(this.availableCourses, []);\n    if(!selection) return;\n    const data: IQuickQuizData = {\n      quickQuiz: selection,\n      templateQuiz: null\n    };\n    await this.createQuickQuizDistinar(data);\n  }\n\n  private async handleQuizTemplateReleaseClick(quizTemplate: IQuizTemplateWithQuestionCountDto): Promise<void> {\n    if(!this.isQuizTemplateReleasable(quizTemplate)) return;\n\n    this.indicatorService.start();\n    try {\n      if(quizTemplate.shared || quizTemplate.released) {\n        await this.quizTemplateApi.unreleaseQuizTemplate(quizTemplate.id);\n        this.toastService.success('pages.quiz-template.unreleaseQuizTemplateSuccess');\n      } else {\n        await this.quizTemplateApi.releaseQuizTemplate(quizTemplate.id);\n        this.toastService.success('pages.quiz-template.releaseQuizTemplateSuccess');\n      }\n      this.updateQuizTemplates();\n    } catch(e) {\n      this.toastService.error('pages.quiz-template.releaseQuizTemplateError');\n      LogUtils.exception(e);\n    }\n    this.indicatorService.stop();\n  }\n\n  private async createQuickQuizDistinar(quickQuizData: IQuickQuizData): Promise<void> {\n    assertNotNullOrUndefined(this.tenantId, 'tenantId cannot be null or undefined');\n    assertNotNullOrUndefined(this.userId, 'userId cannot be null or undefined');\n    await this.eventService.createAndStartQuickQuiz(this.tenantId, this.userId, quickQuizData);\n  }\n\n  private isQuizTemplateEditable(quizTemplate: IQuizTemplateWithQuestionCountDto): boolean {\n    return this.isProvider\n      // a provider might see their own quiz when logged in as operator\n      || (quizTemplate.creator.id === this.userId && !quizTemplate.released)\n      // operators can edit each others quiz if they are at the same tenant\n      || (this.isOperator && quizTemplate.tenantId === this.tenantId && !quizTemplate.released);\n  }\n\n  private isQuizTemplatePlayable(quizTemplate: IQuizTemplateWithQuestionCountDto): boolean {\n    return this.showQuickEvents && this.canStartQuickQuiz && quizTemplate.questionCount > 0;\n  }\n\n  @computedFrom('isProvider', 'showQuickEvents')\n  private get canStartQuickQuiz(): boolean {\n    return !this.isProvider && this.showQuickEvents;\n  }\n\n  private isQuizTemplateReleasable(quizTemplate: IQuizTemplateWithQuestionCountDto): boolean {\n    return this.isQuizTemplateEditable(quizTemplate) && (this.isOperator || this.isProvider);\n  }\n}\n"],"names":[],"sourceRoot":""}