{"version":3,"file":"app-0494c8f1.f584a1446b2fc034d810.bundle.js","mappings":"AAGA","sources":["webpack://lernplattform-admin-frontend/./src/resources/pages/content-editor-preview-frame/content-editor-preview-frame.html","webpack://lernplattform-admin-frontend/./src/resources/pages/content-editor-preview-window/content-editor-preview-window.html","webpack://lernplattform-admin-frontend/./src/resources/pages/content-editor/content-editor.html","webpack://lernplattform-admin-frontend/./src/resources/pages/course-new-course-page/course-new-course-page.html","webpack://lernplattform-admin-frontend/./src/resources/pages/course-overview-page/course-overview-page.html","webpack://lernplattform-admin-frontend/./src/resources/pages/course-page/course-page.html","webpack://lernplattform-admin-frontend/./src/resources/pages/data-protection-page/data-protection-page.html","webpack://lernplattform-admin-frontend/./src/resources/pages/distinar-page/distinar-page.html","webpack://lernplattform-admin-frontend/./src/resources/pages/distinar-separate-window/distinar-separate-conference-window/distinar-separate-conference-window.html","webpack://lernplattform-admin-frontend/./src/resources/pages/distinar-separate-window/distinar-separate-stream-window/distinar-separate-stream-window.html","webpack://lernplattform-admin-frontend/./src/resources/pages/distinar-separate-window/distinar-separate-window.html","webpack://lernplattform-admin-frontend/./src/resources/pages/employee-page/employee-page.html","webpack://lernplattform-admin-frontend/./src/resources/pages/employees-page/employees-page.html","webpack://lernplattform-admin-frontend/./src/resources/pages/error/link-error-page.html","webpack://lernplattform-admin-frontend/./src/resources/pages/error/login-error-page.html","webpack://lernplattform-admin-frontend/./src/resources/pages/error/not-found-page.html","webpack://lernplattform-admin-frontend/./src/resources/pages/content-editor-preview-frame/content-editor-preview-frame.ts","webpack://lernplattform-admin-frontend/./src/resources/pages/content-editor-preview-frame/index.ts","webpack://lernplattform-admin-frontend/./src/resources/pages/content-editor-preview-window/content-editor-preview-window.ts","webpack://lernplattform-admin-frontend/./src/resources/pages/content-editor-preview-window/index.ts","webpack://lernplattform-admin-frontend/./src/resources/pages/content-editor/content-editor.ts","webpack://lernplattform-admin-frontend/./src/resources/pages/course-new-course-page/course-new-course-page.ts","webpack://lernplattform-admin-frontend/./src/resources/pages/course-overview-page/course-overview-page.ts","webpack://lernplattform-admin-frontend/./src/resources/pages/course-page/course-page.ts","webpack://lernplattform-admin-frontend/./src/resources/pages/data-protection-page/data-protection-page.ts","webpack://lernplattform-admin-frontend/./src/resources/pages/distinar-page/distinar-page.ts","webpack://lernplattform-admin-frontend/./src/resources/pages/distinar-separate-window/distinar-separate-conference-window/distinar-separate-conference-window.ts","webpack://lernplattform-admin-frontend/./src/resources/pages/distinar-separate-window/distinar-separate-stream-window/distinar-separate-stream-window.ts","webpack://lernplattform-admin-frontend/./src/resources/pages/distinar-separate-window/distinar-separate-window.ts","webpack://lernplattform-admin-frontend/./src/resources/pages/distinar-separate-window/index.ts","webpack://lernplattform-admin-frontend/./src/resources/pages/employee-page/employee-page.ts","webpack://lernplattform-admin-frontend/./src/resources/pages/employees-page/employees-page.ts","webpack://lernplattform-admin-frontend/./src/resources/pages/error/link-error-page.ts","webpack://lernplattform-admin-frontend/./src/resources/pages/error/login-error-page.ts","webpack://lernplattform-admin-frontend/./src/resources/pages/error/not-found-page.ts"],"sourcesContent":["// Module\nvar code = \"<template> <course-learn-page-content context-identifier.to-view=\\\"contextIdentifier\\\" page-id.to-view=\\\"pageId\\\"></course-learn-page-content> </template>\";\n// Exports\nexport default code;","// Module\nvar code = \"<template> <div class=\\\"content-editor-preview-window\\\"> <div class=\\\"content-editor-preview-window__header-bar\\\"> <div class=\\\"content-editor-preview-window__header-title\\\">${title}</div> <div class=\\\"content-editor-preview-window__header-tools\\\"> <button repeat.for=\\\"sizeConfig of sizeConfigs\\\" class=\\\"content-editor-preview-window__tool-button ${isSizeConfigActive(sizeConfig, width, height) ? 'content-editor-preview-window__tool-button--active' : ''}\\\" click.trigger=\\\"setSize(sizeConfig)\\\"> <x-icon class=\\\"content-editor-preview-window__tool-icon\\\" icon-type=\\\"custom\\\" size=\\\"unspecified\\\" icon-name.to-view=\\\"sizeConfig.icon\\\"></x-icon> </button> </div> <button-lightswitch t=\\\"[label]pages.content-editor-preview-window.landscapeMode\\\" class=\\\"content-editor-preview-window__header-switch\\\" label-before.to-view=\\\"true\\\" value.to-view=\\\"isLandscape\\\" value-changed.delegate=\\\"toggleOrientation($event)\\\" theme=\\\"uce\\\"></button-lightswitch> </div> <iframe class=\\\"content-editor-preview-window__iframe\\\" style.to-view=\\\"style\\\" ref=\\\"iframeElement\\\" src.to-view=\\\"`/content-editor/preview/frame?type=content-editor-preview-frame`\\\"></iframe> </div> </template> \";\n// Exports\nexport default code;","// Module\nvar code = \"<template> <div class=\\\"content-editor ${mode === ContentEditorMode.COURSE ? 'content-editor--course' : ''} ${isIas ? 'content-editor--ias' : ''}\\\"> <content-editor-header-bar class=\\\"content-editor__header\\\" context-identifier.to-view=\\\"mode === ContentEditorMode.PRESENTATION ? presentationContext : courseContext\\\"></content-editor-header-bar> <content-editor-slide-selection if.to-view=\\\"mode === ContentEditorMode.PRESENTATION || isIas\\\" class=\\\"content-editor__slide-select\\\" context-identifier.to-view=\\\"mode === ContentEditorMode.PRESENTATION ? presentationContext : courseContext\\\" editable.to-view=\\\"true\\\"></content-editor-slide-selection> <content-editor-pages-sidebar if.to-view=\\\"mode === ContentEditorMode.COURSE\\\" class=\\\"content-editor__page-select\\\" context-identifier.to-view=\\\"mode === ContentEditorMode.PRESENTATION ? presentationContext : courseContext\\\"></content-editor-pages-sidebar> <div class=\\\"content-editor__content-area\\\"> <uce-slide-editor if.to-view=\\\"mode === ContentEditorMode.PRESENTATION\\\" class=\\\"content-editor__slide\\\" context-identifier.to-view=\\\"presentationContext\\\"></uce-slide-editor> <content-editor-page-editor if.to-view=\\\"mode === ContentEditorMode.COURSE\\\"></content-editor-page-editor> </div> <content-editor-sidebar-right class=\\\"content-editor__sidebar-right\\\" context-identifier.to-view=\\\"contextIdentifier\\\"></content-editor-sidebar-right> </div> </template> \";\n// Exports\nexport default code;","// Module\nvar code = \"<template> <a route-href=\\\"route: courses;\\\"> <button type=\\\"button\\\" class=\\\"btn btn-secondary btn-sm\\\"> <i class=\\\"fe fe-chevron-left\\\"></i><span t=\\\"globals.back\\\"></span> </button> </a> <div class=\\\"page-header\\\"> <h1 class=\\\"page-title\\\" t=\\\"pages.course.newCourse\\\"></h1> </div> <course-edit-form create-mode=\\\"true\\\" course-saved.trigger=\\\"createCourse()\\\" course.two-way=\\\"course\\\"></course-edit-form> </template>\";\n// Exports\nexport default code;","// Module\nvar code = \"<template class=\\\"course-overview-page\\\"> <div class=\\\"page-header\\\"> <h1 class=\\\"page-title\\\" t=\\\"pages.courses.title\\\"></h1> </div> <div class=\\\"d-flex\\\"> <div class=\\\"form-group\\\"> <label class=\\\"custom-control custom-checkbox custom-control-inline\\\"> <input class=\\\"custom-control-input\\\" type=\\\"checkbox\\\" change.delegate=\\\"updateCourses()\\\" checked.two-way=\\\"showArchived\\\"> <span class=\\\"custom-control-label\\\" t=\\\"globals.showArchived\\\"></span> </label> </div> <div class=\\\"ml-auto\\\"> <button-standard if.to-view=\\\"selectedCourses.length\\\" class=\\\"mb-2 d-inline-block\\\" click.delegate=\\\"removeCourses()\\\" t=\\\"[btnText]globals.delete\\\" icon-name=\\\"System/Tabler/Trash\\\" btn-color=\\\"danger\\\"></button-standard> <button-standard if.to-view=\\\"selectedCourses.length\\\" class=\\\"mb-2 d-inline-block\\\" click.delegate=\\\"toggleArchived()\\\" disabled.to-view=\\\"archivedToggleDisabled\\\" t=\\\"[btnText]${archiveTextTK}\\\" icon-name=\\\"System/Tabler/Folder\\\" btn-color=\\\"secondary\\\"></button-standard> <button-standard class=\\\"mb-2 d-inline-block\\\" click.delegate=\\\"addCourse()\\\" t=\\\"[btnText]pages.courses.addCourse\\\" icon-name=\\\"System/Tabler/Plus-Circle\\\" btn-color=\\\"primary\\\"></button-standard> </div> </div> <div class=\\\"row row-cards row-deck\\\"> <div class=\\\"col-12\\\"> <custom-table columns.to-view=\\\"tableColumns\\\" extendable.to-view=\\\"true\\\" selectable.to-view=\\\"true\\\" selected.two-way=\\\"selectedCourses\\\" table-id=\\\"course-overview-page\\\" refresh-version.to-view=\\\"tableRefreshVersion\\\" get-shown-entries-data.call=\\\"fetchTableData(limit, offset)\\\"> <button if.to-view=\\\"learningJourneyFeatureEnabled && selectedCourses.length\\\" click.delegate=\\\"assignTenantsToCourses()\\\" type=\\\"button\\\" class=\\\"btn btn-outline-primary btn-sm\\\" t=\\\"globals.assignTenants\\\"></button> </custom-table> </div> </div> </template>\";\n// Exports\nexport default code;","// Module\nvar code = \"<template> <button-standard click.trigger=\\\"navigateToCourseOverview()\\\" t=\\\"[btnText]globals.back\\\" icon-name=\\\"System/Tabler/Chevron-Left\\\" btn-size=\\\"btn-sm\\\" btn-color=\\\"secondary\\\"></button-standard> <div class=\\\"page-header flex-column align-items-start\\\"> <h1 class=\\\"page-title\\\">${course.title}</h1> <star-rating size=\\\"small\\\" show-label.to-view=\\\"true\\\" rating.to-view=\\\"rating.aggregateRating\\\" rating-count.to-view=\\\"rating.ratingCount\\\" t=\\\"[ratingCountLabel]pages.course.rating\\\" t-params.to-view=\\\"{ count: rating.ratingCount }\\\"></star-rating> </div> <div class=\\\"page-header mb-6\\\"> <navigation-tabs tabs.to-view=\\\"navigationTabs\\\"></navigation-tabs> </div> <div if.to-view=\\\"activeSubPage === SubPages.Settings\\\"> <course-edit-form course.to-view=\\\"course\\\" course-id.to-view=\\\"id\\\" reset-course-data-delegate.call=\\\"resetCourseData()\\\" course-saved.trigger=\\\"saveCourseData($event)\\\"></course-edit-form> <course-edit-success-files-form course-id.to-view=\\\"id\\\" files-updated.trigger=\\\"loadCourse()\\\" module-cover-learning-url.to-view=\\\"moduleCoverLearningUrl\\\" module-cover-training-url.to-view=\\\"moduleCoverTrainingUrl\\\" module-cover-testexam-url.to-view=\\\"moduleCoverTestexamUrl\\\" success-audio-url.to-view=\\\"successAudioUrl\\\" success-lottie-file-url.to-view=\\\"successLottieFileUrl\\\"></course-edit-success-files-form> </div> <div if.to-view=\\\"activeSubPage === SubPages.Structure\\\"> <div class=\\\"container\\\"> <div class=\\\"row\\\"> <div class=\\\"course-page__structure-header\\\"> <div class=\\\"course-page__structure-filter\\\"> <search-input class=\\\"course-page__search-input\\\" t=\\\"[placeholder]pages.content-editor.searchPlaceholder\\\" search-string.from-view=\\\"searchString\\\" search-active.from-view=\\\"searchActive\\\"></search-input> <select class=\\\"course-page__type-options custom-select\\\" value.bind=\\\"pageTypeFilter\\\" attach-focus> <option repeat.for=\\\"option of pageTypeOptions\\\" model.bind=\\\"option\\\" t.to-view=\\\"`pages.content-editor.pageTypeOptions.${option}`\\\"></option> </select> </div> <div class=\\\"course-page__structure-header-buttons-wrapper d-flex\\\"> <button-standard class=\\\"mr-2\\\" click.trigger=\\\"navigateToBlockOverview()\\\" t=\\\"[btnText]pages.course.showBlockOverviewBtnText\\\" icon-name=\\\"System/Tabler/Square\\\" btn-color=\\\"secondary\\\"></button-standard> <button-standard class=\\\"mr-2\\\" click.trigger=\\\"exportStructure()\\\" t=\\\"[btnText]pages.course.exportStructure\\\" btn-color=\\\"secondary\\\"></button-standard> <label class=\\\"mb-2 d-inline-block\\\"> <input type=\\\"file\\\" style=\\\"display:none\\\" files.two-way=\\\"selectedImportFiles\\\" change.delegate=\\\"importStructure()\\\" accept=\\\"lpfcs\\\"> <div class=\\\"btn btn-secondary course-page__import-btn-content\\\"> <safe-icon class=\\\"course-page__import-icon\\\" icon-name=\\\"System/Tabler/Upload\\\"></safe-icon> <span t=\\\"pages.course.importStructure\\\"></span> </div> </label> </div> </div> </div> </div> <content-editor-pages view-model.ref=\\\"contentEditorPages\\\" course-id.to-view=\\\"id\\\" type-filter.to-view=\\\"pageTypeFilter\\\" search-string.to-view=\\\"searchString\\\" search-active.to-view=\\\"searchActive\\\"></content-editor-pages> </div> <course-gallery if.to-view=\\\"activeSubPage === SubPages.MediaLibrary\\\" course-id.to-view=\\\"id\\\"></course-gallery> <course-questions-overview if.to-view=\\\"activeSubPage === SubPages.Questions\\\" id.to-view=\\\"id\\\"></course-questions-overview> <course-ratings-overview if.to-view=\\\"activeSubPage === SubPages.Ratings\\\" id.to-view=\\\"id\\\"></course-ratings-overview> <course-media-shares if.to-view=\\\"activeSubPage === SubPages.MediaShares\\\" course-id.to-view=\\\"id\\\"></course-media-shares> </template>\";\n// Exports\nexport default code;","// Module\nvar code = \"<template> <button-standard click.trigger=\\\"navigateBack()\\\" t=\\\"[btnText]globals.back\\\" icon-name=\\\"System/Tabler/Chevron-Left\\\" btn-size=\\\"btn-sm\\\" btn-color=\\\"secondary\\\"></button-standard> <div class=\\\"row mt-8\\\"> <div class=\\\"col-12\\\"> <markdown-text text.to-view=\\\"termsOfUseText\\\"> </markdown-text> </div> </div> </template>\";\n// Exports\nexport default code;","// Module\nvar code = \"<template> <div if.to-view=\\\"!isLoading && !hasFailedConnecting\\\" class=\\\"distinar-page\\\"> <template if.to-view=\\\"!isPaused\\\"> <distinar-course-view if.to-view=\\\"currentContentView === ContentView.Course\\\"></distinar-course-view> <distinar-quiz-view if.to-view=\\\"currentContentView === ContentView.Quiz\\\"></distinar-quiz-view> <distinar-conference-view if.to-view=\\\"currentContentView === ContentView.Conference\\\"></distinar-conference-view> </template> <lecture-break else active-lecture.to-view=\\\"activeLecture\\\" tenant-logo-picture-url.to-view=\\\"tenantTheme.logoPictureUrl\\\"></lecture-break> </div> <div if.to-view=\\\"hasFailedConnecting\\\" class=\\\"distinar-page--failed-connecting\\\"> <span class=\\\"distinar-page__connection-failed-text\\\" t=\\\"common.distinar.connectionFailed\\\"></span> <div class=\\\"distinar-page__connection-failed-grid\\\"> <lecture-button click.delegate=\\\"retryConnecting()\\\" class=\\\"distinar-page__retry-connecting-btn\\\" type=\\\"fill\\\"> <safe-icon class=\\\"distinar-page__btn-icon\\\" icon-name=\\\"System/Refresh\\\"></safe-icon> <span t=\\\"common.distinar.retryConnecting\\\"></span> </lecture-button> <lecture-button click.delegate=\\\"toHome()\\\" class=\\\"distinar-page__to-home-btn\\\" type=\\\"outline\\\"> <safe-icon class=\\\"distinar-page__btn-icon\\\" icon-name=\\\"System/Menu/Home\\\"></safe-icon> <span t=\\\"common.distinar.toHome\\\"></span> </lecture-button> </div> </div> <loading-overlay visible.to-view=\\\"isLoading\\\" status-messages.to-view=\\\"loadingStatusMessages\\\"></loading-overlay> <audio ref=\\\"audioElement\\\" style=\\\"display:none\\\"></audio> </template>\";\n// Exports\nexport default code;","// Module\nvar code = \"<template class=\\\"distinar-separate-conference-window\\\"> <conference-video-matrix class=\\\"distinar-separate-conference-window__matrix\\\"></conference-video-matrix> </template> \";\n// Exports\nexport default code;","// Module\nvar code = \"<template class=\\\"distinar-separate-stream-window ${isFullscreen ? 'distinar-separate-stream-window--is-fullscreen' : ''}\\\"> <fullscreenable-container view-model.ref=\\\"fullscreenableContainer\\\" is-fullscreen.from-view=\\\"isFullscreen\\\" class=\\\"distinar-separate-stream-window__content-wrapper\\\" mousemove.trigger=\\\"handleMousemove($event)\\\"> <conference-video-matrix if.to-view=\\\"contentShareAttendeeId != null || (showContentView && currentContentView === ContentView.Conference)\\\" class=\\\"distinar-separate-stream-window__matrix\\\" show-content-share.to-view=\\\"true\\\"></conference-video-matrix> <lecture-start if.to-view=\\\"!isLoading && isAboutToStart\\\" active-lecture.to-view=\\\"distinar\\\" tenant-logo-picture-url.to-view=\\\"tenantTheme.logoPictureUrl\\\"></lecture-start> <lecture-break if.to-view=\\\"!isLoading && isPaused\\\" active-lecture.to-view=\\\"distinar\\\" tenant-logo-picture-url.to-view=\\\"tenantTheme.logoPictureUrl\\\"></lecture-break> <lecture-end if.to-view=\\\"!isLoading && hasEnded\\\" active-lecture.to-view=\\\"distinar\\\" tenant-logo-picture-url.to-view=\\\"tenantTheme.logoPictureUrl\\\"></lecture-end> <active-distinar-course-slides if.to-view=\\\"showContentView && currentContentView === ContentView.Course\\\" class=\\\"distinar-separate-stream-window__slides\\\" distinar.to-view=\\\"distinar\\\" status.to-view=\\\"status\\\"></active-distinar-course-slides> <aspect-ratio-container if.to-view=\\\"showContentView && currentContentView === ContentView.Quiz\\\" class=\\\"distinar-separate-stream-window__aspect-ratio-container\\\" mode=\\\"width-and-height-dependent\\\"> <lecture-quiz-slide class=\\\"lecturer-lecture-quiz__slide\\\" lecture-status.to-view=\\\"status\\\" show-header-buttons.to-view=\\\"false\\\"></lecture-quiz-slide> </aspect-ratio-container> <div class=\\\"distinar-separate-stream-window__controls ${(mouseMovedRecently || mouseInsideControlElement) && supportsFullscreen ? 'distinar-separate-stream-window__controls--visible' : ''}\\\" data-control-element=\\\"data-control-element\\\"> <lecture-button class=\\\"distinar-separate-stream-window__fullscreen-toggle-btn\\\" type=\\\"fill\\\" click.delegate=\\\"onToggleFullscreen()\\\"> <safe-icon class=\\\"distinar-separate-stream-window__fullscreen-btn-icon\\\" icon-name.to-view=\\\"fullscreenToggleIconName\\\"></safe-icon> </lecture-button> </div> </fullscreenable-container> </template> \";\n// Exports\nexport default code;","// Module\nvar code = \"<template> <distinar-separate-conference-window if.to-view=\\\"type === SeparateWindowType.Conference\\\"></distinar-separate-conference-window> <distinar-separate-stream-window if.to-view=\\\"type === SeparateWindowType.Stream\\\"></distinar-separate-stream-window> </template> \";\n// Exports\nexport default code;","// Module\nvar code = \"<template> <a route-href=\\\"route: employees\\\"> <button type=\\\"button\\\" class=\\\"btn btn-secondary btn-sm\\\"> <i class=\\\"fe fe-chevron-left\\\"></i><span t=\\\"globals.back\\\"></span> </button> </a> <div class=\\\"page-header\\\"> <h1 class=\\\"page-title\\\" t=\\\"${pageTitleTk}\\\"></h1> </div> <div class=\\\"row row-cards row-deck\\\"> <div class=\\\"card\\\"> <div class=\\\"card-header\\\"> <h3 class=\\\"card-title\\\" t=\\\"pages.employee.form.title\\\"></h3> </div> <div class=\\\"card-body\\\"> <user-form data.two-way=\\\"userData\\\" view-model.ref=\\\"userForm\\\"></user-form> </div> <div class=\\\"card-footer text-right\\\"> <div class=\\\"d-flex\\\"> <button class=\\\"btn btn-primary ml-auto\\\" click.delegate=\\\"saveOrCreate()\\\"> <i if.to-view=\\\"!isEditMode\\\" class=\\\"fe fe-user-plus mr-2\\\"></i><span t=\\\"${submitTk}\\\"></span> </button> </div> </div> </div> </div> </template>\";\n// Exports\nexport default code;","// Module\nvar code = \"<template> <div class=\\\"page-header\\\"> <h1 class=\\\"page-title\\\" t=\\\"pages.employees.title\\\"></h1> <div class=\\\"ml-auto\\\"> <button-standard click.delegate=\\\"linkLecturer()\\\" class=\\\"mb-2 d-inline-block\\\" t=\\\"[btnText]pages.employees.form.addLecturer\\\" icon-name=\\\"System/Tabler/User-Plus\\\" btn-color=\\\"primary\\\"></button-standard> <button-standard click.delegate=\\\"linkPowerUser()\\\" class=\\\"mb-2 d-inline-block\\\" t=\\\"[btnText]pages.employees.form.addPowerUser\\\" icon-name=\\\"System/Tabler/User-Plus\\\" btn-color=\\\"primary\\\"></button-standard> </div> </div> <div class=\\\"row row-cards row-deck\\\"> <div class=\\\"col-12\\\"> <custom-table columns.to-view=\\\"tableColumns\\\" extendable=\\\"true\\\" table-id=\\\"employees-page\\\" data.to-view=\\\"tenant.users\\\"></custom-table> </div> </div> </template>\";\n// Exports\nexport default code;","// Module\nvar code = \"<template> <error-block code.to-view=\\\"code\\\" t.to-view=\\\"'[title]'+titleTk+';[message]'+messageTk\\\"> </error-block></template> \";\n// Exports\nexport default code;","// Module\nvar code = \"<template> <error-block class=\\\"col-login\\\" code=\\\"511\\\" t=\\\"[title]pages.error.login.title;[message]pages.error.login.loginFailedExternal\\\"></error-block> </template>\";\n// Exports\nexport default code;","// Module\nvar code = \"<template> <error-block code.to-view=\\\"404\\\" t=\\\"[title]pages.error.404.title;[message]pages.error.404.message\\\"> </error-block></template> \";\n// Exports\nexport default code;","import { autoinject, Disposable } from 'aurelia-framework';\nimport { StoreManager } from 'common/aurelia-classes/StoreManager';\nimport { selectState } from 'common/aurelia-decorators/select-state';\nimport { DataStorageService } from 'common/aurelia-services/DataStorageService';\nimport { CONTENT_EDITOR_COURSE_CONTEXT } from 'common/constants/content.editor.constants';\nimport { pageIdSelectorCreator } from 'common/store/selectors/course.context.selectors';\nimport { IndexedDbChangedMessage, SeparateWindowService } from 'services/SeparateWindowService';\nimport { applyPartialState } from 'store/actions';\nimport { IState } from 'store/state';\n\n@autoinject()\nexport class ContentEditorPreviewFrame {\n  public contextIdentifier = CONTENT_EDITOR_COURSE_CONTEXT;\n\n  @selectState(ContentEditorPreviewFrame, pageIdSelectorCreator, ['contextIdentifier']) private pageId: number | null = null;\n\n  private disposables: Disposable[] = [];\n\n  constructor(\n    private dataStorageService: DataStorageService,\n    private separateWindowService: SeparateWindowService,\n  ) { }\n\n  public attached(): void {\n    // TODO: selective rehydration could perhaps be centralized to somewhere, as a general means to control external windows from the main window\n    this.disposables.push(this.separateWindowService.subscribeToCrossTabMessage<IndexedDbChangedMessage>('indexeddb-changed', async () => {\n      StoreManager.getStore<IState>().dispatch(applyPartialState, {\n        contentContexts: await this.dataStorageService.getItem<IState['contentContexts']>('$adminMainstate - contentContexts') ?? {},\n        courseContentById: await this.dataStorageService.getItem<IState['courseContentById']>('$adminMainstate - courseContentById') ?? {},\n      });\n    }));\n  }\n\n  public detached(): void {\n    this.disposables.forEach(d => d.dispose());\n    this.disposables.length = 0;\n  }\n}\n","import { FrameworkConfiguration, PLATFORM } from 'aurelia-framework';\n\nexport function configure(config: FrameworkConfiguration): void {\n  config.globalResources(PLATFORM.moduleName('resources/pages/content-editor-preview-frame/content-editor-preview-frame'));\n}\n","import { autoinject } from 'aurelia-framework';\nimport { ButtonLightswitchValueChangedEvent } from 'common/aurelia-components/button-lightswitch/button-lightswitch';\nimport { selectState } from 'common/aurelia-decorators/select-state';\nimport { SubscriptionManager, SubscriptionManagerService } from 'common/aurelia-services/SubscriptionManagerService';\nimport { CONTENT_EDITOR_COURSE_CONTEXT } from 'common/constants/content.editor.constants';\nimport { courseTitleSelectorCreator } from 'common/store/selectors/course.context.selectors';\nimport { assertNotNullOrUndefined } from 'common/utils/assert';\n\n@autoinject()\nexport class ContentEditorPreviewWindow {\n  public contextIdentifier = CONTENT_EDITOR_COURSE_CONTEXT;\n\n  @selectState(ContentEditorPreviewWindow, courseTitleSelectorCreator, ['contextIdentifier']) private title: string | null = null;\n\n  private iframeElement: HTMLIFrameElement | null = null;\n  private subscriptionManager: SubscriptionManager;\n\n  private sizeConfigs: ISizeConfig[] = [\n    { height: 640, width: 360, icon: 'Uce/DeviceMobile' },\n    { height: 640, width: 512, icon: 'Uce/DeviceTablet' },\n    { height: 720, width: 1024, icon: 'Uce/DeviceDesktop' },\n  ];\n\n  private width = 360;\n  private height = 640;\n  private isLandscape = false;\n  private style: Record<string, string> = {};\n\n  constructor(\n    subscriptionManagerService: SubscriptionManagerService,\n  ) {\n    this.subscriptionManager = subscriptionManagerService.createSubscriptionManager();\n  }\n\n  protected attached(): void {\n    this.refresh();\n    assertNotNullOrUndefined(this.iframeElement, 'forgot to ref iframeElement');\n    this.subscriptionManager.subscribeToElementResize(this.iframeElement, cr => {\n      this.width = cr.width;\n      this.height = cr.height;\n      this.refresh();\n    });\n  }\n\n  private toggleOrientation(event: ButtonLightswitchValueChangedEvent): void {\n    const { width, height } = this;\n    const shouldBeLandscape = event.detail.value;\n\n    const [shorterSide, longerSide] = [width, height].sort((a, b) => a - b);\n    this.setSize(shouldBeLandscape ? {\n      width: longerSide,\n      height: shorterSide,\n    } : {\n      width: shorterSide,\n      height: longerSide,\n    });\n  }\n\n  private setSize(sizeConfig: Pick<ISizeConfig, 'width' | 'height'>): void {\n    this.width = sizeConfig.width;\n    this.height = sizeConfig.height;\n    this.refresh();\n  }\n\n  /**\n   * a bug in ios prevents us from using 100vh to set the page size\n   * to support safari on ios < 15.4 viewport height is calculated here\n   */\n  private updateViewportHeight(): void {\n    const vh = window.innerHeight * 0.01;\n    document.documentElement.style.setProperty('--course-learn-page-vh-in-px', `${vh}px`);\n  }\n\n  private isSizeConfigActive(sizeConfig: ISizeConfig, width: number, height: number): boolean {\n    return (sizeConfig.width === width && sizeConfig.height === height)\n      || (sizeConfig.width === height && sizeConfig.height === width);\n  }\n\n  private refresh(): void {\n    this.isLandscape = this.width > this.height;\n    this.updateViewportHeight();\n    this.style = {\n      width: `${this.width}px`,\n      height: `${this.height}px`,\n    };\n  }\n}\n\ninterface ISizeConfig {\n  width: number;\n  height: number;\n  icon: string;\n}\n","import { FrameworkConfiguration, PLATFORM } from 'aurelia-framework';\n\nexport function configure(config: FrameworkConfiguration): void {\n  config.globalResources(PLATFORM.moduleName('resources/pages/content-editor-preview-window/content-editor-preview-window'));\n}\n","import { autoinject } from 'aurelia-framework';\nimport { Router } from 'aurelia-router';\nimport { selectState } from 'common/aurelia-decorators/select-state';\nimport { StoreService } from 'common/aurelia-services/StoreService';\nimport { CONTENT_EDITOR_COURSE_CONTEXT, CONTENT_EDITOR_PRESENTATION_CONTEXT } from 'common/constants/content.editor.constants';\nimport { ContentElementType } from 'common/dto/content-editor/content.editor.types';\nimport { ThemeColor } from 'common/enum/theme.enums';\nimport * as CourseContentMutations from 'common/store/mutations/course.content.mutations';\nimport * as CourseContextMutations from 'common/store/mutations/course.context.mutations';\nimport * as PresentationContentMutations from 'common/store/mutations/presentation.content.mutations';\nimport * as PresentationContextMutations from 'common/store/mutations/presentation.context.mutations';\nimport { isIasSelectorCreator } from 'common/store/selectors/course.context.selectors';\nimport { ICourseContextState } from 'common/store/store.interface';\nimport { assertNotNullOrUndefined } from 'common/utils/assert';\nimport { LogUtils } from 'common/utils/LogUtils';\nimport { ContentEditorApiService } from '../../../services/api/ContentEditorApiService';\nimport { PresentationManagementApiService } from '../../../services/api/PresentationManagementApiService';\nimport { TenantApiService } from '../../../services/api/TenantApiService';\nimport {\n  DndContext,\n  DndManager,\n  DndManagerService,\n  DndPayloadUniversal,\n  DndSubContext\n} from '../../../services/DndManagerService';\nimport * as ClipboardMutations from '../../../store/clipboard.mutations';\nimport * as ContentEditorMutations from '../../../store/content.editor.mutations';\nimport { IContentEditorDndState, IState } from '../../../store/state';\nimport * as TemplateMutations from '../../../store/template.mutations';\nimport { InspectorMode } from './../../../store/state';\n\ninterface IParams {\n  presentationId?: string;\n  courseId?: string;\n  pageId?: string;\n}\n\nexport enum ContentEditorMode {\n  PRESENTATION = 'presentation',\n  COURSE = 'course',\n}\n\n@autoinject()\nexport class ContentEditor {\n  public contextIdentifier?: string;\n\n  private presentationId: number | null = null;\n  private courseId: number | null = null;\n  private pageId?: number;\n  private contentMacroId: number | null = null;\n\n  private mode: ContentEditorMode = ContentEditorMode.PRESENTATION;\n  private presentationContext = CONTENT_EDITOR_PRESENTATION_CONTEXT;\n  private courseContext = CONTENT_EDITOR_COURSE_CONTEXT;\n  private ContentEditorMode = ContentEditorMode;\n  private inspectorMode: InspectorMode | null = null;\n\n  private dndLayoutActive = false;\n  private dndLayoutContext: DndContext = DndContext.ContentEditorLayout;\n  private dndLayoutManager: DndManager<ContentElementType>;\n\n  @selectState(ContentEditor, isIasSelectorCreator, ['contextIdentifier']) private isIas?: boolean;\n\n  constructor(\n    private router: Router,\n    private storeService: StoreService<IState>,\n    private dndManagerService: DndManagerService,\n    private contentEditorApi: ContentEditorApiService,\n    private tenantApi: TenantApiService,\n    private presentationManagementApi: PresentationManagementApiService,\n  ) {\n    this.dndLayoutManager = this.dndManagerService.getDndManager<ContentElementType>(this.dndLayoutContext);\n  }\n\n  public async dndLayoutPayloadChangedHandler(payload: DndPayloadUniversal<ContentElementType>): Promise<void> {\n    const dndActive = payload != null;\n    const normalDndMode = dndActive && payload.source !== DndSubContext.Template;\n\n    let dndStateUpdate: Partial<IContentEditorDndState> = {\n      active: dndActive,\n      showDroptargets: normalDndMode,\n    };\n    if(!dndActive) dndStateUpdate = {\n      ...dndStateUpdate,\n      lowerWhitespaceTargeted: false,\n    };\n\n    const pipe = this.storeService.pipe(ContentEditorMutations.updateDragAndDropState, dndStateUpdate);\n    if(this.inspectorMode !== InspectorMode.Container) pipe\n      .pipe(ContentEditorMutations.updateContainerStylesState, { showExtraContainerSpace: normalDndMode });\n    if(!dndActive) pipe.pipe(ContentEditorMutations.resetContainerHoverState);\n    await pipe.dispatch();\n  }\n\n  protected async activate(params: IParams): Promise<void> {\n    if(params.presentationId) {\n      await this.storeService.dispatch(ContentEditorMutations.updateContentEditorMode, ContentEditorMode.PRESENTATION);\n      this.mode = ContentEditorMode.PRESENTATION;\n      this.contextIdentifier = CONTENT_EDITOR_PRESENTATION_CONTEXT;\n      this.presentationId = parseInt(params.presentationId, 10);\n      this.contentMacroId = this.presentationId;\n    } else if(params.courseId) {\n      await this.storeService.dispatch(ContentEditorMutations.updateContentEditorMode, ContentEditorMode.COURSE);\n      this.mode = ContentEditorMode.COURSE;\n      this.contextIdentifier = CONTENT_EDITOR_COURSE_CONTEXT;\n      this.courseId = parseInt(params.courseId, 10);\n      this.pageId = params.pageId != null ? parseInt(params.pageId, 10) : undefined;\n      this.contentMacroId = this.courseId;\n    }\n    // Reset sidebar right state here, to fix possible leftover state after using browser history navigation\n    void this.storeService\n      .pipe(ContentEditorMutations.updateContainerStylesState, { showExtraContainerSpace: false })\n      .pipe(ContentEditorMutations.closeSidebarRight)\n      .pipe(ContentEditorMutations.updateInspector)\n      .dispatch();\n\n    this.refreshContent();\n  }\n\n  protected attached(): void {\n    this.dndLayoutManager.subscribePayloadChanged(this, this.dndLayoutPayloadChangedHandler.bind(this));\n    this.subscribeToState();\n  }\n\n  protected detached(): void {\n    this.storeService.disposeAllSubscriptions(this);\n    this.dndLayoutManager.disposeSubscriptions(this);\n  }\n\n  private subscribeToState(): void {\n    this.storeService.disposeAllSubscriptions(this);\n\n    this.storeService.subscribeToDetailedChanges(this, [\n      state => state.currentRole?.tenantId ?? null,\n      state => state.contentEditor.mode\n    ], (_state, tenantId, mode) => {\n      this.mode = mode;\n      this.updateThemeColors(tenantId);\n      this.updateIsTenantShared(tenantId);\n    });\n\n    this.storeService.subscribeToDetailedChanges(this, [\n      state => (state.contentContexts[CONTENT_EDITOR_COURSE_CONTEXT]?.state as ICourseContextState | undefined)?.pageId ?? null,\n    ], (_state, pageId) => {\n      if(this.mode === ContentEditorMode.COURSE && pageId != null) {\n        this.router.navigateToRoute('content-editor-course-page', {\n          courseId: this.courseId,\n          pageId,\n        }, { trigger: false });\n      }\n    });\n\n    this.storeService.subscribeToDetailedChanges(this, [\n      state => state.contentEditor.inspector.mode,\n    ], (_state, inspectorMode) => {\n      this.inspectorMode = inspectorMode;\n    });\n  }\n\n  private async updateThemeColors(tenantId: number | null): Promise<void> {\n    const tenant = tenantId ? await this.tenantApi.getTenant(tenantId) : null;\n\n    await this.storeService.dispatch(ContentEditorMutations.updateThemeColors, [\n      tenant?.primaryColor ?? ThemeColor.ColdBlue,\n      tenant?.secondaryColor1 ?? ThemeColor.BrightOrange,\n      tenant?.secondaryColor2 ?? ThemeColor.FreshGreen,\n      tenant?.secondaryColor3 ?? ThemeColor.MellowPurple\n    ]);\n  }\n\n  private async updateIsTenantShared(tenantId: number | null): Promise<void> {\n    if(!this.presentationId || !tenantId) return;\n    const isShared = await this.presentationManagementApi.getIsPresentationShared(this.presentationId, tenantId);\n    await this.storeService.dispatch(ContentEditorMutations.updateIsPresentationShared, isShared);\n  }\n\n  private async refreshContent(): Promise<void> {\n    try {\n      if(this.mode === ContentEditorMode.PRESENTATION) {\n        assertNotNullOrUndefined(this.presentationId, 'presentationId cannot be null or undefined');\n        const presentationContent = await this.contentEditorApi.getPresentationContent(this.presentationId);\n        await this.storeService.pipe(PresentationContentMutations.setContent, presentationContent)\n          .pipe(PresentationContextMutations.setInitialState, CONTENT_EDITOR_PRESENTATION_CONTEXT, this.presentationId)\n          .dispatch();\n      } else if(this.mode === ContentEditorMode.COURSE) {\n        assertNotNullOrUndefined(this.courseId, 'courseId cannot be null or undefined');\n        const courseContent = await this.contentEditorApi.getCourseContent(this.courseId);\n        await this.storeService.pipe(CourseContentMutations.setContent, courseContent)\n          .pipe(CourseContextMutations.setInitialState, CONTENT_EDITOR_COURSE_CONTEXT, this.courseId, this.pageId)\n          .dispatch();\n      }\n      assertNotNullOrUndefined(this.contentMacroId, 'presentation or course Id cannot be null or undefined');\n      const clipboardContent = await this.contentEditorApi.getClipboardContent(this.contentMacroId);\n      await this.storeService.dispatch(ClipboardMutations.setContent, clipboardContent);\n      const templates = await this.contentEditorApi.getTemplates(this.contentMacroId);\n      await this.storeService.dispatch(TemplateMutations.setContent, templates);\n    } catch(error) {\n      this.navigateBack();\n      LogUtils.error(error);\n    }\n  }\n\n  private navigateBack(): void {\n    const routeName = this.mode === ContentEditorMode.PRESENTATION ? 'presentation-overview' : 'courses/id';\n    this.router.navigateToRoute(routeName, { id: this.courseId });\n  }\n}\n","import { autoinject } from 'aurelia-framework';\nimport { Router } from 'aurelia-router';\nimport { DEFAULT_ACCESS_CODE_EXPIRATION_IN_MONTHS, DEFAULT_DAYS_TO_EXAM, DEFAULT_FRP_ACCESS_CODE_EXPIRATION_IN_MONTHS, DEFAULT_MIN_DAYS_TO_EXAM } from 'common/constants/course.constants';\nimport { ICreateCourseDto } from 'common/dto/course/management/course.management.interface';\nimport { Language } from 'common/dto/other/language.enum';\nimport { GamesAndChatStatus } from 'common/enum/course.enum';\nimport { ErrorType } from 'common/enum/errortype.enum';\nimport { ExamType } from 'common/enum/quiz.enum';\nimport { AppUtils } from 'common/utils/AppUtils';\nimport { CourseManagementApiService } from '../../../services/api/CourseManagementApiService';\nimport { ToastService } from '../../../services/ToastService';\n\n@autoinject()\nexport class CourseNewCoursePage {\n  private course: ICreateCourseDto | null = null;\n\n  constructor(\n    private courseManagementApiService: CourseManagementApiService,\n    private toastService: ToastService,\n    private router: Router\n  ) { }\n\n  protected activate(): void {\n    const { isFrp } = AppUtils;\n    const accessCodeExpirationInMonths: number = isFrp\n      ? DEFAULT_FRP_ACCESS_CODE_EXPIRATION_IN_MONTHS\n      : DEFAULT_ACCESS_CODE_EXPIRATION_IN_MONTHS;\n    this.course = {\n      accessCodeExpirationInMonths,\n      defaultDaysToExam: DEFAULT_DAYS_TO_EXAM,\n      examType: ExamType.Default,\n      gamesAndChatStatus: GamesAndChatStatus.Enabled,\n      language: Language.De,\n      learnChaptersInQuizEnabled: false,\n      learningDisabledExplanation: null,\n      learningEnabled: true,\n      mediaPercentageConsideredSeen: 80,\n      minDaysToExam: DEFAULT_MIN_DAYS_TO_EXAM,\n      minPointsToPassAssessment: null,\n      selectedAvatarSetIds: [],\n      textPercentageConsideredRead: 80,\n      title: '',\n    };\n  }\n\n  private async createCourse(): Promise<void> {\n    if(!this.course) return;\n    try {\n      const created = await this.courseManagementApiService.createCourse(this.course);\n      this.toastService.genericCreateSuccess('globals.course');\n      this.router.navigateToRoute('courses/id', { id: created.id });\n    } catch(e) {\n      if(e.message === ErrorType.COURSE_TITLE_ALREADY_IN_USE) {\n        this.toastService.error('pages.course.messages.newCourseTitleAlreadyInUse');\n      } else if(e.message === ErrorType.VALIDATION_FAILED) {\n        this.toastService.error('pages.course.messages.invalidData');\n      } else {\n        this.toastService.genericCreateError('globals.course');\n      }\n    }\n  }\n}\n","import { autoinject, computedFrom } from 'aurelia-framework';\nimport { Router } from 'aurelia-router';\nimport { COURSE_RATING_OPTIONS } from 'common/constants/course.constants';\nimport { ICourseEditResponseDto } from 'common/dto/course/management/course.management.interface';\nimport { ColumnSortDirection, FilterOperationType } from 'common/dto/filter/filter.enum';\nimport { IFilterDto } from 'common/dto/filter/filter.interface';\nimport { ErrorType } from 'common/enum/errortype.enum';\nimport { Config } from 'common/fe-config/config';\nimport { LogUtils } from 'common/utils/LogUtils';\nimport { CustomDialogService } from '../../../services/CustomDialogService';\nimport { ToastService } from '../../../services/ToastService';\nimport { CourseManagementApiService } from '../../../services/api/CourseManagementApiService';\nimport { FilterUtils } from '../../../utils/FilterUtils';\nimport { CustomTableCell } from '../../elements/custom-table/custom-table-cell/custom-table-cell';\nimport { VirtualFilterField, createPrepareServersideSortingFunction, prepareServersideFiltering } from '../../elements/custom-table/custom-table-helpers';\nimport { IColumnDefinition, SortNulls } from '../../elements/custom-table/custom-table-interfaces';\nimport { RatingCell } from '../../elements/custom-table/rating-cell/rating-cell';\nimport { TaggedTextTableCell } from '../../elements/custom-table/tagged-text-cell/tagged-text-cell';\n\n@autoinject()\nexport class CourseOverviewPage {\n  private selectedCourses: ICourseEditResponseDto[] = [];\n  private showArchived = false;\n  private learningJourneyFeatureEnabled: boolean = Config().learningJourney.enabled;\n  private filter: IFilterDto<ICourseEditResponseDto> = {\n    filter: [],\n    order: [{\n      field: 'title',\n      dir: ColumnSortDirection.ASC,\n    }],\n  };\n  private tableRefreshVersion: number = 0;\n\n  private tableColumns: Array<\n    IColumnDefinition<CustomTableCell<ICourseEditResponseDto>\n      | TaggedTextTableCell<ICourseEditResponseDto>\n      | RatingCell<ICourseEditResponseDto>\n    >> = [\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: course => this.router.navigateToRoute('courses/id', { id: course.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: 'language',\n        nameTk: 'globals.language',\n        cellViewModel: CustomTableCell,\n        prepareSorting: createPrepareServersideSortingFunction(this.filter, 'language'),\n        cellOptions: {\n          transformer: course => course.language.toUpperCase(),\n        }\n      },\n      {\n        id: 'type',\n        nameTk: 'globals.type',\n        cellViewModel: CustomTableCell,\n        cellOptions: {\n          tk: course => course.isLectureCourse ? 'globals.lectureCourse' : 'globals.course'\n        }\n      },\n      {\n        id: 'rating',\n        nameTk: 'elements.ratingTable.rating',\n        cellViewModel: RatingCell,\n        cellOptions: {\n          rating: course => course.aggregateRating,\n        },\n        prepareSorting: createPrepareServersideSortingFunction(this.filter, 'aggregateRating'),\n        filter: {\n          placeholderTk: 'globals.filter',\n          prepare: v => prepareServersideFiltering(this.filter, 'aggregateRating', v, FilterOperationType.EqFloor),\n          options: COURSE_RATING_OPTIONS,\n        },\n        sortNulls: SortNulls.Smallest,\n      },\n    ];\n\n  constructor(\n    private courseManagementApiService: CourseManagementApiService,\n    private router: Router,\n    private toastService: ToastService,\n    private customDialogService: CustomDialogService,\n  ) { }\n\n  private async updateCourses(): Promise<void> {\n    this.tableRefreshVersion++;\n  }\n\n  private async fetchTableData(limit: number, offset: number): Promise<{ total: number; data: ICourseEditResponseDto[] }> {\n    this.filter.limit = limit;\n    this.filter.offset = offset;\n\n    this.filter = this.showArchived\n      ? FilterUtils.removeFilterForField(this.filter, 'isArchived')\n      : this.filter = FilterUtils.removeDuplicateFilterFields(this.filter, {\n        field: 'isArchived' as VirtualFilterField,\n        type: FilterOperationType.Ne,\n        value: true,\n      });\n\n    const response = await this.courseManagementApiService.getCourses(this.filter);\n\n    return {\n      data: response.rows,\n      total: response.count,\n    };\n  }\n\n  private addCourse(): void {\n    this.router.navigateToRoute('courses/new');\n  }\n\n  private async removeCourses(): Promise<void> {\n    const confirmed = await this.customDialogService.confirmAction({\n      icon: { name: 'alert-triangle', color: 'var(--warning)' },\n      titleTk: 'pages.courses.deleteWarningTitle',\n      textTk: 'pages.courses.deleteWarning',\n      textTkParams: { courseTitles: this.selectedCourses.map(c => c.title).join(', ') },\n      confirmTk: 'pages.courses.deleteWarningTitle',\n    });\n    if(!confirmed) return;\n    for(const course of this.selectedCourses) {\n      try {\n        await this.courseManagementApiService.removeCourse(course.id);\n        this.toastService.success('pages.courses.deleteSuccess');\n      } catch(e) {\n        if(e.message === ErrorType.RESOURCE_IN_USE) {\n          this.toastService.error('pages.courses.resourceInUseError', { courseTitle: course.title });\n        } else {\n          this.toastService.genericDeleteError('pages.courses.deleteError');\n          LogUtils.exception(e);\n        }\n      }\n    }\n    this.updateCourses();\n  }\n\n  private async toggleArchived(): Promise<void> {\n    const idsToArchive = this.selectedCourses.filter(c => !c.isArchived).map(c => c.id);\n    const idsToUnarchive = this.selectedCourses.filter(c => c.isArchived).map(c => c.id);\n    if(idsToArchive.length) {\n      await this.courseManagementApiService.archiveCourse(idsToArchive);\n    }\n    if(idsToUnarchive.length) {\n      await this.courseManagementApiService.unarchiveCourse(idsToUnarchive);\n    }\n    this.updateCourses();\n  }\n\n  private async assignTenantsToCourses(): Promise<void> {\n    if(await this.customDialogService.showAssignTenantsToCoursesDialog({ courses: this.selectedCourses })) {\n      this.tableRefreshVersion++;\n    }\n  }\n\n  @computedFrom('selectedCourses.length')\n  private get archiveTextTK(): string {\n    if(this.selectedCourses.every(course => course.isArchived)) {\n      return 'globals.unarchive';\n    }\n    if(this.selectedCourses.every(course => !course.isArchived)) {\n      return 'globals.archiveAction';\n    }\n\n    return 'globals.toggleMixedUp';\n  }\n\n  @computedFrom('selectedCourses.length')\n  private get archivedToggleDisabled(): boolean {\n    return this.selectedCourses.length === 0 || !this.selectedCourses.every((course, _idx, courses) => course.isArchived === courses[0].isArchived);\n  }\n}\n","import { autoinject } from 'aurelia-framework';\nimport { Router } from 'aurelia-router';\nimport { ICourseEditResponseDto, ICreateCourseDto, IPageEditResponseDto, IUpdateCourseDto } from 'common/dto/course/management/course.management.interface';\nimport { ErrorType } from 'common/enum/errortype.enum';\nimport { CustomDialogService } from '../../../services/CustomDialogService';\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 { ContentEditorPages, PageTypeFilter } from '../../elements/content-editor-pages/content-editor-pages';\nimport { CourseSavedEvent } from '../../elements/course-edit-form/course-edit-form';\nimport { INavigationTab } from '../../elements/navigation-tabs/navigation-tabs';\n\nexport enum SubPages {\n  Settings = 'settings',\n  Structure = 'structure',\n  Questions = 'questions',\n  MediaLibrary = 'media-library',\n  MediaShares = 'media-shares',\n  Ratings = 'ratings',\n}\n\ninterface IParams {\n  id: string;\n  subPage?: SubPages;\n}\n\nconst OperationContext = Symbol();\n\n@autoinject()\nexport class CoursePage {\n  private id!: number;\n  private course: ICreateCourseDto | null = null;\n  private rating: Pick<ICourseEditResponseDto, 'ratingCount' | 'aggregateRating'> | null = null;\n  private successLottieFileUrl: string | null = null;\n  private successAudioUrl: string | null = null;\n  private moduleCoverLearningUrl: string | null = null;\n  private moduleCoverTrainingUrl: string | null = null;\n  private moduleCoverTestexamUrl: string | null = null;\n\n  private SubPages = SubPages;\n  private activeSubPage: SubPages | null = null;\n  private navigationTabs: INavigationTab[] = [];\n\n  private searchString = '';\n  private searchActive = false;\n  private selectedImportFiles: FileList | null = null;\n  private pageTypeOptions = [PageTypeFilter.LearningOnly, PageTypeFilter.All];\n  private pageTypeFilter = PageTypeFilter.LearningOnly;\n\n  private contentEditorPages?: ContentEditorPages;\n\n  constructor(\n    private courseManagementApiService: CourseManagementApiService,\n    private router: Router,\n    private toastService: ToastService,\n    private courseExportApiService: CourseExportApiService,\n    private courseImportApiService: CourseImportApiService,\n    private exportImportService: ExportImportService,\n    private dialogService: CustomDialogService,\n  ) { }\n\n  protected async activate(params: IParams): Promise<void> {\n    this.id = parseInt(params.id);\n    this.activeSubPage = params.subPage ?? SubPages.Settings;\n    this.updateNavigationTabs();\n    await this.loadCourse();\n  }\n\n  protected detached(): void {\n    this.exportImportService.untrackOperations(OperationContext);\n  }\n\n  private updateNavigationTabs(): void {\n    this.navigationTabs = [\n      {\n        title: { titleTk: 'pages.course.navTabSettings' },\n        active: this.activeSubPage === SubPages.Settings,\n        callback: () => this.router.navigateToRoute('courses/id', { id: this.id, subPage: SubPages.Settings }),\n      },\n      {\n        title: { titleTk: 'pages.course.navTabStructure' },\n        active: this.activeSubPage === SubPages.Structure,\n        callback: () => this.router.navigateToRoute('courses/id', { id: this.id, subPage: SubPages.Structure }),\n      },\n      {\n        title: { titleTk: 'pages.course.navTabMediaLibrary' },\n        active: this.activeSubPage === SubPages.MediaLibrary,\n        callback: () => this.router.navigateToRoute('courses/id', { id: this.id, subPage: SubPages.MediaLibrary }),\n      },\n      {\n        title: { titleTk: 'pages.course.navTabQuestions' },\n        active: this.activeSubPage === SubPages.Questions,\n        callback: () => this.router.navigateToRoute('courses/id', { id: this.id, subPage: SubPages.Questions }),\n      },\n      {\n        title: { titleTk: 'globals.shares' },\n        active: this.activeSubPage === SubPages.MediaShares,\n        callback: () => this.router.navigateToRoute('courses/id', { id: this.id, subPage: SubPages.MediaShares }),\n      },\n      {\n        title: { titleTk: 'pages.course.navTabRatings' },\n        active: this.activeSubPage === SubPages.Ratings,\n        callback: () => this.router.navigateToRoute('courses/id', { id: this.id, subPage: SubPages.Ratings }),\n      },\n    ];\n  }\n\n  private async loadCourse(): Promise<void> {\n    const course = await this.courseManagementApiService.getCourse(this.id);\n    this.successLottieFileUrl = course.successAnimationFileUrl;\n    this.successAudioUrl = course.successAudioUrl;\n    this.moduleCoverLearningUrl = course.moduleCoverLearningUrl;\n    this.moduleCoverTrainingUrl = course.moduleCoverTrainingUrl;\n    this.moduleCoverTestexamUrl = course.moduleCoverTestexamUrl;\n    this.setCourse(course);\n  }\n\n  private setCourse(course: ICourseEditResponseDto): void {\n    this.course = this.createEditDto(course);\n    this.rating = {\n      aggregateRating: course.aggregateRating,\n      ratingCount: course.ratingCount,\n    };\n  }\n\n  private createEditDto(course: ICourseEditResponseDto): ICreateCourseDto {\n    return {\n      accessCodeExpirationInMonths: course.accessCodeExpirationInMonths,\n      defaultDaysToExam: course.defaultDaysToExam,\n      examType: course.examType,\n      gamesAndChatStatus: course.gamesAndChatStatus,\n      language: course.language,\n      learnChaptersInQuizEnabled: course.learnChaptersInQuizEnabled,\n      learningDisabledExplanation: course.learningDisabledExplanation,\n      learningEnabled: course.learningEnabled,\n      mediaPercentageConsideredSeen: course.mediaPercentageConsideredSeen,\n      minDaysToExam: course.minDaysToExam,\n      minPointsToPassAssessment: course.minPointsToPassAssessment,\n      selectedAvatarSetIds: [],\n      textPercentageConsideredRead: course.textPercentageConsideredRead,\n      title: course.title,\n    };\n  }\n\n  private async saveCourseData(event: CourseSavedEvent): Promise<void> {\n    const { course } = event.detail;\n    const dto: IUpdateCourseDto = {\n      accessCodeExpirationInMonths: course.accessCodeExpirationInMonths,\n      defaultDaysToExam: course.defaultDaysToExam,\n      examType: course.examType,\n      language: course.language,\n      learnChaptersInQuizEnabled: course.learnChaptersInQuizEnabled,\n      learningDisabledExplanation: course.learningDisabledExplanation,\n      learningEnabled: course.learningEnabled,\n      gamesAndChatStatus: course.gamesAndChatStatus,\n      mediaPercentageConsideredSeen: course.mediaPercentageConsideredSeen,\n      minDaysToExam: course.minDaysToExam,\n      minPointsToPassAssessment: course.minPointsToPassAssessment ?? undefined,\n      textPercentageConsideredRead: course.textPercentageConsideredRead,\n      title: course.title\n    };\n    try {\n      await this.courseManagementApiService.updateCourse(this.id, dto);\n      this.toastService.genericUpdateSuccess('globals.course');\n      await this.loadCourse();\n    } catch(e) {\n      if(e.message === ErrorType.COURSE_TITLE_ALREADY_IN_USE) {\n        this.toastService.error('pages.course.messages.editCourseTitleAlreadyInUse');\n      } else {\n        this.toastService.genericUpdateError('globals.course');\n      }\n    }\n  }\n\n  private navigateToCourseOverview(): void {\n    this.router.navigateToRoute('courses');\n  }\n\n  private navigateToBlockOverview(): void {\n    this.router.navigateToRoute('block-overview/id', { courseId: this.id });\n  }\n\n  private async exportStructure(): Promise<void> {\n    const operationId = await this.courseExportApiService.exportStructure(this.id);\n    await this.exportImportService.trackExportOperation(OperationContext, operationId);\n    await this.dialogService.showOperationStatusDialog({\n      operationContext: OperationContext,\n      title: { textTk: 'pages.course.structureExport' },\n    });\n    this.exportImportService.untrackOperation(operationId);\n  }\n\n  private async importStructure(): Promise<void> {\n    const file = this.selectedImportFiles?.[0];\n    if(file == null) return;\n\n    const operationId = await this.courseImportApiService.uploadCourseStructure(this.id, file);\n    await this.exportImportService.trackImportOperation(OperationContext, operationId, file.name);\n    await this.dialogService.showOperationStatusDialog({\n      operationContext: OperationContext,\n      title: { textTk: 'pages.course.structureImport' },\n    });\n    this.exportImportService.untrackOperation(operationId);\n    await this.contentEditorPages?.refresh();\n  }\n}\n\nexport interface IPage extends IPageEditResponseDto {\n  children: IPageEditResponseDto[];\n}\n","import { autoinject } from 'aurelia-framework';\nimport { Router } from 'aurelia-router';\nimport { TermsOfUseApiService } from 'common/aurelia-services/TermsOfUseApiService';\nimport { LoginClient } from 'common/enum/auth.enum';\n\n@autoinject()\nexport class DataProtectionPage {\n  private termsOfUseText: string = '';\n\n  constructor(\n    private router: Router,\n    private termsOfUseApiService: TermsOfUseApiService,\n  ) { }\n\n  protected async attached(): Promise<void> {\n    const termsOfUse = await this.termsOfUseApiService.getTermsOfUse(LoginClient.UserFrontendApp);\n    this.termsOfUseText = termsOfUse.text;\n  }\n\n  private navigateBack(): void {\n    this.router.navigateBack();\n  }\n}","import { autoinject, computedFrom } from 'aurelia-framework';\nimport { ILoadingOverlayStatus, LoadingOverlayType } from 'common/aurelia-components/loading-overlay/loading-overlay';\nimport { LectureConferenceService } from 'common/aurelia-services/LectureConferenceService';\nimport { LoadingIndicatorService } from 'common/aurelia-services/LoadingIndicatorService';\nimport { selectFirstChange, StoreService } from 'common/aurelia-services/StoreService';\nimport { CONFERENCE_ACTIVE_MEETING_SYNC_CONTEXT } from 'common/constants/distinar.constants';\nimport { IEventDto } from 'common/dto/tenant/event/tenant.event.interface';\nimport { ContentView } from 'common/enum/distinar.enum';\nimport { isReconnectingSelector } from 'common/store/selectors/distinar.connection.selectors';\nimport { assertNotNullOrUndefined } from 'common/utils/assert';\nimport { NicknameUtils } from 'common/utils/NicknameUtils';\nimport differenceBy from 'lodash/differenceBy';\nimport { v4 as uuidv4 } from 'uuid';\nimport { QuizTemplateApiService } from '../../../services/api/QuizTemplateApiService';\nimport { TenantApiService } from '../../../services/api/TenantApiService';\nimport { CustomDialogService } from '../../../services/CustomDialogService';\nimport { DistinarService } from '../../../services/DistinarService';\nimport { SeparateWindowService } from '../../../services/SeparateWindowService';\nimport { SnackbarService } from '../../../services/SnackbarService';\nimport { setQuizTemplates } from '../../../store/actions';\nimport { IState } from '../../../store/state';\n\n@autoinject()\nexport class DistinarPage {\n  private static MEETING_CONNECTION_SNACKBAR_ID = uuidv4();\n  private static ATTENDEE_JOINED_SNACKBAR_ID = uuidv4();\n  private static ATTENDEE_LEFT_SNACKBAR_ID = uuidv4();\n  private ContentView = ContentView;\n\n  private audioElement?: HTMLAudioElement;\n\n  private isDistinarPaused: boolean | null = null;\n  private currentContentView: ContentView | null = null;\n  private activeLecture: IEventDto<string> | null = null;\n  private tenantId: number | null = null;\n\n  private tenantLogoPictureUrl: string | null = null;\n  private tenantName: string | null = null;\n\n  private loadingStatusMessages: Array<ILoadingOverlayStatus> = [];\n  private statusMessageInterval: number | null = null;\n  private isSyncingActiveMeeting = false;\n  private isInitializing = true;\n  private isConnecting = false;\n  private hasFailedConnecting = false;\n\n  constructor(\n    private storeService: StoreService<IState>,\n    private quizTemplateApiService: QuizTemplateApiService,\n    private distinarService: DistinarService,\n    private tenantApiService: TenantApiService,\n    private lectureConferenceService: LectureConferenceService,\n    private loadingIndicatorService: LoadingIndicatorService,\n    private snackbarService: SnackbarService,\n    private separateWindowService: SeparateWindowService,\n    private dialogService: CustomDialogService,\n  ) { }\n\n  protected attached(): void {\n    assertNotNullOrUndefined(this.audioElement, 'audioElement cannot be null or undefined');\n    this.lectureConferenceService.bindAudioToElement(this.audioElement);\n    this.separateWindowService.requestClaimMainWindow();\n\n    this.loadingIndicatorService.subscribeToActivity(this, active => {\n      this.isSyncingActiveMeeting = active;\n    }, CONFERENCE_ACTIVE_MEETING_SYNC_CONTEXT);\n\n    this.storeService.subscribeToDetailedChanges(this, [\n      state => state.distinarInitializing,\n      state => state.distinarConnection.connecting,\n      state => state.distinarConnection.failedConnecting,\n      state => isReconnectingSelector({ state }),\n    ], (_state, initializing, connecting, failedConnecting, reconnecting) => {\n      this.isInitializing = initializing;\n      this.isConnecting = connecting;\n      this.hasFailedConnecting = failedConnecting;\n\n      this.updateLoadingStatusMessages(connecting, reconnecting);\n    });\n\n    this.storeService.subscribeToDetailedChanges(this, [\n      state => state.conferenceContext.connectionIsGood,\n      selectFirstChange(),\n    ], (state, connectionIsGood, firstChange) => {\n      if(firstChange) return;\n\n      // we dont want to track active meeting/disconnection/initialization changes but consider them\n      const { hasActiveMeeting } = state.distinar.conference;\n      const { disconnecting, connecting } = state.distinarConnection;\n      const initializing = state.distinarInitializing;\n\n      if(!hasActiveMeeting || initializing || connecting || disconnecting) return;\n\n      if(!connectionIsGood) {\n        this.snackbarService.hideById(DistinarPage.MEETING_CONNECTION_SNACKBAR_ID);\n        this.snackbarService.show(DistinarPage.MEETING_CONNECTION_SNACKBAR_ID, {\n          message: { textTk: 'common.distinar.conferenceConnectionIsPoor' },\n          icon: 'System/Tabler/Alert-Triangle',\n          dismissable: false,\n        });\n      } else if(connectionIsGood) {\n        this.snackbarService.hideById(DistinarPage.MEETING_CONNECTION_SNACKBAR_ID);\n        this.snackbarService.show(DistinarPage.MEETING_CONNECTION_SNACKBAR_ID, {\n          message: { textTk: 'common.distinar.conferenceConnectionIsGood' },\n          icon: 'System/Tabler/Check',\n          autoCloseAfterMs: 3000,\n          dismissable: true\n        });\n      }\n    });\n\n    // subscribe to attendee changes to track online status\n    this.storeService.subscribeToValueChange(this, state => state.distinar.attendees.filter(attendee => attendee.online), (\n      currentlyOnline,\n      previouslyOnline,\n      state,\n    ) => {\n      if(previouslyOnline == null) return;\n\n      const { disconnecting, connecting } = state.distinarConnection;\n      const initializing = state.distinarInitializing;\n      if(initializing || connecting || disconnecting) return;\n\n      const joined = differenceBy(currentlyOnline, previouslyOnline, attendee => attendee.id);\n      const left = differenceBy(previouslyOnline, currentlyOnline, attendee => attendee.id);\n\n      for(const attendee of joined) {\n        assertNotNullOrUndefined(attendee.nickname, 'attendee.nickname cannot be null or undefined');\n\n        this.snackbarService.show(DistinarPage.ATTENDEE_JOINED_SNACKBAR_ID, {\n          message: {\n            textTk: 'pages.distinar.attendeeJoined',\n            tkParams: {\n              userName: NicknameUtils.getFullName(attendee, true, attendee.nickname, true),\n            }\n          },\n          autoCloseAfterMs: 6000,\n          dismissable: true,\n        });\n      }\n\n      for(const attendee of left) {\n        assertNotNullOrUndefined(attendee.nickname, 'attendee.nickname cannot be null or undefined');\n\n        this.snackbarService.show(DistinarPage.ATTENDEE_LEFT_SNACKBAR_ID, {\n          message: {\n            textTk: 'pages.distinar.attendeeLeft',\n            tkParams: {\n              userName: NicknameUtils.getFullName(attendee, true, attendee.nickname, true),\n            }\n          },\n          autoCloseAfterMs: 6000,\n          dismissable: true,\n        });\n\n      }\n    });\n\n    this.storeService.subscribeToDetailedChanges(this, [\n      state => state.distinar.lectureStatus?.paused,\n      state => state.distinar.lectureStatus?.currentContentView,\n      state => state.distinar.activeLecture,\n    ], state => {\n      this.isDistinarPaused = state.distinar.lectureStatus?.paused ?? null;\n      this.currentContentView = state.distinar.lectureStatus?.currentContentView ?? null;\n      this.activeLecture = state.distinar.activeLecture;\n    });\n\n    this.storeService.subscribeToDetailedChanges(this, [\n      state => state.distinar.lectureStatus?.qrCode?.shown\n    ], (_, shouldShowQrCode) => {\n      if(shouldShowQrCode) this.distinarService.showQrCodeDialog();\n    });\n\n    this.storeService.subscribeToDetailedChanges(this, [\n      state => state.currentRole?.tenantId,\n    ], state => {\n      this.tenantId = state.currentRole?.tenantId ?? null;\n      this.updateTenantData();\n    });\n\n    this.updateQuizTemplates();\n  }\n\n  protected detached(): void {\n    this.storeService.disposeAllSubscriptions(this);\n    this.lectureConferenceService.unbindAudioElement();\n    this.loadingIndicatorService.disposeSubscriptions(this);\n  }\n\n  private updateLoadingStatusMessages(connecting: boolean, reconnecting: boolean): void {\n    if(this.statusMessageInterval != null) window.clearInterval(this.statusMessageInterval);\n    if(!connecting) this.loadingStatusMessages = [];\n\n    if(reconnecting) {\n      this.loadingStatusMessages = [{ type: LoadingOverlayType.Info, textTk: 'common.distinar.reconnecting1' }];\n      let i = 0;\n      this.statusMessageInterval = window.setInterval(() => {\n        i++;\n        this.loadingStatusMessages = [{ type: LoadingOverlayType.Info, textTk: `common.distinar.reconnecting${i % 2 !== 0 ? '2' : '1'}` }];\n      }, 2000);\n    } else {\n      this.loadingStatusMessages = [{ type: LoadingOverlayType.Info, textTk: 'common.distinar.connecting' }];\n    }\n  }\n\n  @computedFrom('isInitializing', 'isDistinarPaused', 'currentContentView', 'isSyncingActiveMeeting', 'isConnecting')\n  private get isLoading(): boolean {\n    /**\n     * isSyncingActiveMeeting as set by LectureConferenceServcie ignores Distinar-Paused-State.\n     * This results in isLoading = true, whenever a distinar with an active conference is paused.\n     * Thus isLoading requires \"isDistinarPaused === false\"-check to allow pause-screen to be displayed.\n     */\n    return this.isInitializing\n      || this.isConnecting\n      || ((\n        this.isSyncingActiveMeeting\n        || this.isDistinarPaused == null\n        || this.currentContentView == null\n      ) && (this.isDistinarPaused != null && this.isDistinarPaused === false));\n  }\n\n  @computedFrom('isDistinarPaused')\n  private get isPaused(): boolean {\n    return this.isDistinarPaused === true;\n  }\n\n  private async updateTenantData(): Promise<void> {\n    if(!this.tenantId) return;\n    const { logoPictureUrl, name } = await this.tenantApiService.getTenant(this.tenantId);\n    this.tenantLogoPictureUrl = logoPictureUrl;\n    this.tenantName = name;\n  }\n\n  private async retryConnecting(): Promise<void> {\n    await this.distinarService.reconnect({ navigate: false });\n  }\n\n  private async toHome(): Promise<void> {\n    await this.distinarService.disconnect();\n  }\n\n  private async updateQuizTemplates(): Promise<void> {\n    const quizTemplates = await this.quizTemplateApiService.getQuizTemplates({});\n    await this.storeService.dispatch(setQuizTemplates, quizTemplates);\n  }\n}\n","import { autoinject } from 'aurelia-framework';\n\n@autoinject()\nexport class DistinarSeparateConferenceWindow { }\n","import { autoinject, computedFrom } from 'aurelia-framework';\nimport { FullscreenableContainer } from 'common/aurelia-components/fullscreenable-container/fullscreenable-container';\nimport { StoreService } from 'common/aurelia-services/StoreService';\nimport { ILectureStatusDto } from 'common/dto/distinar/distinar.interface';\nimport { IEventDto } from 'common/dto/tenant/event/tenant.event.interface';\nimport { ContentView } from 'common/enum/distinar.enum';\nimport { selectContentShareAttendeeId, selectContentShareHasTile } from 'common/store/selectors/conference.context.selectors';\nimport { assertNotNullOrUndefined } from 'common/utils/assert';\nimport { DomUtils } from 'common/utils/DomUtils';\nimport { FullscreenUtils } from 'common/utils/FullscreenUtils';\nimport { FunctionUtils } from 'common/utils/FunctionUtils';\nimport { IState } from '../../../../store/state';\n\n@autoinject()\nexport class DistinarSeparateStreamWindow {\n  public static MOUSE_MOVED_RECENTLY_TIMEOUT = 1000;\n\n  private distinar: IEventDto<string> | null = null;\n  private status: ILectureStatusDto | null = null;\n  private reconnecting = false;\n  private currentContentView: ContentView | null = null;\n\n  private contentShareAttendeeId: string | null = null;\n\n  private mouseMovedRecently = false;\n  private mouseInsideControlElement = false;\n  private debouncedResetMouseMoved = FunctionUtils.debounce(() => { this.mouseMovedRecently = false; }, DistinarSeparateStreamWindow.MOUSE_MOVED_RECENTLY_TIMEOUT);\n  private fullscreenableContainer: FullscreenableContainer | null = null;\n  private isFullscreen = false;\n\n  private ContentView = ContentView;\n\n  constructor(\n    private storeService: StoreService<IState>,\n  ) { }\n\n  protected attached(): void {\n    this.storeService.subscribeToDetailedChanges(this, [\n      state => state.distinar.activeLecture,\n      state => state.distinar.lectureStatus,\n    ], (_, distinar, status) => {\n      this.distinar = distinar;\n      this.status = status;\n      this.currentContentView = status?.currentContentView ?? null;\n    });\n\n    this.storeService.subscribeToDetailedChanges(this, [\n      state => selectContentShareAttendeeId({ state }),\n      state => selectContentShareHasTile({ state }),\n    ], (_state, attendeeId, hasTile) => {\n      this.contentShareAttendeeId = hasTile ? attendeeId : null;\n    });\n  }\n\n  protected detached(): void {\n    this.storeService.disposeAllSubscriptions(this);\n    this.contentShareAttendeeId = null;\n  }\n\n  @computedFrom('reconnecting', 'status', 'distinar', 'currentContentView')\n  private get isLoading(): boolean {\n    return this.reconnecting\n      || this.distinar == null\n      || this.status == null\n      || this.currentContentView == null;\n  }\n\n  @computedFrom('reconnecting', 'status')\n  private get isConferenceActive(): boolean {\n    return !!(!this.reconnecting || this.status?.conference.active);\n  }\n\n  @computedFrom('distinar')\n  private get isAboutToStart(): boolean {\n    return this.distinar?.actualDateFrom == null;\n  }\n\n  @computedFrom('distinar')\n  private get hasEnded(): boolean {\n    return this.distinar?.actualDateTo != null;\n  }\n\n  @computedFrom('status', 'isAboutToStart', 'hasEnded')\n  private get isPaused(): boolean {\n    return !!this.status?.paused\n      && !this.isAboutToStart\n      && !this.hasEnded;\n  }\n\n  @computedFrom('isLoading', 'isAboutToStart', 'hasEnded', 'isPaused', 'contentShareAttendeeId')\n  private get showContentView(): boolean {\n    return !this.isLoading\n      && !this.isAboutToStart\n      && !this.hasEnded\n      && !this.isPaused\n      && this.contentShareAttendeeId == null;\n  }\n\n  @computedFrom()\n  private get supportsFullscreen(): boolean {\n    return FullscreenUtils.isEnabled;\n  }\n\n  @computedFrom('isFullscreen')\n  private get fullscreenToggleIconName(): string {\n    if(this.isFullscreen) return 'System/Tabler/Minimize';\n    return 'System/Tabler/Maximize';\n  }\n\n  private handleMousemove(event: MouseEvent): boolean {\n    assertNotNullOrUndefined(event.target, 'event without a target in PresentationSlide.handleMousemove');\n    this.mouseInsideControlElement = this.elementIsInsideControlElement(event.target as HTMLElement);\n\n    this.mouseMovedRecently = true;\n    this.debouncedResetMouseMoved();\n    return true;\n  }\n\n  private elementIsInsideControlElement(element: HTMLElement): boolean {\n    return DomUtils.isInsideElementWithDataAttribute(element, 'controlElement');\n  }\n\n  private onToggleFullscreen(): void {\n    if(this.isFullscreen) {\n      this.fullscreenableContainer?.exitFullscreen();\n    } else {\n      this.fullscreenableContainer?.requestFullscreen();\n    }\n  }\n}\n","import { autoinject } from 'aurelia-framework';\nimport { StoreService } from 'common/aurelia-services/StoreService';\nimport { DistinarService } from 'services/DistinarService';\nimport { SeparateWindowService, SeparateWindowType } from 'services/SeparateWindowService';\nimport { IState } from 'store/state';\n\n@autoinject()\nexport class DistinarSeparateWindow {\n  private type: SeparateWindowType | null = null;\n  private SeparateWindowType = SeparateWindowType;\n\n  constructor(\n    private separateWindowService: SeparateWindowService,\n    private storeService: StoreService<IState>,\n    private distinarService: DistinarService,\n  ) { }\n\n  protected activate(): void {\n    this.type = this.separateWindowService.getSeparateWindowType();\n  }\n\n  protected attached(): void {\n    this.storeService.subscribeToDetailedChanges(this, [\n      state => state.distinar.lectureStatus?.qrCode?.shown,\n    ], (_, shouldShowQrCode) => {\n      if(shouldShowQrCode) this.distinarService.showQrCodeDialog();\n    });\n  }\n\n  protected detached(): void {\n    this.storeService.disposeAllSubscriptions(this);\n  }\n}\n","import { FrameworkConfiguration, PLATFORM } from 'aurelia-framework';\n\nexport function configure(config: FrameworkConfiguration): void {\n  config.globalResources(PLATFORM.moduleName('resources/pages/distinar-separate-window/distinar-separate-window'));\n  config.globalResources(PLATFORM.moduleName('resources/pages/distinar-separate-window/distinar-separate-conference-window/distinar-separate-conference-window'));\n  config.globalResources(PLATFORM.moduleName('resources/pages/distinar-separate-window/distinar-separate-stream-window/distinar-separate-stream-window'));\n}\n","import { autoinject, computedFrom } from 'aurelia-framework';\nimport { Router } from 'aurelia-router';\nimport { StoreService } from 'common/aurelia-services/StoreService';\nimport { IUserData, } from 'common/dto/auth/auth.interface';\nimport { ITenantUserInfoDto } from 'common/dto/tenant/other/tenant.user.info.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 { ToastService } from '../../../services/ToastService';\nimport { TenantApiService } from '../../../services/api/TenantApiService';\nimport { IState } from '../../../store/state';\nimport { UserForm } from '../user-editor-page/user-form';\n\nenum ParamIds {\n  NewPowerUser = 'new-power-user',\n  NewLecturer = 'new-lecturer',\n}\n\n@autoinject()\nexport class EmployeePage {\n  private userData!: IUserData<string> | ITenantUserInfoDto;\n  private tenantId: number | null = null;\n  private userId: number | null = null;\n  private newEmployeeRoleName: RoleName.PowerUser | RoleName.Lecturer | null = null;\n  private userForm: UserForm | null = null;\n\n  constructor(\n    private tenantApiService: TenantApiService,\n    private router: Router,\n    private toastService: ToastService,\n    private storeService: StoreService<IState>,\n  ) { }\n\n  protected async activate(params: Record<string, ParamIds | string>): Promise<void> {\n    this.storeService.subscribeToDetailedChanges(this, [\n      state => state.currentRole?.tenantId ?? null\n    ], (_state, tenantId) => {\n      this.tenantId = tenantId;\n    });\n    this.userId = 0;\n    if(params.id === ParamIds.NewLecturer || params.id === ParamIds.NewPowerUser) {\n      this.userData = this.getEmptyUserData(params.email || '');\n      this.newEmployeeRoleName = params.id === ParamIds.NewLecturer ? RoleName.Lecturer : RoleName.PowerUser;\n      return;\n    }\n    this.userId = parseInt(params.id);\n    this.userData = await this.tenantApiService.getUserData(this.userId);\n  }\n\n  protected deactivate(): void {\n    this.storeService.disposeAllSubscriptions(this);\n  }\n\n  @computedFrom('userId')\n  private get isEditMode(): boolean {\n    return (this.userId != null) && this.userId > 0;\n  }\n\n  @computedFrom('newEmployeeRoleName', 'isEditMode')\n  private get pageTitleTk(): string {\n    if(this.isEditMode) return 'pages.employee.title';\n    else if(this.newEmployeeRoleName === RoleName.PowerUser) return 'pages.employee.newPowerUserTitle';\n    return 'pages.employee.newLecturerTitle';\n  }\n\n  @computedFrom('newEmployeeRoleName', 'isEditMode')\n  private get submitTk(): string {\n    if(this.isEditMode) return 'globals.save';\n    else if(this.newEmployeeRoleName === RoleName.PowerUser) return 'pages.employee.form.addPowerUser';\n    return 'pages.employee.form.addLecturer';\n  }\n\n  private async saveOrCreate(): Promise<void> {\n    if(!this.tenantId) return;\n\n    if(this.checkUserData()) {\n      if(this.isEditMode) await this.saveUserData();\n      else if(this.newEmployeeRoleName === RoleName.Lecturer) await this.createLecturer(this.tenantId);\n      else await this.createPowerUser(this.tenantId);\n    }\n  }\n\n  private async createLecturer(tenantId: number): Promise<void> {\n    try {\n      await this.tenantApiService.createLecturer(tenantId, this.userData);\n      this.toastService.genericCreateSuccess('globals.lecturer');\n      this.router.navigateToRoute('employees');\n    } catch(e) {\n      if(e.message === ErrorType.INVALID_EMAIL) {\n        this.toastService.error('common.validationUtils.emailInvalidError');\n      } else {\n        this.toastService.genericCreateError('globals.lecturer');\n        LogUtils.exception(e);\n      }\n    }\n  }\n\n  private async createPowerUser(tenantId: number): Promise<void> {\n    try {\n      await this.tenantApiService.createPowerUser(tenantId, this.userData);\n      this.toastService.genericCreateSuccess('globals.poweruser');\n      this.router.navigateToRoute('employees');\n    } catch(e) {\n      if(e.message === ErrorType.INVALID_EMAIL) {\n        this.toastService.error('common.validationUtils.emailInvalidError');\n      } else {\n        this.toastService.genericCreateError('globals.poweruser');\n        LogUtils.exception(e);\n      }\n    }\n  }\n\n  private async saveUserData(): Promise<void> {\n    try {\n      if(!('id' in this.userData)) throw new Error('Cannot update user without id');\n      assertNotNullOrUndefined(this.userData.id, 'userData.id cannot be null or undefined');\n\n      const dto = {\n        email: this.userData.email,\n        phoneNumber: this.userData.phoneNumber,\n        firstname: this.userData.firstname,\n        lastname: this.userData.lastname,\n        titlePrefix: this.userData.titlePrefix,\n        titleSuffix: this.userData.titleSuffix,\n      };\n      await this.tenantApiService.updateUserData(this.userData.id, dto);\n      this.toastService.genericUpdateSuccess('globals.user');\n      this.router.navigateToRoute('employees');\n    } catch(e) {\n      if(e.message === ErrorType.PHONE_NUMBER_EXISTS) {\n        this.toastService.error('common.validationUtils.phoneNumberExistsError');\n      } else if(e.message === ErrorType.USER_EXISTS) {\n        this.toastService.error('common.validationUtils.emailExistsError');\n      } else if(e.message === ErrorType.INVALID_EMAIL) {\n        this.toastService.error('common.validationUtils.emailInvalidError');\n      } else {\n        this.toastService.genericUpdateError('globals.user');\n        LogUtils.exception(e);\n      }\n    }\n  }\n\n  private checkUserData(): boolean {\n    return this.userForm?.validate() ?? false;\n  }\n\n  private getEmptyUserData(email: string): IUserData<string> {\n    return {\n      email,\n      firstname: '',\n      lastname: '',\n    };\n  }\n}\n","import { autoinject } from 'aurelia-framework';\nimport { I18N } from 'aurelia-i18n';\nimport { Router } from 'aurelia-router';\nimport { StoreService } from 'common/aurelia-services/StoreService';\nimport { ITenantDto, ITenantDtoLecturer } from 'common/dto/tenant/management/tenant.management.interface';\nimport { ITenantUserInfoDto } from 'common/dto/tenant/other/tenant.user.info.interface';\nimport { ErrorType } from 'common/enum/errortype.enum';\nimport { RoleName } from 'common/enum/role.enums';\nimport { Config } from 'common/fe-config/config';\nimport { assertNotNullOrUndefined } from 'common/utils/assert';\nimport { LogUtils } from 'common/utils/LogUtils';\nimport { TenantApiService } from '../../../services/api/TenantApiService';\nimport { CustomDialogService } from '../../../services/CustomDialogService';\nimport { ToastService } from '../../../services/ToastService';\nimport { IState } from '../../../store/state';\nimport { ActionsCell } from '../../elements/custom-table/actions-cell/actions-cell';\nimport { CustomTableCell } from '../../elements/custom-table/custom-table-cell/custom-table-cell';\nimport { deepPropertyFilter, propertySorter } from '../../elements/custom-table/custom-table-helpers';\nimport { IColumnDefinition } from '../../elements/custom-table/custom-table-interfaces';\n\n@autoinject()\nexport class EmployeesPage {\n  private tenantId: number | null = null;\n  private tenant: ITenantDto<string> | ITenantDtoLecturer | null = null;\n\n  private tableColumns: Array<IColumnDefinition<CustomTableCell<ITenantUserInfoDto> | ActionsCell<ITenantUserInfoDto>>> = [\n    {\n      id: 'firstname',\n      nameTk: 'globals.firstname',\n      cellViewModel: CustomTableCell,\n      cellOptions: {\n        transformer: e => e.firstname ?? this.i18n.tr('globals.empty'),\n      },\n      sortFunction: propertySorter('firstname'),\n      filter: {\n        placeholderTk: 'globals.filter',\n        filterCallback: deepPropertyFilter('firstname'),\n      }\n    },\n    {\n      id: 'lastname',\n      nameTk: 'globals.lastname',\n      cellViewModel: CustomTableCell,\n      cellOptions: {\n        transformer: e => e.lastname ?? this.i18n.tr('globals.empty'),\n      },\n      sortFunction: propertySorter('lastname'),\n      filter: {\n        placeholderTk: 'globals.filter',\n        filterCallback: deepPropertyFilter('lastname'),\n      }\n    },\n    {\n      id: 'invitationStatus',\n      nameTk: '',\n      cellViewModel: CustomTableCell,\n      cellOptions: {\n        tk: user => user.pending ? 'pages.employees.pending' : '',\n        spanClasses: 'tag'\n      },\n    },\n    {\n      id: 'permission',\n      nameTk: 'globals.permission',\n      cellViewModel: CustomTableCell,\n      cellOptions: {\n        transformer: user => user.role,\n        tk: user => `globals.${user.role.toLowerCase()}`\n      },\n      sortFunction: propertySorter('role'),\n      filter: {\n        placeholderTk: 'globals.filter',\n        filterCallback: deepPropertyFilter('role'),\n        options: [\n          { key: this.i18n.tr('globals.operator'), value: RoleName.Operator },\n          { key: this.i18n.tr('globals.poweruser'), value: RoleName.PowerUser },\n          { key: this.i18n.tr('globals.lecturer'), value: RoleName.Lecturer },\n        ]\n      }\n    },\n    {\n      id: 'status',\n      nameTk: 'globals.status',\n      cellViewModel: CustomTableCell,\n      cellOptions: {\n        tk: user => user.enabled ? 'globals.active' : 'globals.inactive',\n        spanClasses: user => user.enabled ? '' : 'text-danger'\n      },\n      sortFunction: propertySorter('role'),\n      filter: {\n        initialValue: Config().defaultTableFilter.enabled ? '1' : '',\n        placeholderTk: 'globals.filter',\n        filterCallback: (user, value) => user.enabled === (value === '1'),\n        options: [\n          { key: this.i18n.tr('globals.active'), value: '1' },\n          { key: this.i18n.tr('globals.inactive'), value: '0' }\n        ]\n      }\n    },\n    {\n      id: 'actions',\n      nameTk: '',\n      shrink: true,\n      cellViewModel: ActionsCell,\n      cellOptions: {\n        entries: [\n          {\n            labelTk: 'globals.edit',\n            icon: 'fe-edit',\n            clickCallback: user => { this.router.navigateToRoute('employees/id', { id: user.id }); },\n            showCallback: user => user.role === RoleName.Operator || user.pending === false\n          },\n          {\n            icon: 'fe-activity',\n            labelTk: user => user.enabled ? 'globals.deactivate' : 'globals.activate',\n            clickCallback: user => { this.setUserEnabled(user, !user.enabled); },\n            showCallback: user => user.role === RoleName.PowerUser\n          },\n          'divider',\n          {\n            labelTk: 'globals.remove',\n            icon: 'fe-delete',\n            additionalClasses: 'text-danger',\n            clickCallback: user => { this.unlinkUser(user); },\n            showCallback: user => user.role === RoleName.PowerUser || user.role === RoleName.Lecturer\n          }\n        ]\n      }\n    }\n  ];\n\n  constructor(\n    private tenantApiService: TenantApiService,\n    private toastService: ToastService,\n    private i18n: I18N,\n    private router: Router,\n    private customDialogService: CustomDialogService,\n    private storeService: StoreService<IState>,\n  ) { }\n\n  protected async attached(): Promise<void> {\n    this.storeService.subscribeToDetailedChanges(this, [\n      state => state.currentRole?.tenantId,\n    ], state => {\n      this.tenantId = state.currentRole?.tenantId ?? null;\n      this.loadData();\n    });\n  }\n\n  protected detached(): void {\n    this.storeService.disposeAllSubscriptions(this);\n  }\n\n  private async loadData(): Promise<void> {\n    if(!this.tenantId) return;\n    this.tenant = await this.tenantApiService.getTenant(this.tenantId);\n  }\n\n  private isUserOperator(user: ITenantUserInfoDto): boolean {\n    return user.role === RoleName.Operator;\n  }\n\n  private async setUserEnabled(user: ITenantUserInfoDto, enabled: boolean): Promise<void> {\n    assertNotNullOrUndefined(this.tenant?.id, 'tenant.id cannot be null or undefined');\n    await this.tenantApiService.setTenantUserStatus(this.tenant.id, user.id, enabled);\n    await this.loadData();\n  }\n\n  private async unlinkUser(user: ITenantUserInfoDto): Promise<void> {\n    assertNotNullOrUndefined(this.tenantId, 'tenantId cannot be null or undefined');\n\n    await this.tenantApiService.unlinkUser(this.tenantId, user.id);\n    this.toastService.success(\n      `pages.employees.notifications.${user.role === RoleName.Lecturer ? 'unlinkLecturerSuccess' : 'unlinkPowerUserSuccess'}`,\n      { name: `${user.firstname ?? ''} ${user.lastname ?? ''}`.trim() || user.email }\n    );\n    await this.loadData();\n  }\n\n  private async linkPowerUser(): Promise<void> {\n    this.linkRole(RoleName.PowerUser);\n  }\n\n  private async linkLecturer(): Promise<void> {\n    this.linkRole(RoleName.Lecturer);\n  }\n\n  private async linkRole(roleName: RoleName.PowerUser | RoleName.Lecturer): Promise<void> {\n    assertNotNullOrUndefined(this.tenantId, 'tenantId cannot be null or undefined');\n    const email = await this.customDialogService.promptEmail({\n      labelTk: `pages.employees.dialogs.link${roleName}`,\n      placeholderTk: 'globals.email'\n    });\n    if(!email) return;\n    try {\n      if(roleName === RoleName.PowerUser) {\n        await this.tenantApiService.linkPowerUser(this.tenantId, email);\n      } else {\n        await this.tenantApiService.linkLecturer(this.tenantId, email);\n      }\n      await this.loadData();\n      this.toastService.success('pages.employees.notifications.linkUserInviteSent', { email });\n      return;\n    } catch(e) {\n      if(e.message === ErrorType.USER_EXISTS_AS_STUDENT) {\n        this.toastService.error('common.validationUtils.userExistsAsStudentError');\n        return;\n      }\n      if(e.message === ErrorType.USER_ALREADY_BELONGS_TO_TENANT) {\n        this.toastService.error('pages.employees.dialogs.userExistsAsAdministratorError');\n        return;\n      }\n      if(e.message === ErrorType.USER_NOT_FOUND) {\n        this.router.navigateToRoute('employees/id', { id: `new-${roleName === RoleName.Lecturer ? 'lecturer' : 'power-user'}`, email });\n        return;\n      }\n      LogUtils.exception(e);\n      this.toastService.error(`pages.employees.dialogs.link${roleName}Error`);\n      return;\n    }\n  }\n}\n","export class LinkErrorPage {\n  private code = '';\n  private titleTk = 'pages.error.sorry';\n  private messageTk = 'pages.error.unknown';\n\n  private errors: Record<string, { code: string; titleTk: string; messageTk: string }> = {\n    'ERR_UNKNOWN': {\n      code: '500',\n      titleTk: 'pages.error.sorry',\n      messageTk: 'pages.error.unknown'\n    },\n    'ERR_LOGIN_PROVIDER_IN_USE': {\n      code: '500',\n      titleTk: 'pages.error.link.title',\n      messageTk: 'pages.error.link.providerInUse'\n    }\n  };\n\n  public async activate(params: Record<string, string>): Promise<void> {\n    const error = { ...this.errors['ERR_UNKNOWN'], ...this.errors[params.error] };\n    this.code = error.code;\n    this.titleTk = error.titleTk;\n    this.messageTk = error.messageTk;\n  }\n}\n","export class LoginErrorPage {\n}\n","export class NotFoundPage {}\n"],"names":[],"sourceRoot":""}