{"version":3,"file":"app-41373ea2.f584a1446b2fc034d810.bundle.js","mappings":"AAGA","sources":["webpack://lernplattform-admin-frontend/./src/resources/elements/content-editor-page-editor/content-editor-page-editor.html","webpack://lernplattform-admin-frontend/./src/resources/elements/content-editor-pages-sidebar/content-editor-pages-sidebar.html","webpack://lernplattform-admin-frontend/./src/resources/elements/content-editor-pages/content-editor-pages.html","webpack://lernplattform-admin-frontend/./src/resources/elements/content-editor-sidebar-right/content-editor-sidebar-block-clipboard/content-editor-sidebar-block-clipboard.html","webpack://lernplattform-admin-frontend/./src/resources/elements/content-editor-sidebar-right/content-editor-sidebar-block-select/content-editor-sidebar-block-select.html","webpack://lernplattform-admin-frontend/./src/resources/elements/content-editor-sidebar-right/content-editor-sidebar-blocks/content-editor-sidebar-blocks.html","webpack://lernplattform-admin-frontend/./src/resources/elements/content-editor-sidebar-right/content-editor-sidebar-right.html","webpack://lernplattform-admin-frontend/./src/resources/elements/content-editor-sidebar-right/content-editor-sidebar-templates/content-editor-sidebar-templates.html","webpack://lernplattform-admin-frontend/./src/resources/elements/content-editor-slide-selection/content-editor-slide-selection.html","webpack://lernplattform-admin-frontend/./src/resources/elements/content-editor-template-preview/content-editor-template-preview.html","webpack://lernplattform-admin-frontend/./src/resources/elements/content-editor-template-preview/template-preview-layout-container/template-preview-layout-container.html","webpack://lernplattform-admin-frontend/./src/resources/elements/course-and-group-list-item/course-and-group-list-item.html","webpack://lernplattform-admin-frontend/./src/resources/elements/course-and-group-select/course-and-group-select.html","webpack://lernplattform-admin-frontend/./src/resources/elements/content-editor-page-editor/content-editor-page-editor.ts","webpack://lernplattform-admin-frontend/./src/resources/elements/content-editor-page-editor/index.ts","webpack://lernplattform-admin-frontend/./src/resources/elements/content-editor-pages-sidebar/content-editor-pages-sidebar.ts","webpack://lernplattform-admin-frontend/./src/resources/elements/content-editor-pages/content-editor-pages.ts","webpack://lernplattform-admin-frontend/./src/resources/elements/content-editor-sidebar-right/content-editor-sidebar-block-clipboard/content-editor-sidebar-block-clipboard.ts","webpack://lernplattform-admin-frontend/./src/resources/elements/content-editor-sidebar-right/content-editor-sidebar-block-select/content-editor-sidebar-block-select.ts","webpack://lernplattform-admin-frontend/./src/resources/elements/content-editor-sidebar-right/content-editor-sidebar-blocks/content-editor-sidebar-blocks.ts","webpack://lernplattform-admin-frontend/./src/resources/elements/content-editor-sidebar-right/content-editor-sidebar-right.ts","webpack://lernplattform-admin-frontend/./src/resources/elements/content-editor-sidebar-right/content-editor-sidebar-templates/content-editor-sidebar-templates.ts","webpack://lernplattform-admin-frontend/./src/resources/elements/content-editor-sidebar-right/index.ts","webpack://lernplattform-admin-frontend/./src/resources/elements/content-editor-slide-selection/content-editor-slide-selection.ts","webpack://lernplattform-admin-frontend/./src/resources/elements/content-editor-slide-selection/index.ts","webpack://lernplattform-admin-frontend/./src/resources/elements/content-editor-template-preview/content-editor-template-preview.ts","webpack://lernplattform-admin-frontend/./src/resources/elements/content-editor-template-preview/index.ts","webpack://lernplattform-admin-frontend/./src/resources/elements/content-editor-template-preview/template-preview-layout-container/template-preview-layout-container.ts","webpack://lernplattform-admin-frontend/./src/resources/elements/course-and-group-list-item/course-and-group-list-item.ts","webpack://lernplattform-admin-frontend/./src/resources/elements/course-and-group-select/course-and-group-select.ts"],"sourcesContent":["// Module\nvar code = \"<template class=\\\"content-editor-page-editor\\\"> <course-learn-page-content if.to-view=\\\"pageId != null && !isIas\\\" class=\\\"content-editor-page-editor__content\\\" context-identifier.to-view=\\\"contextIdentifier\\\" page-id.to-view=\\\"pageId\\\" hide-page-content.to-view=\\\"dndTemplateActive\\\" layout-container-view-model.to-view=\\\"layoutContainerViewModel\\\" header-view-model.to-view=\\\"headerViewModel\\\"> <drop-zone if.to-view=\\\"isPageEmpty || dndTemplateActive\\\" slot=\\\"additional\\\" class=\\\"content-editor-page-editor__empty-page\\\" context.to-view=\\\"dndContext\\\" dnd-active.to-view=\\\"dndActive\\\" config.to-view=\\\"firstDropzoneConfig\\\" provide-dropzone-identifiers.to-view=\\\"true\\\"> <div if.to-view=\\\"dndEmptyPageTargeted\\\" class=\\\"content-editor-page-editor__add-first\\\"> <safe-icon class=\\\"content-editor-page-editor__add-first-icon\\\" icon-name=\\\"System/Tabler/Plus\\\"></safe-icon> </div> </drop-zone> <drop-zone if.to-view=\\\"!isPageEmpty && dndActive && !dndTemplateActive\\\" slot=\\\"additional\\\" class=\\\"content-editor-page-editor__lower-dropzone\\\" context.to-view=\\\"dndContext\\\" dnd-active.to-view=\\\"dndActive\\\" config.to-view=\\\"lastDropzoneConfig\\\" provide-dropzone-identifiers.to-view=\\\"true\\\"> </drop-zone> </course-learn-page-content> <template if.to-view=\\\"isIas\\\"> <uce-ias-editor class=\\\"content-editor-page-editor__ias-editor\\\"></uce-ias-editor> </template> <rich-text-editor-toolbar context-identifier.to-view=\\\"contextIdentifier\\\" class=\\\"content-editor-page-editor__text-editor-toolbar ${ isIas ? 'content-editor-page-editor__text-editor-toolbar--ias' : '' }\\\"></rich-text-editor-toolbar> <div click.delegate=\\\"navigateBack()\\\" class=\\\"content-editor-page-editor__nav-course ${ !navigationInfo.hasPreviousPage ? 'content-editor-page-editor__nav-course--disabled' : '' }\\\"> <safe-icon icon-name=\\\"System/Tabler/Chevron-Left\\\" class=\\\"content-editor-page-editor__nav-icon\\\"></safe-icon> </div> <div click.delegate=\\\"navigateForward()\\\" class=\\\"content-editor-page-editor__nav-course content-editor-page-editor__nav-course--right ${ !navigationInfo.hasNextPage ? 'content-editor-page-editor__nav-course--disabled' : '' }\\\"> <safe-icon icon-name=\\\"System/Tabler/Chevron-Right\\\" class=\\\"content-editor-page-editor__nav-icon\\\"></safe-icon> </div> </template>\";\n// Exports\nexport default code;","// Module\nvar code = \"<template class=\\\"content-editor-pages-sidebar ${ open ? 'content-editor-pages-sidebar--open' : '' }\\\"> <div class=\\\"content-editor-pages-sidebar__overlay\\\" click.delegate=\\\"toggleOpen()\\\"> </div> <div class=\\\"content-editor-pages-sidebar__inner\\\"> <div class=\\\"content-editor-pages-sidebar__header\\\"> <div class=\\\"content-editor-pages-sidebar__header-content\\\"> <h6 class=\\\"content-editor-pages-sidebar__header-title\\\" t=\\\"pages.content-editor.pagesSideBarHeaderTitle\\\"></h6> <search-input class=\\\"content-editor-pages-sidebar__header-search\\\" t=\\\"[placeholder]pages.content-editor.searchPlaceholder\\\" search-string.from-view=\\\"searchString\\\" search-active.from-view=\\\"searchActive\\\"></search-input> <select class=\\\"content-editor-pages-sidebar__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> <div class=\\\"content-editor-pages-sidebar__content\\\"> <content-editor-pages if.to-view=\\\"courseId\\\" class=\\\"content-editor-pages-sidebar__pages\\\" context-identifier.to-view=\\\"contextIdentifier\\\" course-id.to-view=\\\"courseId\\\" page-id.to-view=\\\"pageId\\\" page-selected.trigger=\\\"toggleOpen()\\\" type-filter.to-view=\\\"pageTypeFilter\\\" search-string.to-view=\\\"searchString\\\" search-active.to-view=\\\"searchActive\\\"></content-editor-pages> </div> <div class=\\\"content-editor-pages-sidebar__handle\\\" click.delegate=\\\"toggleOpen()\\\"> <safe-icon class=\\\"content-editor-pages-sidebar__handle-icon\\\" icon-name=\\\"${ open ? 'System/Chevron-Left' : 'System/Chevron-Right' }\\\"></safe-icon> <h6 class=\\\"content-editor-pages-sidebar__handle-title\\\" t=\\\"${ open ? 'globals.close' : 'pages.content-editor.pagesSideBarTitle' }\\\"></h6> </div> </div> </template>\";\n// Exports\nexport default code;","// Module\nvar code = \"<template class=\\\"content-editor-pages ${ searchActive ? 'content-editor-pages--search-active' : '' }\\\"> <separator if.to-view=\\\"!dndPayload && !searchActive\\\" text-tk=\\\"pages.content-editor.addPageOrChapter\\\" click.delegate=\\\"createPage(null, 0)\\\" class=\\\"content-editor-pages__separator ${ !viewData.rows.length ? 'content-editor-pages__separator--show' : '' }\\\"></separator> <drop-zone if.to-view=\\\"!!dndPayload && !searchActive\\\" config.to-view=\\\"firstDropzoneConfig\\\" context.to-view=\\\"dndContext\\\" class=\\\"content-editor-pages__dnd-target ${ getFirstTargetDisplayModifier(firstTargetDisplayMode) }\\\"></drop-zone> <page-row repeat.for=\\\"row of viewDataRows\\\" dnd-active.to-view=\\\"dndActive\\\" dnd-context.to-view=\\\"dndContext\\\" as-simple-list-element.to-view=\\\"searchActive\\\" page-id.to-view=\\\"pageId\\\" course-id.to-view=\\\"courseId\\\" max-layer.to-view=\\\"viewData.maxLayer\\\" view-data.to-view=\\\"row.element\\\" target-display.to-view=\\\"row.targetDisplay\\\" page-selected.trigger=\\\"handlePageSelectedEvent($event)\\\" create-page.trigger=\\\"handleCreatePageEvent($event)\\\" update-page.trigger=\\\"handleUpdatePageEvent($event)\\\" toggle-collapse.trigger=\\\"handleChapterCollapseEvent($event)\\\" delete-page.call=\\\"deletePage(pageId)\\\"></page-row> </template>\";\n// Exports\nexport default code;","// Module\nvar code = \"<template class=\\\"content-editor-sidebar-block-clipboard\\\"> <p class=\\\"content-editor-sidebar-block-clipboard__block-select-description\\\" t=\\\"pages.content-editor.clipboardMessage\\\"></p> <div if.to-view=\\\"!(dndActiveSourceOutsideClipboard && dndActiveAndClipboardEnabled) && !clipboardEmpty\\\" class=\\\"content-editor-sidebar-block-clipboard__block-select\\\"> <div class=\\\"content-editor-sidebar-block-clipboard__block-wrapper\\\"> <draggable repeat.for=\\\"clipboardElement of clipboardContent\\\" class=\\\"content-editor-sidebar-block-clipboard__block-draggable\\\" payload.to-view=\\\"{\\n                   source: DndSubContext.Clipboard,\\n                   existingPayloadId: clipboardElement.layoutContainerId,\\n                   existingPayloadType: clipboardElement.elementType,\\n                 }\\\" drag-dom-element.to-view=\\\"draggedElements[$index]\\\" dnd-active.to-view=\\\"dndActive\\\" context.to-view=\\\"dndContext\\\" click.trigger=\\\"showPreview(clipboardElement.layoutContainerId)\\\"> <content-editor-dragged-block ref=\\\"draggedElements[$index]\\\" icon-name.to-view=\\\"getIconName(clipboardElement.elementType)\\\" modifier-icon-name.to-view=\\\"getModifierIconName(clipboardElement.elementType)\\\"> </content-editor-dragged-block> <div class=\\\"content-editor-sidebar-block-clipboard__block ${clipboardElement.layoutContainerId === dndPayload.existingPayloadId ? 'content-editor-sidebar-block-clipboard__block--ghost' : ''}\\\"> <x-icon class=\\\"content-editor-sidebar-block-clipboard__block-icon\\\" size=\\\"unspecified\\\" icon-type=\\\"custom\\\" icon-name.to-view=\\\"getIconName(clipboardElement.elementType)\\\"></x-icon> <span class=\\\"content-editor-sidebar-block-clipboard__block-description\\\" t=\\\"pages.content-editor.element-with-index.${clipboardElement.elementType}\\\" t-params.to-view=\\\"{index: clipboardElement.orderWithinType}\\\"></span> </div> </draggable> </div> </div> <drop-zone if.to-view=\\\"clipboardEmpty || (dndActiveAndClipboardEnabled && dndActiveSourceOutsideClipboard)\\\" class=\\\"content-editor-sidebar-block-clipboard__content-dropzone\\\" config.to-view=\\\"dndContentDropzoneConfig\\\" context.to-view=\\\"dndContext\\\" dnd-active.to-view=\\\"dndActive\\\"> <p class=\\\"content-editor-sidebar-block-clipboard__content-dropzone-display ${ dndClipboardContentTargeted ? 'content-editor-sidebar-block-clipboard__content-dropzone-display--targeted' : '' }\\\" t=\\\"pages.content-editor.clipboardDropzoneMessage\\\"></p> </drop-zone> <drop-zone if.to-view=\\\"dndActiveAndClipboardEnabled && dndActiveSourceClipboard\\\" class=\\\"content-editor-sidebar-block-clipboard__trash-dropzone\\\" config.to-view=\\\"dndTrashDropzoneConfig\\\" context.to-view=\\\"dndContext\\\" dnd-active.to-view=\\\"dndActive\\\"> <div class=\\\"content-editor-sidebar-block-clipboard__trash-dropzone-icon-wrapper ${ dndClipboardTrashTargeted ? 'content-editor-sidebar-block-clipboard__trash-dropzone-icon-wrapper--targeted' : '' }\\\"> <x-icon class=\\\"content-editor-sidebar-block-clipboard__trash-icon ${ dndClipboardTrashTargeted ? 'content-editor-sidebar-block-clipboard__trash-icon--targeted' : '' }\\\" size=\\\"unspecified\\\" icon-type=\\\"custom\\\" icon-name=\\\"Uce/Delete\\\"></x-icon> </div> <p class=\\\"h6 content-editor-sidebar-block-clipboard__trash-dropzone-text ${ dndClipboardTrashTargeted ? 'content-editor-sidebar-block-clipboard__trash-dropzone-text--targeted' : '' }\\\" t=\\\"pages.content-editor.dndTrashMessage\\\"></p> </drop-zone> </template> \";\n// Exports\nexport default code;","// Module\nvar code = \"<template class=\\\"content-editor-sidebar-block-select\\\"> <p class=\\\"content-editor-sidebar-block-select__block-select-description\\\" t=\\\"pages.content-editor.blockSelectDescription\\\"></p> <div class=\\\"content-editor-sidebar-block-select__block-select\\\"> <div class=\\\"content-editor-sidebar-block-select__block-wrapper\\\"> <draggable repeat.for=\\\"blockType of availableBlockTypes\\\" class=\\\"content-editor-sidebar-block-select__block-draggable\\\" payload.to-view=\\\"{\\n                   source: DndPayloadSource.New,\\n                   createNewType: blockType,\\n                 }\\\" drag-dom-element.to-view=\\\"draggedBlockElement\\\" dnd-active.to-view=\\\"dndActive\\\" context.to-view=\\\"dndContext\\\"> <content-editor-dragged-block ref=\\\"draggedBlockElement\\\" icon-name.to-view=\\\"getIconName(blockType)\\\" modifier-icon-name.to-view=\\\"getModifierIconName(blockType)\\\"></content-editor-dragged-block> <div class=\\\"content-editor-sidebar-block-select__block ${ blockType === dndPayload.createNewType ? 'content-editor-sidebar-block-select__block--ghost' : ''}\\\"> <safe-icon class=\\\"content-editor-sidebar-block-select__block-icon\\\" icon-name.to-view=\\\"getIconName(blockType)\\\"></safe-icon> <safe-icon class=\\\"content-editor-sidebar-block-select__block-modifier-icon\\\" icon-name.to-view=\\\"getModifierIconName(blockType)\\\"></safe-icon> <span class=\\\"content-editor-sidebar-block-select__block-description\\\" t=\\\"pages.content-editor.block.${blockType}\\\"></span> </div> </draggable> </div> </div> </template> \";\n// Exports\nexport default code;","// Module\nvar code = \"<template class=\\\"content-editor-sidebar-blocks\\\"> <navigation-tabs class=\\\"content-editor-sidebar-blocks__nav-header\\\" tabs.to-view=\\\"navigationTabs\\\" fill-width.to-view=\\\"true\\\" fill-height.to-view=\\\"true\\\" no-border.to-view=\\\"true\\\" horizontal-padding.to-view=\\\"true\\\"></navigation-tabs> <content-editor-sidebar-block-select if.to-view=\\\"!showBlockClipboard\\\" context-identifier.to-view=\\\"contextIdentifier\\\"></content-editor-sidebar-block-select> <content-editor-sidebar-block-clipboard if.to-view=\\\"showBlockClipboard\\\" class=\\\"content-editor-sidebar-blocks__inner-content\\\" context-identifier.to-view=\\\"contextIdentifier\\\" clipboard-content.to-view=\\\"clipboardContent\\\"></content-editor-sidebar-block-clipboard> </template>\";\n// Exports\nexport default code;","// Module\nvar code = \"<template class=\\\"content-editor-sidebar-right ${ sidebarOpen ? 'content-editor-sidebar-right--is-visible' : '' }\\\"> <div class=\\\"content-editor-sidebar-right__content\\\"> <content-editor-sidebar-blocks if.to-view=\\\"isSidebarBlocksActive\\\" class=\\\"content-editor-sidebar-right__inner-content\\\" context-identifier.to-view=\\\"contextIdentifier\\\"></content-editor-sidebar-blocks> <content-editor-sidebar-templates if.to-view=\\\"isSidebarTemplatesActive\\\" class=\\\"content-editor-sidebar-right__inner-content\\\" context-identifier.to-view=\\\"contextIdentifier\\\"></content-editor-sidebar-templates> <content-editor-inspector if.to-view=\\\"isSidebarInspectorActive\\\" context-identifier.to-view=\\\"contextIdentifier\\\" class=\\\"content-editor-sidebar-right__inner-content\\\"></content-editor-inspector> </div> </template> \";\n// Exports\nexport default code;","// Module\nvar code = \"<template class=\\\"content-editor-sidebar-templates content-editor-sidebar-templates--mode-${editorMode} ${ showPublicTemplates ? 'content-editor-sidebar-templates--public' : '' }\\\"> <navigation-tabs class=\\\"content-editor-sidebar-templates__nav-header\\\" tabs.to-view=\\\"navigationTabs\\\" fill-width.to-view=\\\"true\\\" fill-height.to-view=\\\"true\\\" no-border.to-view=\\\"true\\\" horizontal-padding.to-view=\\\"true\\\"></navigation-tabs> <p class=\\\"content-editor-sidebar-templates__select-description\\\" t=\\\"${descriptionTextTk}\\\"></p> <div if.to-view=\\\"!showPublicTemplates\\\" class=\\\"content-editor-sidebar-templates__new ${ createTemplateDisabled ? 'content-editor-sidebar-templates__new--disabled' : '' }\\\"> <div class=\\\"content-editor-sidebar-templates__new-click-area ${ createTemplateDisabled ? 'content-editor-sidebar-templates__new-click-area--disabled' : '' }\\\" click.delegate=\\\"onAddTemplate()\\\"> <div class=\\\"content-editor-sidebar-templates__new-click-area-icon-wrapper\\\"> <safe-icon class=\\\"content-editor-sidebar-templates__new-click-area-icon\\\" icon-name=\\\"System/Add\\\"></safe-icon> </div> <span t=\\\"${addNewTextTk}\\\"></span> </div> </div> <div class=\\\"content-editor-sidebar-templates__template-select\\\"> <div class=\\\"content-editor-sidebar-templates__template-wrapper\\\"> <draggable repeat.for=\\\"templateId of templateIds\\\" payload.to-view=\\\"{\\n                   source: DndSubContext.Template,\\n                   existingPayloadId: templateId,\\n                 }\\\" drag-dom-element.to-view=\\\"draggedElements[$index]\\\" dnd-active.to-view=\\\"dndActive\\\" context.to-view=\\\"dndContext\\\"> <content-editor-template-preview class=\\\"content-editor-sidebar-templates__template ${ templateId === dndPayload.existingPayloadId ? 'content-editor-sidebar-templates__template--ghost' : '' }\\\" template-id.to-view=\\\"templateId\\\" editor-mode.to-view=\\\"editorMode\\\"></content-editor-template-preview> </draggable> </div> </div> <drop-zone if.to-view=\\\"dndActiveSourceTemplate && !showPublicTemplates\\\" class=\\\"content-editor-sidebar-templates__trash-dropzone\\\" config.to-view=\\\"dndTrashDropzoneConfig\\\" context.to-view=\\\"dndContext\\\" dnd-active.to-view=\\\"dndActive\\\"> <div class=\\\"content-editor-sidebar-templates__trash-dropzone-icon-wrapper ${ dndTemplateTrashTargeted ? 'content-editor-sidebar-templates__trash-dropzone-icon-wrapper--targeted' : '' }\\\"> <x-icon class=\\\"content-editor-sidebar-templates__trash-icon ${ dndTemplateTrashTargeted ? 'content-editor-sidebar-templates__trash-icon--targeted' : '' }\\\" size=\\\"unspecified\\\" icon-type=\\\"custom\\\" icon-name=\\\"Uce/Delete\\\"></x-icon> </div> <p class=\\\"h6 content-editor-sidebar-templates__trash-dropzone-text ${ dndTemplateTrashTargeted ? 'content-editor-sidebar-templates__trash-dropzone-text--targeted' : '' }\\\" t=\\\"pages.content-editor.dndTrashMessage\\\"></p> </drop-zone> </template> \";\n// Exports\nexport default code;","// Module\nvar code = \"<template class=\\\"content-editor-slide-selection ${styleClasses}\\\"> <div class=\\\"content-editor-slide-selection__scroll-wrapper\\\"> <div repeat.for=\\\"slideId of (isIas ? orderedIasSlideIds : orderedSlideIds)\\\" class=\\\"content-editor-slide-selection__single-element\\\"> <div class=\\\"content-editor-slide-selection__draggable-slide-wrapper\\\"> <draggable payload.to-view=\\\"{\\n                                      source: DndPayloadSource.Content,\\n                                      existingPayloadId: slideId,\\n                                    }\\\" dnd-active.to-view=\\\"dndActive\\\" context.to-view=\\\"dndContext\\\" drag-allowed.to-view=\\\"editable\\\"> <div class=\\\"content-editor-slide-selection__slide ${slideId === dndPayload.existingPayloadId ? 'content-editor-slide-selection__slide--ghost' : ''} ${slideId === selectedSlideId || slideId === selectedIasSlideId ? 'content-editor-slide-selection__slide--selected' : ''}\\\"> <div class=\\\"content-editor-slide-selection__controls\\\"> <safe-icon if.to-view=\\\"editable\\\" class=\\\"content-editor-slide-selection__control-icon content-editor-slide-selection__control-icon-drag\\\" icon-name=\\\"System/DragAlt\\\"></safe-icon> <div class=\\\"content-editor-slide-selection__slide-index\\\">${$index + 1}</div> <safe-icon if.to-view=\\\"editable && !isIas\\\" click.delegate=\\\"onCloneSlide(slideId)\\\" class=\\\"content-editor-slide-selection__control-icon content-editor-slide-selection__control-icon-clone\\\" icon-name=\\\"System/Tabler/Copy\\\" role=\\\"button\\\"></safe-icon> <safe-icon if.to-view=\\\"editable\\\" click.delegate=\\\"onDeleteSlide(slideId)\\\" class=\\\"content-editor-slide-selection__control-icon content-editor-slide-selection__control-icon-trash\\\" icon-name=\\\"System/Tabler/Trash\\\" role=\\\"button\\\"></safe-icon> </div> <div class=\\\"content-editor-slide-selection__slide-preview-wrapper\\\" click.delegate=\\\"onSelectSlide(slideId)\\\"> <intersection-target view-model.ref=\\\"intersectionTarget\\\" class=\\\"content-editor-slide-selection__intersection-target\\\" root.to-view=\\\"domElement\\\"> <aspect-ratio-container class=\\\"content-editor-slide-selection__slide-aspect-ratio-container\\\"> <content-editor-presentation-slide if.to-view=\\\"!isIas && intersectionTarget.visible && contextIdentifier != null\\\" class=\\\"content-editor-slide-selection__slide-preview slide-markdown\\\" context-identifier.to-view=\\\"contextIdentifier\\\" slide-id.to-view=\\\"slideId\\\"></content-editor-presentation-slide> <ias-slide if.to-view=\\\"isIas && intersectionTarget.visible && contextIdentifier != null\\\" class=\\\"content-editor-slide-selection__ias-slide\\\" context-identifier.to-view=\\\"contextIdentifier\\\" slide-id.to-view=\\\"slideId\\\"></ias-slide> </aspect-ratio-container> </intersection-target> </div> </div> </draggable> </div> <drop-zone class=\\\"content-editor-slide-selection__drop-zone ${ dndActive && dndTargetDisplaySettings[$index].before !== DndTargetDisplay.Hidden ? 'content-editor-slide-selection__drop-zone--front content-editor-slide-selection__drop-zone--before' : '' } content-editor-slide-selection__drop-zone--${ dndTargetDisplaySettings[$index].before } ${ $index === 0 ? 'content-editor-slide-selection__drop-zone--first' : '' }\\\" style=\\\"grid-row:1/2\\\" dnd-active.to-view=\\\"dndActive\\\" context.to-view=\\\"dndContext\\\" config.to-view=\\\"getDropzoneConfig($index, isIas, DndInsertModeSequential.Before)\\\" provide-dropzone-identifiers.to-view=\\\"false\\\"></drop-zone> <drop-zone class=\\\"content-editor-slide-selection__drop-zone ${ dndActive && dndTargetDisplaySettings[$index].after !== DndTargetDisplay.Hidden ? 'content-editor-slide-selection__drop-zone--front content-editor-slide-selection__drop-zone--after' : '' } content-editor-slide-selection__drop-zone--${ dndTargetDisplaySettings[$index].after } ${ $last ? 'content-editor-slide-selection__drop-zone--last' : '' }\\\" style=\\\"grid-row:2/3\\\" dnd-active.to-view=\\\"dndActive\\\" context.to-view=\\\"dndContext\\\" config.to-view=\\\"getDropzoneConfig($index, isIas, DndInsertModeSequential.After)\\\" provide-dropzone-identifiers.to-view=\\\"false\\\"></drop-zone> <div if.to-view=\\\"!dndActive && !$last && editable\\\" class=\\\"content-editor-slide-selection__add-slide-hover-zone\\\" click.delegate=\\\"onAddSlide(slideId)\\\"> <separator text-tk=\\\"elements.presentationSideBar.addSlide\\\" class=\\\"content-editor-slide-selection__separator\\\"></separator> </div> </div> <div if.to-view=\\\"editable\\\" class=\\\"content-editor-slide-selection__new-slide\\\"> <div class=\\\"content-editor-slide-selection__new-slide-click-area\\\" click.delegate=\\\"onAddSlide()\\\"> <div class=\\\"content-editor-slide-selection__new-slide-click-area-icon-wrapper\\\"> <safe-icon class=\\\"content-editor-slide-selection__new-slide-click-area-icon\\\" icon-name=\\\"System/Add\\\"></safe-icon> </div> <span class=\\\"content-editor-slide-selection__new-slide-click-area-text\\\" t=\\\"elements.presentationSideBar.addSlide\\\"></span> </div> </div> </div> </template> \";\n// Exports\nexport default code;","// Module\nvar code = \"<template class=\\\"content-editor-template-preview\\\"> <aspect-ratio-container class=\\\"content-editor-template-preview__aspect-ratio-container\\\" ratio.to-view=\\\"ratio\\\"> <template-preview-layout-container class=\\\"content-editor-template-preview__root-container\\\" layout-container-id.to-view=\\\"template.rootLayoutContainerId\\\"></template-preview-layout-container> </aspect-ratio-container> </template>\";\n// Exports\nexport default code;","// Module\nvar code = \"<template class.to-view=\\\"styleClasses\\\" style.to-view=\\\"{ '--template-preview-layout-container-scale-factor': scaleFactor }\\\"> <div class=\\\"template-preview-layout-container__content\\\" style.to-view=\\\"layoutStyles\\\"> <template-preview-block if.to-view=\\\"mode === 'block'\\\" class=\\\"template-preview-layout-container__block\\\" block-type.to-view=\\\"layoutContainer.templateBlockType\\\"></template-preview-block> <template-preview-layout-container if.to-view=\\\"mode === 'children' && orderedChildIds != null\\\" repeat.for=\\\"childId of orderedChildIds\\\" class=\\\"template-preview-layout-container__child\\\" layout-container-id.to-view=\\\"childId\\\"></template-preview-layout-container> </div> </template> \";\n// Exports\nexport default code;","// Module\nvar code = \"<template class=\\\"course-and-group-list-item ${ removable ? 'course-and-group-list-item--removable' : '' }\\\"> <div class=\\\"course-and-group-list-item__inner\\\"> <div>${course.title}</div> <div>${courseGroup.name}</div> </div> <button-standard if.to-view=\\\"removable\\\" click.delegate=\\\"handleRemoveCourseAndGroupClick()\\\" btn-type=\\\"outline\\\" icon-name=\\\"System/Tabler/Trash\\\" btn-color=\\\"danger\\\"></button-standard> </template>\";\n// Exports\nexport default code;","// Module\nvar code = \"<template class=\\\"course-and-group-select\\\"> <div class=\\\"row mb-4\\\"> <div class=\\\"${ isInvitedToAllCourses ? 'col-md-12' : 'col-md-6' }\\\"> <label class=\\\"form-label required-label\\\" t=\\\"globals.course\\\"></label> <select class=\\\"custom-select\\\" disabled.to-view=\\\"disabled\\\" value.bind=\\\"selectedCourse\\\" change.trigger=\\\"handleSelectedCourseChanged(selectedCourse.id)\\\"> <option if.to-view=\\\"isInvitedToAllCourses\\\" model.bind=\\\"null\\\" t=\\\"dialogs.invitationCreate.invitedToAllCourses\\\"></option> <option repeat.for=\\\"course of availableCourses\\\" disabled.to-view=\\\"!course.available\\\" model.bind=\\\"course\\\"> ${ course.available ? course.title : course.info } </option> </select> </div> <div if.to-view=\\\"!isInvitedToAllCourses\\\" class=\\\"col-md-6\\\"> <label class=\\\"form-label required-label\\\" t=\\\"globals.courseGroup\\\"></label> <select if.to-view=\\\"!noGroupInCourse\\\" class=\\\"custom-select\\\" value.bind=\\\"selectedCourseGroup\\\"> <option repeat.for=\\\"courseGroup of availableCourseCourseGroups\\\" model.bind=\\\"courseGroup\\\"> ${ courseGroup.name } </option> </select> <button else class=\\\"btn btn-secondary w-100\\\" click.delegate=\\\"handleCreateCourseGroupClick()\\\" t=\\\"dialogs.invitationCreate.createCourseGroup\\\"></button> </div> </div> <template if.to-view=\\\"!isInvitedToAllCourses\\\"> <div class=\\\"row\\\"> <div class=\\\"col-md-12\\\"> <div class=\\\"form-group mb-0\\\"> <label class=\\\"form-label\\\" t=\\\"dialogs.invitationCreate.learnGoalLabel\\\"></label> </div> </div> </div> <div class=\\\"row\\\"> <div class=\\\"form-group col-md-6 mb-0\\\"> <date-input click.trigger=\\\"examDateError = null\\\" mode=\\\"date\\\" value.two-way=\\\"examDate\\\" can-clear.to-view=\\\"true\\\" is-invalid.to-view=\\\"!!examDateError\\\"></date-input> <div if.to-view=\\\"examDateError\\\" class=\\\"invalid-feedback d-block\\\" t=\\\"${examDateError}\\\"></div> </div> <div class=\\\"form-group col-md-6 mb-0\\\"> <button-standard class=\\\"mt-auto w-100\\\" btn-fill-mode=\\\"fill-width\\\" disabled.to-view=\\\"noGroupInCourse\\\" click.delegate=\\\"handleAddCourseAndGroupClick()\\\" t=\\\"[btnText]dialogs.invitationCreate.addCourse\\\" icon-name=\\\"System/Tabler/Plus\\\" btn-color=\\\"primary\\\"></button-standard> </div> </div> <div class=\\\"row\\\"> <div class=\\\"col-12\\\"> <small class=\\\"form-text text-muted\\\" t-params.to-view=\\\"{ minDaysToExam: selectedCourse.minDaysToExam, defaultDaysToExam: selectedCourse.defaultDaysToExam, maxDaysToExam: maxDaysToExam }\\\" t=\\\"globals.learnGoalInfoText\\\"></small> </div> </div> </template> </template> \";\n// Exports\nexport default code;","import { autoinject, ObserverLocator, Parser } from 'aurelia-framework';\nimport { I18N } from 'aurelia-i18n';\nimport { selectState, SelectStateObservableComponent } from 'common/aurelia-decorators/select-state';\nimport { StoreService } from 'common/aurelia-services/StoreService';\nimport { SubscriptionManager, SubscriptionManagerService } from 'common/aurelia-services/SubscriptionManagerService';\nimport { ContentEditingInsertModeSelector } from 'common/constants/content.editing.constants';\nimport { CONTENT_EDITOR_COURSE_CONTEXT } from 'common/constants/content.editor.constants';\nimport { ContentElementType, NonBlockElementType } from 'common/dto/content-editor/content.editor.types';\nimport { BlockType } from 'common/dto/course/content/course.types';\nimport * as ContentContextMutations from 'common/store/mutations/content.context.mutations';\nimport * as CourseContextMutations from 'common/store/mutations/course.context.mutations';\nimport {\n  courseIdSelectorCreator,\n  isIasSelectorCreator,\n  isPageEmptySelectorCreator,\n  navigationInfoSelectorCreator,\n  pageIdSelectorCreator,\n} from 'common/store/selectors/course.context.selectors';\nimport { ICourseNavigationInfo } from 'common/store/store.interface';\nimport { ContentEditorApiService } from '../../../services/api/ContentEditorApiService';\nimport { ContentEditorService } from '../../../services/ContentEditorService';\nimport { CustomDialogService } from '../../../services/CustomDialogService';\nimport {\n  DND_EMPTY_PAGE_ID,\n  DND_LOWER_WHITESPACE_ID,\n  DndActivityEventData,\n  DndContext,\n  DndDropEventData,\n  DndDropzoneIdentifier,\n  DndManager,\n  DndManagerService,\n  DndPayloadUniversal,\n  DndSubContext,\n  IDndDropzoneConfig,\n} from '../../../services/DndManagerService';\nimport * as ClipboardMutations from '../../../store/clipboard.mutations';\nimport * as ContentEditorMutations from '../../../store/content.editor.mutations';\nimport { IState } from '../../../store/state';\nimport { BlockUtils } from '../../../utils/BlockUtils';\nimport { RichTextEditorToolbar } from '../rich-text-editor/rich-text-editor-toolbar/rich-text-editor-toolbar';\nimport { UceComposableLayoutContainer } from '../uce-composable-layout-container/uce-composable-layout-container';\nimport { UceCourseLearnPageHeader } from '../uce-course-learn-page-header/uce-course-learn-page-header';\nimport { InspectorMode } from './../../../store/state';\nimport { IContentEditorBlockEditorBlock } from './../../dialogs/content-editor-block-editor-dialog/content-editor-block-editor-dialog';\n\n\n@autoinject()\nexport class ContentEditorPageEditor extends SelectStateObservableComponent {\n  private layoutContainerViewModel = UceComposableLayoutContainer;\n  private headerViewModel = UceCourseLearnPageHeader;\n\n  private subscriptionManager: SubscriptionManager;\n  private domElement: HTMLElement;\n  private inspectorMode: InspectorMode | null = null;\n\n  public contextIdentifier = CONTENT_EDITOR_COURSE_CONTEXT;\n  @selectState(ContentEditorPageEditor, pageIdSelectorCreator, ['contextIdentifier']) private pageId?: number | null = null;\n  @selectState(ContentEditorPageEditor, navigationInfoSelectorCreator, ['contextIdentifier']) private navigationInfo?: ICourseNavigationInfo | null;\n  @selectState(ContentEditorPageEditor, isPageEmptySelectorCreator, ['contextIdentifier']) private isPageEmpty?: boolean;\n  @selectState(ContentEditorPageEditor, isIasSelectorCreator, ['contextIdentifier']) private isIas?: boolean;\n  @selectState(ContentEditorPageEditor, courseIdSelectorCreator, ['contextIdentifier']) private courseId?: number | null;\n\n  private dndTemplateActive = false;\n  private dndContext: DndContext = DndContext.ContentEditorLayout;\n  private dndManager: DndManager<ContentElementType> | null = null;\n  private dndActive = false;\n  private dndEmptyPageTargeted = false;\n  private dndEmptyPageIdentifier: DndDropzoneIdentifier = {\n    superId: DND_EMPTY_PAGE_ID,\n    subId: DND_EMPTY_PAGE_ID,\n  };\n  private dndLowerWhitespaceIdentifier: DndDropzoneIdentifier = {\n    superId: DND_LOWER_WHITESPACE_ID,\n    subId: DND_LOWER_WHITESPACE_ID,\n  };\n  private firstDropzoneConfig: IDndDropzoneConfig = {\n    identifier: this.dndEmptyPageIdentifier,\n    insertModeSelector: ContentEditingInsertModeSelector.Plain,\n    subContext: DndSubContext.Content,\n  };\n  private lastDropzoneConfig: IDndDropzoneConfig = {\n    identifier: this.dndLowerWhitespaceIdentifier,\n    insertModeSelector: ContentEditingInsertModeSelector.Sequential,\n    subContext: DndSubContext.Content,\n  };\n\n  constructor(\n    element: Element,\n    subscriptionManagerService: SubscriptionManagerService,\n    private storeService: StoreService<IState>,\n    private dndManagerService: DndManagerService,\n    private customDialogService: CustomDialogService,\n    private contentEditorApi: ContentEditorApiService,\n    private contentEditorService: ContentEditorService,\n    parser: Parser,\n    observerLocator: ObserverLocator,\n    private i18n: I18N,\n  ) {\n    super(parser, observerLocator);\n    this.domElement = element as HTMLElement;\n    this.subscriptionManager = subscriptionManagerService.createSubscriptionManager();\n  }\n\n  protected attached(): void {\n    this.storeService.subscribeToDetailedChanges(this, [\n      state => state.contentEditor.dragAndDrop.active,\n    ], (_state, active) => {\n      this.dndActive = active;\n      if(!this.dndActive) {\n        this.dndEmptyPageTargeted = false;\n      }\n    });\n\n    this.dndManager = this.dndManagerService.getDndManager<ContentElementType>(this.dndContext);\n    this.dndManager.subscribeDrop(this, this.dndOnDropHandler.bind(this), [this.dndEmptyPageIdentifier]);\n    this.dndManager.subscribePayloadChanged(this, this.dndPayloadChangedHandler.bind(this));\n    this.dndManager.subscribeToDragActivity(\n      this,\n      this.dndDragActivityOnLowerWhitespaceHandler.bind(this),\n      [this.dndLowerWhitespaceIdentifier],\n    );\n    this.dndManager.subscribeToDragActivity(\n      this,\n      this.dndDragActivityOnEmptyPageHandler.bind(this),\n      [this.dndEmptyPageIdentifier],\n    );\n\n    this.storeService.subscribeToDetailedChanges(this, [\n      state => state.contentEditor.inspector.mode,\n    ], (_state, inspectorMode) => {\n      this.inspectorMode = inspectorMode;\n    });\n  }\n\n  protected detached(): void {\n    this.storeService.disposeAllSubscriptions(this);\n    this.subscriptionManager.disposeSubscriptions();\n    this.dndManager?.disposeSubscriptions(this);\n  }\n\n  private dndPayloadChangedHandler(payload: DndPayloadUniversal<ContentElementType>): void {\n    this.dndTemplateActive = payload != null && payload.source === DndSubContext.Template;\n  }\n\n  private async dndOnDropHandler(dropInfo: DndDropEventData<ContentElementType>): Promise<void> {\n    // stop dnd before BE-API activity\n    this.stopDnd();\n    if(this.pageId == null\n      || this.contextIdentifier == null\n      || dropInfo.dndPayload == null\n      || dropInfo.dropTarget.subContext !== DndSubContext.Content) return;\n\n    if(dropInfo.dndPayload.source === DndSubContext.Clipboard) {\n      if(dropInfo.dndPayload.existingPayloadId == null) return;\n      const changes = await this.contentEditorApi.moveElementFromClipboard(\n        dropInfo.dndPayload.existingPayloadId,\n        {\n          insertMode: ContentEditingInsertModeSelector.Plain,\n          pageId: this.pageId,\n        },\n      );\n      await this.storeService\n        .pipe(ContentContextMutations.applyLayoutChanges, this.contextIdentifier, changes.targetLayoutChanges)\n        .pipe(\n          ClipboardMutations.applyRemoveChanges,\n          changes.clipboardLayoutContainerChanges,\n          changes.clipboardCardContentChanges,\n          changes.clipboardRemovedBlockId,\n        ).pipe(ContentEditorMutations.updateContainerStructure, this.contextIdentifier)\n        .dispatch();\n    } else if(dropInfo.dndPayload.source === DndSubContext.New) {\n      // insert a fresh block\n      if(dropInfo.dndPayload.createNewType == null) return;\n\n      let createBlockDto: IContentEditorBlockEditorBlock | null = null;\n      switch(dropInfo.dndPayload.createNewType) {\n        case NonBlockElementType.Card:\n          createBlockDto = {\n            type: BlockType.EMPTY_CARD,\n            text: this.i18n.tr('pages.content-editor.cardTextBlockPlaceholder'),\n          };\n          break;\n        case BlockType.TEXT:\n        case BlockType.SPACER:\n          createBlockDto = {\n            type: dropInfo.dndPayload.createNewType,\n            text: '',\n          };\n          break;\n        case NonBlockElementType.AiText:\n          createBlockDto = {\n            type: BlockType.TEXT,\n            text: '',\n          };\n          break;\n        case NonBlockElementType.AiQuiz:\n          createBlockDto = await this.customDialogService.showContentEditorBlockEditorDialog({\n            contextIdentifier: this.contextIdentifier,\n            blockId: null,\n            block: {\n              type: BlockType.QUESTION,\n              nonBlockElementType: dropInfo.dndPayload.createNewType,\n            }\n          });\n          break;\n        default:\n          createBlockDto = await this.customDialogService.showContentEditorBlockEditorDialog({\n            contextIdentifier: this.contextIdentifier,\n            blockId: null,\n            block: {\n              type: dropInfo.dndPayload.createNewType\n            }\n          });\n          break;\n      }\n      if(createBlockDto == null) return;\n\n      const changes = await this.contentEditorApi.addFirstElementOnPage(this.pageId, BlockUtils.convertToCreateUniversalBlock(createBlockDto));\n      await this.storeService\n        .pipe(ContentContextMutations.applyLayoutChanges, this.contextIdentifier, changes)\n        .pipe(ContentEditorMutations.updateContainerStructure, this.contextIdentifier)\n        .dispatch();\n\n      if(dropInfo.dndPayload.createNewType === NonBlockElementType.AiText) RichTextEditorToolbar.showAiTray();\n      await this.contentEditorService.focusNewTextBlock(changes);\n\n    } else if(dropInfo.dndPayload.source === DndSubContext.Template) {\n      // apply template to page:\n      if(dropInfo.dndPayload.existingPayloadId == null || this.courseId == null) return;\n      const changes = await this.contentEditorApi.applyTemplateExisting(\n        this.courseId,\n        this.pageId,\n        dropInfo.dndPayload.existingPayloadId,\n      );\n      await this.storeService\n        .pipe(ContentContextMutations.applyLayoutChanges, this.contextIdentifier, changes.layoutChanges)\n        .pipe(ContentEditorMutations.updateContainerStructure, this.contextIdentifier)\n        .dispatch();\n    }\n  }\n\n  private stopDnd(): void {\n    if(this.inspectorMode !== InspectorMode.Container) this.storeService\n      .dispatch(ContentEditorMutations.updateContainerStylesState, { showExtraContainerSpace: false });\n\n    this.storeService\n      .pipe(ContentEditorMutations.updateDragAndDropState, {\n        active: false,\n        lowerWhitespaceTargeted: false,\n      })\n      .pipe(ContentEditorMutations.resetContainerHoverState)\n      .dispatch();\n  }\n\n  private dndDragActivityOnEmptyPageHandler(dndInfo: DndActivityEventData<IDndDropzoneConfig>): void {\n    this.dndEmptyPageTargeted = dndInfo.enterEvent;\n  }\n\n  private dndDragActivityOnLowerWhitespaceHandler(dndInfo: DndActivityEventData<IDndDropzoneConfig>): void {\n    const lowerWhitespaceTargeted = dndInfo.enterEvent;\n    this.storeService.dispatch(ContentEditorMutations.updateDragAndDropState, { lowerWhitespaceTargeted });\n  }\n\n  private ensureInspectorEmpty(): void {\n    if(this.inspectorMode !== InspectorMode.Inactive) {\n      // reset selected layout container but keep inspector open\n      this.storeService.dispatch(ContentEditorMutations.updateInspector, null);\n    }\n  }\n\n  private async pageIdChanged(): Promise<void> {\n    await this.storeService\n      .pipe(ContentEditorMutations.updateContainerStructure, this.contextIdentifier)\n      .pipe(ContentEditorMutations.resetContainerHoverState)\n      .dispatch();\n  }\n\n  private async navigateBack(): Promise<void> {\n    this.ensureInspectorEmpty();\n    await this.storeService.dispatch(CourseContextMutations.navigateBack, this.contextIdentifier);\n  }\n\n  private async navigateForward(): Promise<void> {\n    this.ensureInspectorEmpty();\n    await this.storeService.dispatch(CourseContextMutations.navigateForward, this.contextIdentifier);\n  }\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/elements/content-editor-page-editor/content-editor-page-editor'));\n}\n","import { autoinject, bindable } from 'aurelia-framework';\nimport { selectState } from 'common/aurelia-decorators/select-state';\nimport { courseIdSelectorCreator, pageIdSelectorCreator } from 'common/store/selectors/course.context.selectors';\nimport { PageTypeFilter } from '../content-editor-pages/content-editor-pages';\n\n@autoinject()\nexport class ContentEditorPagesSidebar {\n  @bindable() public contextIdentifier: string | null = null;\n  private open = false;\n  private searchString = '';\n  private searchActive = false;\n  private pageTypeOptions = [PageTypeFilter.LearningOnly, PageTypeFilter.All];\n  private pageTypeFilter = PageTypeFilter.LearningOnly;\n\n  @selectState(ContentEditorPagesSidebar, courseIdSelectorCreator, ['contextIdentifier']) private courseId?: number | null;\n  @selectState(ContentEditorPagesSidebar, pageIdSelectorCreator, ['contextIdentifier']) private pageId?: number | null = null;\n\n  private toggleOpen(): void {\n    this.open = !this.open;\n  }\n}\n","import { autoinject, bindable } from 'aurelia-framework';\nimport { I18N } from 'aurelia-i18n';\nimport { StoreService } from 'common/aurelia-services/StoreService';\nimport { ContentEditingInsertModeSelector } from 'common/constants/content.editing.constants';\nimport { IPageEditResponseDto } from 'common/dto/course/management/course.management.interface';\nimport { PageType } from 'common/enum/course.enum';\nimport { ErrorType } from 'common/enum/errortype.enum';\nimport * as CourseContextMutations from 'common/store/mutations/course.context.mutations';\nimport { ArrayUtils } from 'common/utils/ArrayUtils';\nimport { assertNotNullOrUndefined } from 'common/utils/assert';\nimport { DomUtils } from 'common/utils/DomUtils';\nimport { LogUtils } from 'common/utils/LogUtils';\nimport { ContentEditorApiService } from '../../../services/api/ContentEditorApiService';\nimport { CourseContentManagementApiService } from '../../../services/api/CourseContentManagementApiService';\nimport { CourseManagementApiService } from '../../../services/api/CourseManagementApiService';\nimport { CourseCacheService } from '../../../services/CourseCacheService';\nimport { CustomDialogService } from '../../../services/CustomDialogService';\nimport {\n  DndActivityEventData,\n  DndContext,\n  DndDropEventData,\n  DndManager,\n  DndManagerService,\n  DndPayloadUniversal,\n  IDndDropzoneConfig\n} from '../../../services/DndManagerService';\nimport { ToastService } from '../../../services/ToastService';\nimport { IState } from '../../../store/state';\nimport { CreatePageEvent, PageSelectedEvent, ToggleCollapseEvent, UpdatePageEvent } from '../page-row/page-row';\nimport { DndInsertModeSequential, DndSubContext } from './../../../services/DndManagerService';\n\n/**\n * The abbreviation DndPac used in course-page and course-row represents: Drag and Drop - Pages and Chapters\n * DndPacFirstTargetDisplayMode is used for the very first drag and drop target above the first content element,\n * all other targets are handled inside page-row elements with a different logic.\n */\nenum DndPacFirstTargetDisplayMode {\n  Hidden = 'hidden',\n  Allowed = 'allowed',\n  Target = 'target',\n}\n\nexport enum DndPacForbiddenLayersMode {\n  All = 'all',\n  None = 'none',\n  Range = 'range',\n}\n\ninterface IDndPacLayerRange {\n  from: number;\n  to: number;\n}\n\ninterface IDndPacForbiddenLayersSetting {\n  mode: DndPacForbiddenLayersMode;\n  range: IDndPacLayerRange;\n}\n\n/**\n * IDndPacTargetDisplaySetting is a part of the interface to page-row via binding\n */\nexport interface IDndPacTargetDisplaySetting {\n  layer: number;\n  isTarget: boolean;\n  forbiddenLayers: IDndPacForbiddenLayersSetting;\n}\n\nexport interface IDndPacViewDataCurrentElement {\n  payload: DndPayloadUniversal<PageType>;\n  prefix: string;\n  title: string;\n  parentId: number | null;\n  order: number;\n  layer: number;\n  row: number;\n  visible: boolean;\n  collapsed: boolean;\n  isIntro: boolean;\n  isTrainingOnly: boolean;\n  rootIsTrainingOnly: boolean;\n  hasRequirements: boolean;\n  highlighted: boolean;\n  active: boolean;\n}\n\nexport interface IDndPacViewDataNeighbourElement {\n  id: number;\n  layer: number;\n  type: PageType;\n}\n\n/**\n * IDndPacViewDataElementData is used for each row in the list of elements representing the course content.\n * The additional information of previous and next is used by each page-row to calculate it's drag and drop\n * - drop-zones and targets within it's limited scope.\n */\nexport interface IDndPacViewDataElementData {\n  current: IDndPacViewDataCurrentElement;\n  previous: IDndPacViewDataNeighbourElement | null;\n  next: IDndPacViewDataNeighbourElement | null;\n}\n\ninterface IDndPacViewDataRow {\n  element: IDndPacViewDataElementData;\n  targetDisplay: IDndPacTargetDisplaySetting;\n}\n\n/**\n * DndPacViewData collects the majority of information required by the view to do it's job in one type.\n * In the view each page-row is provided the \"maxLayer\" and one array-element of \"rows\" as bindings.\n */\ntype DndPacViewData = {\n  maxLayer: number;\n  rows: Array<IDndPacViewDataRow>;\n};\n\ntype TAddAllChildrenToListConfig = {\n  counterIn: number;\n  currentLayer: number;\n  page: IPage;\n  currentPageParentChaptersIds?: Set<number>;\n  prefixBase: string;\n  rootPage?: IPage;\n};\n\n@autoinject()\nexport class ContentEditorPages {\n  /**\n   * only available inside content-editor\n   */\n  @bindable() public contextIdentifier: string | null = null;\n  @bindable() public courseId: number | null = null;\n  @bindable() public searchString = '';\n  @bindable() public searchActive = false;\n  @bindable() public typeFilter = PageTypeFilter.LearningOnly;\n  @bindable() public pageId: number | null = null;\n\n  private INTRO_CHAPTER_PREFIX: string = this.i18N.tr('pages.content-editor.introChapterPrefix');\n  private isAttached = false;\n  private domElement: HTMLElement;\n\n  /**\n   * dndActive and onDropActive are used to create a representation of drag-and-drop-active\n   * which is used by the draggable view-elements to prevent starting a new drag-and-drop activity\n   * while the current one is still active (meaning the async onDrop reaction is still ongoing).\n   */\n  private dndActive = false;\n  private onDropActive = false;\n  private pages: IPageEditResponseDto[] = [];\n  private viewDataRows: Array<IDndPacViewDataRow> = [];\n  private viewData: DndPacViewData = { maxLayer: 0, rows: [] };\n  private viewDataBuffer: DndPacViewData = { maxLayer: 0, rows: [] };\n  private rowElementMinWidth = 5;\n  private dndPayload: DndPayloadUniversal<PageType> | null = null;\n  private dndLastEnteredInfo: IDndDropzoneConfig | null = null;\n  private firstTargetDisplayMode: DndPacFirstTargetDisplayMode = DndPacFirstTargetDisplayMode.Allowed;\n  private firstDropzoneConfig: IDndDropzoneConfig = {\n    identifier: {\n      superId: 0,\n      subId: 0,\n    },\n    insertModeSelector: ContentEditingInsertModeSelector.Sequential,\n    insertModeSequential: DndInsertModeSequential.Before,\n  };\n  private dndManager: DndManager<PageType>;\n  private dndContext = DndContext.PagesAndChapters;\n\n  constructor(\n    private storeService: StoreService<IState>,\n    private contentEditorApi: ContentEditorApiService,\n    private courseManagementApiService: CourseManagementApiService,\n    private courseContentManagementApiService: CourseContentManagementApiService,\n    private dndManagerService: DndManagerService,\n    private customDialogService: CustomDialogService,\n    private toastService: ToastService,\n    private courseCacheService: CourseCacheService,\n    private i18N: I18N,\n    element: Element,\n  ) {\n    this.domElement = element as HTMLElement;\n    this.dndManager = this.dndManagerService.getDndManager<PageType>(this.dndContext);\n  }\n\n  public async refresh(): Promise<void> {\n    if(!this.isAttached) return;\n    await this.updatePages();\n    this.updateViewData();\n  }\n\n  protected attached(): void {\n    this.dndManager.subscribePayloadChanged(this, this.dndPayloadChangedHandler.bind(this));\n    this.dndManager.subscribeToDragActivity(this, this.dndDragActivityHandler.bind(this));\n    this.dndManager.subscribeDrop(this, this.onDrop.bind(this));\n    this.isAttached = true;\n  }\n\n  protected detached(): void {\n    this.dndManager.disposeSubscriptions(this);\n    this.isAttached = false;\n  }\n\n  private async courseIdChanged(): Promise<void> {\n    if(!this.courseId) return;\n    await this.updatePages();\n    this.updateViewData();\n  }\n\n  private async pageIdChanged(): Promise<void> {\n    if(!this.pageId) return;\n    this.updateViewData();\n  }\n\n  private searchActiveChanged(): void {\n    if(!this.searchActive) {\n      this.refreshFilter();\n    }\n  }\n\n  private searchStringChanged(): void {\n    this.refreshFilter();\n  }\n\n  private typeFilterChanged(): void {\n    this.updateViewData();\n    this.refreshFilter();\n  }\n\n  private refreshFilter(): void {\n    if(!this.isAttached) return;\n    const cleanSearchString: string = this.searchString.trim().toLocaleLowerCase();\n    this.viewDataRows = this.viewData.rows.filter(row => (\n      (!cleanSearchString || row.element.current.title.toLocaleLowerCase().includes(cleanSearchString))\n      && ((!row.element.current.isTrainingOnly && !row.element.current.rootIsTrainingOnly) || this.typeFilter === PageTypeFilter.All))\n    );\n  }\n\n  private handlePageSelectedEvent(event: PageSelectedEvent): void {\n    DomUtils.passThroughEvent(this.domElement, event);\n  }\n\n  private async updatePages(): Promise<void> {\n    assertNotNullOrUndefined(this.courseId, 'courseId cannot be null or undefined');\n    this.pages = await this.courseManagementApiService.getPages(this.courseId);\n    // update content for content-editor\n    await this.courseCacheService.ensureCourseInCache(this.courseId);\n  }\n\n  private updateViewData(): void {\n    // preparing a sorted course structure as a tree\n    const currentPage = this.pages.find(page => page.id === this.pageId) ?? null;\n\n    this.pages.sort((a, b) => a.order - b.order);\n    const pagesById = this.pages.reduce((prev, cur) => {\n      prev.set(cur.id, { ...cur, children: [] });\n      return prev;\n    }, new Map<number, IPage>());\n\n    // keep track of the current page path for highlights/visibility\n    const currentPageParentChaptersIds = new Set<number>();\n    let currentParent: IPage | undefined;\n    const pageTree = Array.from(pagesById.values()).reduce((prev, cur) => {\n      const parent = pagesById.get(cur.parentId || 0);\n      if(parent) {\n        if(currentParent?.parentId === parent?.id || currentPage?.parentId === parent?.id) {\n          currentParent = parent;\n          currentPageParentChaptersIds.add(currentParent.id);\n        }\n        parent.children.push(cur);\n      } else {\n        prev.push(cur);\n      }\n      return prev;\n    }, [] as IPage[]);\n\n    // The numbering of intro pages is independent of regular pages as is the numbering of visible pages\n    let allPageCounter = 1;\n    let learnPageCounter = 1;\n    let introPageCounter = 1;\n\n    // flattening the course structure tree into an array of rows including all necessary display settings required by page-row\n    this.viewDataBuffer = { maxLayer: 0, rows: [] };\n    pageTree.forEach(page => {\n      let counter = 1;\n      let prefixBase = '';\n      if(page.isIntro) {\n        counter = introPageCounter++;\n        prefixBase = this.INTRO_CHAPTER_PREFIX;\n      } else {\n        counter = allPageCounter++;\n        if(this.typeFilter === PageTypeFilter.LearningOnly && !page.isTrainingOnly) {\n          counter = learnPageCounter++;\n        }\n      }\n      this.addAllChildrenToList({\n        page,\n        currentLayer: 0,\n        prefixBase,\n        counterIn: counter,\n        currentPageParentChaptersIds\n      });\n    });\n    // page-rows will create grid-columns for each layer; basic (minimum) content element width is added as nr. of columns (rowElementMinWidth)\n    this.viewDataBuffer.maxLayer = this.viewDataBuffer.maxLayer + this.rowElementMinWidth;\n    if(this.viewDataBuffer.rows.length > 1) {\n      for(const [idx, row] of this.viewDataBuffer.rows.entries()) {\n        row.element.current.row = idx;\n        if(idx > 0) {\n          const previousRowElement = this.viewDataBuffer.rows[idx - 1].element.current;\n          assertNotNullOrUndefined(\n            previousRowElement.payload?.existingPayloadId,\n            'current row id must be available to config previous row',\n          );\n          assertNotNullOrUndefined(\n            previousRowElement.payload?.existingPayloadType,\n            'current row type must be available to config previous row',\n          );\n          row.element.previous = {\n            id: previousRowElement.payload?.existingPayloadId,\n            layer: previousRowElement.layer,\n            type: previousRowElement.payload.existingPayloadType,\n          };\n        }\n        if(idx < (this.viewDataBuffer.rows.length - 1)) {\n          const nextRowElement = this.viewDataBuffer.rows[idx + 1].element.current;\n          assertNotNullOrUndefined(\n            nextRowElement.payload?.existingPayloadId,\n            'current row id must be available to config next row',\n          );\n          assertNotNullOrUndefined(\n            nextRowElement.payload?.existingPayloadType,\n            'current row type must be available to config next row',\n          );\n          row.element.next = {\n            id: nextRowElement.payload.existingPayloadId,\n            layer: nextRowElement.layer,\n            type: nextRowElement.payload.existingPayloadType,\n          };\n        }\n      }\n    }\n    this.viewData = this.viewDataBuffer;\n    this.refreshFilter();\n  }\n\n  private addAllChildrenToList(config: TAddAllChildrenToListConfig): void {\n    let counter = config.counterIn;\n    const prefixBase: string = config.prefixBase === '' || config.prefixBase === this.INTRO_CHAPTER_PREFIX ? '' : '.';\n    const prefix = config.prefixBase.concat(prefixBase, counter.toString());\n    const oldElementCurrent = this.viewData.rows.find(e => e.element.current.payload?.existingPayloadId === config.page.id)?.element.current;\n    const hasNewElement = !!this.viewData.rows.length && !oldElementCurrent;\n    const isOnCurrentPagePath = !!config.currentPageParentChaptersIds?.has(config.page.id);\n    const isAmongCurrentPagePathSiblings = !!(config.page.parentId && config.currentPageParentChaptersIds?.has(config.page.parentId));\n    const isCurrentPage = this.pageId === config.page.id;\n    const shouldBeVisible = (hasNewElement || !config.currentLayer || isOnCurrentPagePath || isCurrentPage || isAmongCurrentPagePathSiblings);\n\n    this.viewDataBuffer.rows.push({\n      element: {\n        current: {\n          payload: {\n            source: DndSubContext.Content,\n            existingPayloadId: config.page.id,\n            // must translate PageType because there are two identically named entities, a type (from course.types) and an enum (from course.enum).\n            // it is still preferrable, because then the enum can be used in course-page and course-row for comparisons\n            existingPayloadType: config.page.type === 'chapter' ? PageType.Chapter : PageType.Page,\n          },\n          visible: oldElementCurrent?.visible ?? shouldBeVisible,\n          collapsed: (oldElementCurrent?.collapsed ?? !isOnCurrentPagePath) ?? true,\n          title: config.page.title,\n          highlighted: isOnCurrentPagePath,\n          active: isCurrentPage,\n          prefix,\n          layer: config.currentLayer,\n          parentId: config.page.parentId,\n          order: config.page.order,\n          row: 0,\n          isIntro: config.page.isIntro,\n          isTrainingOnly: config.page.isTrainingOnly,\n          rootIsTrainingOnly: (config.rootPage ?? config.page).isTrainingOnly,\n          hasRequirements: !!config.page.pagesToCompleteIds.length\n        },\n        // previous and next will be set after the list has been created (in loadPages())\n        previous: null,\n        next: null,\n      },\n      // majority of targetDisplay settings will be dynamically generated based on drag and drop activity\n      // (forbiddenLayers in dndPayloadChangedHandler() and target-state in calcDndDisplaySettings()) -> // TODO: rename????\n      targetDisplay: {\n        layer: config.currentLayer,\n        isTarget: false,\n        forbiddenLayers: {\n          mode: DndPacForbiddenLayersMode.None,\n          range: {\n            from: 0,\n            to: 0,\n          }\n        }\n      },\n    });\n    this.viewDataBuffer.maxLayer = Math.max(this.viewDataBuffer.maxLayer, config.currentLayer);\n\n    if(config.page.type === PageType.Chapter) {\n      counter = 0;\n      config.page.children.forEach(p => {\n        counter += 1;\n        this.addAllChildrenToList({\n          page: p as IPage,\n          currentLayer: config.currentLayer + 1,\n          prefixBase: prefix,\n          counterIn: counter,\n          rootPage: config.rootPage ?? config.page,\n          currentPageParentChaptersIds: config.currentPageParentChaptersIds\n        });\n      });\n    }\n  }\n\n  private getFirstTargetDisplayModifier(targetDisplayMode: DndPacFirstTargetDisplayMode): string {\n    if(targetDisplayMode === DndPacFirstTargetDisplayMode.Target) return 'content-editor-pages__dnd-target--target';\n    return targetDisplayMode === DndPacFirstTargetDisplayMode.Allowed\n      ? 'content-editor-pages__dnd-target--allowed'\n      : 'content-editor-pages__dnd-target--hidden';\n  }\n\n  private dndPayloadChangedHandler(payload: DndPayloadUniversal<PageType>): void {\n    if(payload) {\n      this.dndActive = true;\n\n      if(!this.dndPayload) {\n        // reset display modes when a fresh drag and drop activity is started\n        this.firstTargetDisplayMode = DndPacFirstTargetDisplayMode.Allowed;\n        this.viewDataBuffer.rows.forEach(row => {\n          this.viewDataBuffer.rows[row.element.current.row].targetDisplay.forbiddenLayers.mode = DndPacForbiddenLayersMode.None;\n        });\n      }\n    }\n    this.dndPayload = payload;\n    if(payload) {\n      this.calcForbiddenDndTargets();\n      this.viewData = this.viewDataBuffer;\n      this.refreshFilter();\n    } else if(!this.onDropActive) this.dndActive = false;\n  }\n\n  private dndDragActivityHandler(dndInfo: DndActivityEventData<IDndDropzoneConfig>): void {\n    if(this.dndPayload == null) return;\n    if(dndInfo && this.dndLastEnteredInfo === null) this.dndLastEnteredInfo = dndInfo.dropTarget;\n    if(dndInfo.enterEvent && this.dndLastEnteredInfo) {\n      this.calcCurrentDndTargetView(dndInfo);\n      this.dndLastEnteredInfo = this.dndLastEnteredInfo !== dndInfo.dropTarget ? dndInfo.dropTarget : this.dndLastEnteredInfo;\n    } else {\n      // calculate display setttings when all drop-zone area is left\n      if(this.dndLastEnteredInfo && this.dndLastEnteredInfo === dndInfo.dropTarget) {\n        this.calcCurrentDndTargetView(dndInfo);\n      }\n    }\n  }\n\n  private async onDrop(dropInfo: DndDropEventData<PageType>): Promise<void> {\n    this.onDropActive = true;\n\n    if(!dropInfo.dndPayload || !this.courseId || dropInfo.dndPayload.existingPayloadId == null) {\n      this.dndActive = false;\n      this.onDropActive = false;\n      return;\n    }\n    const pageToMove = await this.courseManagementApiService.getPage(this.courseId, dropInfo.dndPayload.existingPayloadId);\n    const payloadRowIndex = this.viewData.rows.findIndex(row => row.element.current.payload?.existingPayloadId === dropInfo.dndPayload?.existingPayloadId);\n    const insertBefore = dropInfo.dropTarget.insertModeSequential === DndInsertModeSequential.Before;\n    const { superId: eventSourceRow } = dropInfo.dropTarget.identifier;\n    // inserting at the very beginning of the course content\n    if(insertBefore && eventSourceRow === 0) {\n      // cannot move first element before first element\n      if(payloadRowIndex === 0) {\n        this.dndActive = false;\n        this.onDropActive = false;\n        return;\n      }\n      pageToMove.order = 0;\n      pageToMove.parentId = null;\n    } else {\n      const targetRowIndex = insertBefore ? eventSourceRow - 1 : eventSourceRow;\n      const targetRow = this.viewData.rows[targetRowIndex].element.current;\n      const targetRowForbiddenLayers = this.viewData.rows[targetRowIndex].targetDisplay.forbiddenLayers;\n      const payloadRow = this.viewData.rows[payloadRowIndex].element.current;\n      const { subId: targetLayer } = dropInfo.dropTarget.identifier;\n      /**\n       * check if current drop target is actually forbidden and return if it is.\n       * the reason why possibly forbidden target-drop events are not prevented at their origin in\n       * page-rows is that the drop event can come from a neighbouring row. thus quite a bit of\n       * event/binding \"traffic\" would be required to achieve this. in order to minimize this load\n       * and because all information is already available in this class it is checked here:\n      */\n      if(targetRowForbiddenLayers.mode === DndPacForbiddenLayersMode.All\n        || (targetRowForbiddenLayers.mode === DndPacForbiddenLayersMode.Range\n          && (targetLayer >= targetRowForbiddenLayers.range.from\n            && targetLayer <= targetRowForbiddenLayers.range.to))) {\n        this.dndActive = false;\n        this.onDropActive = false;\n        return;\n      }\n      if(targetRow.payload?.existingPayloadId == null) return;\n      let pageToCompare = await this.courseManagementApiService.getPage(this.courseId, targetRow.payload?.existingPayloadId);\n      if(targetLayer === targetRow.layer) {\n        // when moving an element backwards, after it's current order, within it's siblings list\n        // the resulting \"hole\" in the order needs to be compensated:\n        const orderCompensation = payloadRowIndex < targetRowIndex\n          && payloadRow.layer === targetLayer\n          && payloadRow.parentId === targetRow.parentId;\n        pageToMove.order = pageToCompare.order + (orderCompensation ? 0 : 1);\n        pageToMove.parentId = pageToCompare.parentId;\n      } else if(targetLayer === targetRow.layer + 1) {\n        // move to become the first child of a chapter\n        pageToMove.order = 0;\n        pageToMove.parentId = pageToCompare.id;\n      } else {\n        // for inserting at A: visually - at the end of a chapter in the page-row list AND  B: closer to the root layer\n        // a search backwards to the previous occurrence of target layer is required to extract proper order and parentId:\n        const searchInRows: Array<IDndPacViewDataRow> = this.viewData.rows.slice(0, targetRowIndex).reverse();\n        const index = searchInRows.findIndex(row => row.element.current.layer === targetLayer);\n        if(index > -1) {\n          const currentRow = searchInRows[index].element.current;\n          if(currentRow.payload?.existingPayloadId == null) return;\n          pageToCompare = await this.courseManagementApiService.getPage(this.courseId, currentRow.payload?.existingPayloadId);\n          pageToMove.order = pageToCompare.order + 1;\n          pageToMove.parentId = pageToCompare.parentId;\n        }\n      }\n    }\n    try {\n      // eslint-disable-next-line @typescript-eslint/no-unused-vars\n      const { id, importId, ...pageWithoutId } = pageToMove;\n      await this.contentEditorApi.updatePage(id, pageWithoutId);\n      this.toastService.genericUpdateSuccess('pages.content-editor.addPageOrChapter');\n      await this.updatePages();\n      this.updateViewData();\n    } catch(e) {\n      LogUtils.exception(e);\n      this.toastService.genericUpdateError('pages.content-editor.addPageOrChapter', e);\n      throw e;\n    }\n    this.dndActive = false;\n    this.onDropActive = false;\n  }\n\n  /**\n   * providing only \"from\" will result in a rage from X to X, meaning one specific layer is forbidden.\n   */\n  private setForbiddenLayersRange(from: number, to = from): IDndPacForbiddenLayersSetting {\n    return {\n      mode: DndPacForbiddenLayersMode.Range,\n      range: {\n        from,\n        to,\n      }\n    };\n  }\n\n  private handleChapterCollapseEvent(event: ToggleCollapseEvent): void {\n    const { parentId, collapsed } = event.detail;\n    for(const row of this.getAllSubRows(parentId)) {\n      // toggle visibility of all children\n      row.element.current.visible = !collapsed;\n      // toggle collapsed of parent\n      if(row.element.current.payload?.existingPayloadType === PageType.Chapter) {\n        row.element.current.collapsed = collapsed;\n      }\n    }\n  }\n\n  private getAllSubRows(parentId: number): IDndPacViewDataRow[] {\n    const rows: Array<IDndPacViewDataRow> = [];\n    const subRows = ArrayUtils.immutableSort(\n      this.viewDataRows.filter(p => p.element.current.parentId === parentId),\n      (a, b) => a.element.current.order - b.element.current.order\n    );\n\n    for(const row of subRows) {\n      if(row.element.current.payload?.existingPayloadId && row.element.current.payload.existingPayloadType === PageType.Chapter) {\n        rows.push(...this.getAllSubRows(row.element.current.payload.existingPayloadId));\n      }\n      rows.push(row);\n    }\n    return rows;\n  }\n\n  private handleCreatePageEvent(event: CreatePageEvent): void {\n    const { parentId, order } = event.detail;\n    this.createPage(parentId, order);\n  }\n\n  private async handleUpdatePageEvent(event: UpdatePageEvent): Promise<void> {\n    const { pageId, update, onError } = event.detail;\n    try {\n      await this.contentEditorApi.updatePage(pageId, update);\n      await this.updatePages();\n      this.updateViewData();\n    } catch(e) {\n      LogUtils.exception(e);\n      onError?.();\n    }\n  }\n\n  private async createPage(parentId: number | null, order: number): Promise<void> {\n    const newPage = await this.customDialogService.showAddPageDialog({ parentId, order });\n    if(!newPage) return;\n\n    try {\n      assertNotNullOrUndefined(this.courseId, 'courseId cannot be null or undefined');\n      await this.courseContentManagementApiService.createPage(this.courseId, newPage);\n      this.toastService.success(`pages.content-editor.${newPage.type}AddSuccess`);\n      await this.updatePages();\n      this.updateViewData();\n    } catch(error) {\n      if(error.message === ErrorType.PARENT_PAGE_MUST_BE_CHAPTER) {\n        this.toastService.error(`pages.content-editor.${newPage.type}AddErrorParentNotChapter`);\n      } else {\n        LogUtils.exception(error);\n        this.toastService.error(`pages.content-editor.${newPage.type}AddError`);\n      }\n    }\n  }\n\n  private async deletePage(pageId: number): Promise<void> {\n    await this.contentEditorApi.deletePage(pageId);\n    await this.updatePages();\n    this.updateViewData();\n\n    // if the current page was deleted jump to the root page\n    if(this.contextIdentifier != null) {\n      const state = await this.storeService.getState();\n      const contextState = state.contentContexts[this.contextIdentifier];\n      if(contextState?.type === 'course' && contextState.state.pageId === pageId) {\n        await this.storeService.dispatch(CourseContextMutations.resetToRootPageId, this.contextIdentifier);\n      }\n    }\n  }\n\n  /**\n   * calculate forbidden drag and drop target layers, for a fresh payload\n   */\n  private calcForbiddenDndTargets(): void {\n    const payloadRowIndex = this.viewData.rows.findIndex(row => row.element.current.payload?.existingPayloadId === this.dndPayload?.existingPayloadId);\n    const payloadLayer = this.viewData.rows[payloadRowIndex].element.current.layer;\n    if(payloadRowIndex === 0) {\n      this.firstTargetDisplayMode = DndPacFirstTargetDisplayMode.Hidden;\n    } else {\n      this.firstTargetDisplayMode = DndPacFirstTargetDisplayMode.Allowed;\n\n      // drag and drop target above payload is forbidden only on the same (or lesser) layer as the payload\n      this.viewDataBuffer.rows[payloadRowIndex - 1].targetDisplay.forbiddenLayers = this.setForbiddenLayersRange(payloadLayer);\n      this.viewDataBuffer.rows[payloadRowIndex].targetDisplay.forbiddenLayers = this.setForbiddenLayersRange(payloadLayer, this.viewData.maxLayer);\n\n      // in case an element follows children of its previous sibling, this previous sibling and its first child must forbid\n      // drag and drop to same layer as payload. begin by finding this previous sibling:\n      if(payloadLayer < this.viewData.rows[payloadRowIndex - 1].element.current.layer) {\n        const searchInRows: Array<IDndPacViewDataRow> = this.viewData.rows.slice(0, payloadRowIndex - 1).reverse();\n        const previousSiblingIndexHelper = searchInRows.findIndex(row => row.element.current.layer === payloadLayer);\n        const previousSiblingIndex = this.viewData.rows.findIndex(\n          row => row.element.current.payload?.existingPayloadId === searchInRows[previousSiblingIndexHelper].element.current.payload?.existingPayloadId\n        );\n        this.viewDataBuffer.rows[previousSiblingIndex].targetDisplay.forbiddenLayers = this.setForbiddenLayersRange(0, payloadLayer);\n        this.viewDataBuffer.rows[previousSiblingIndex + 1].targetDisplay.forbiddenLayers = this.setForbiddenLayersRange(0, payloadLayer);\n      }\n    }\n    // drag and drop targets are forbidden for all elements within a chapter (cannot move a parent into itself)\n    if(payloadRowIndex < this.viewData.rows.length - 1 && this.viewData.rows[payloadRowIndex + 1].element.current.layer > payloadLayer) {\n      let helperIndex = this.viewData.rows.slice(payloadRowIndex + 1).findIndex(row => row.element.current.layer <= payloadLayer);\n      helperIndex = helperIndex === -1\n        ? this.viewData.rows.length\n        : Math.min(payloadRowIndex + helperIndex + 1, this.viewData.rows.length);\n\n      for(let i = payloadRowIndex; i < helperIndex; i++) this.viewDataBuffer.rows[i].targetDisplay.forbiddenLayers.mode = DndPacForbiddenLayersMode.All;\n      this.viewDataBuffer.rows[helperIndex - 1].targetDisplay.forbiddenLayers = {\n        mode: DndPacForbiddenLayersMode.Range,\n        range: {\n          from: payloadLayer,\n          to: this.viewData.maxLayer,\n        }\n      };\n    }\n    this.viewData = this.viewDataBuffer;\n    this.refreshFilter();\n  }\n\n  /**\n   * following each drag and drop - drop zone - enter/leave event the target-display-settings need to be set\n   */\n  private calcCurrentDndTargetView(dndInfo: DndActivityEventData<IDndDropzoneConfig>): void {\n    if(!this.dndLastEnteredInfo || !this.dndPayload) return;\n    if(dndInfo.dropTarget.insertModeSelector !== ContentEditingInsertModeSelector.Sequential\n      || dndInfo.dropTarget.insertModeSequential == null\n      || this.dndLastEnteredInfo.insertModeSelector !== ContentEditingInsertModeSelector.Sequential\n      || this.dndLastEnteredInfo.insertModeSequential == null) return;\n\n    const { insertModeSequential: insertMode } = dndInfo.dropTarget;\n    const { superId: eventSourceRow, subId: targetLayer } = dndInfo.dropTarget.identifier;\n    const { insertModeSequential: lastEnteredInsertMode } = this.dndLastEnteredInfo;\n    const { superId: lastEnteredSourceRow } = this.dndLastEnteredInfo.identifier;\n\n    const payloadRowIndex = this.viewData.rows.findIndex(row => row.element.current.payload?.existingPayloadId === this.dndPayload?.existingPayloadId);\n    const eventSourceForbiddenTargets = this.viewData.rows[eventSourceRow].targetDisplay.forbiddenLayers;\n\n    // reset last target\n    if(lastEnteredInsertMode === DndInsertModeSequential.Before) {\n      if(lastEnteredSourceRow === 0) {\n        this.firstTargetDisplayMode = payloadRowIndex === 0 ? this.firstTargetDisplayMode : DndPacFirstTargetDisplayMode.Allowed;\n      } else {\n        this.viewDataBuffer.rows[lastEnteredSourceRow - 1].targetDisplay.isTarget = false;\n      }\n    } else {\n      this.viewDataBuffer.rows[lastEnteredSourceRow].targetDisplay.isTarget = false;\n    }\n    // don't set target for row of payload and previous on the same layer as the target layer\n    // ensures moving up and down in hierarchy is possible, while same layer is forbidden\n    if(targetLayer === this.viewData.rows[payloadRowIndex].element.current.layer\n      && (eventSourceRow === payloadRowIndex\n        || (insertMode === DndInsertModeSequential.Before\n          && eventSourceRow === payloadRowIndex + 1)\n        || (insertMode === DndInsertModeSequential.After\n          && eventSourceRow === payloadRowIndex - 1))) return;\n    if(eventSourceForbiddenTargets.mode === DndPacForbiddenLayersMode.All) return;\n\n    // set current target, depending on targeted row, insertMode and forbiddenLayers\n    if(dndInfo.enterEvent) {\n      if(insertMode === DndInsertModeSequential.Before) {\n        if(eventSourceRow === 0) {\n          this.firstTargetDisplayMode = DndPacFirstTargetDisplayMode.Target;\n        } else {\n          const preEventSourceForbiddenLayers = this.viewData.rows[eventSourceRow - 1].targetDisplay.forbiddenLayers;\n          if(preEventSourceForbiddenLayers.mode === DndPacForbiddenLayersMode.All\n            || (preEventSourceForbiddenLayers.mode === DndPacForbiddenLayersMode.Range\n              && ((targetLayer >= preEventSourceForbiddenLayers.range.from\n                && targetLayer <= preEventSourceForbiddenLayers.range.to)))) return;\n          this.viewDataBuffer.rows[eventSourceRow - 1].targetDisplay.layer = targetLayer;\n          this.viewDataBuffer.rows[eventSourceRow - 1].targetDisplay.isTarget = true;\n        }\n      } else {\n        if(eventSourceForbiddenTargets.mode === DndPacForbiddenLayersMode.Range\n          && (targetLayer >= eventSourceForbiddenTargets.range.from\n            && targetLayer <= eventSourceForbiddenTargets.range.to)) return;\n        this.viewDataBuffer.rows[eventSourceRow].targetDisplay.layer = targetLayer;\n        this.viewDataBuffer.rows[eventSourceRow].targetDisplay.isTarget = true;\n      }\n    }\n  }\n}\n\nexport interface IPage extends IPageEditResponseDto {\n  children: IPageEditResponseDto[];\n}\n\nexport enum PageTypeFilter {\n  All = 'all',\n  LearningOnly = 'learningOnly',\n}\n","import { autoinject, bindable, computedFrom } from 'aurelia-framework';\nimport { StoreService } from 'common/aurelia-services/StoreService';\nimport { ContentEditingInsertModeSelector } from 'common/constants/content.editing.constants';\nimport { CONTENT_EDITOR_PRESENTATION_CONTEXT } from 'common/constants/content.editor.constants';\nimport { ContentElementType } from 'common/dto/content-editor/content.editor.types';\nimport { BlockType } from 'common/dto/course/content/course.types';\nimport * as ContentContextMutations from 'common/store/mutations/content.context.mutations';\nimport { BlockUtils } from 'common/utils/BlockUtils';\nimport { ContentEditorApiService } from '../../../../services/api/ContentEditorApiService';\nimport { CustomDialogService } from '../../../../services/CustomDialogService';\nimport {\n  ALL_DND_IDS,\n  CLIPBOARD_CONTENT_DROPZONE_ID,\n  CLIPBOARD_TRASH_DROPZONE_ID,\n  DndActivityEventData,\n  DndContext,\n  DndDropEventData,\n  DndManager,\n  DndManagerService,\n  DndPayloadUniversal,\n  DndSubContext,\n  IDndDropzoneConfig,\n} from '../../../../services/DndManagerService';\nimport * as ClipboardMutations from '../../../../store/clipboard.mutations';\nimport { IClipboardContentElementLayoutContainer } from '../../../../store/clipboard.selectors';\nimport * as ContentEditorMutations from '../../../../store/content.editor.mutations';\nimport { IState } from '../../../../store/state';\nimport { ContentEditorMode } from '../../../pages/content-editor/content-editor';\n\n@autoinject()\nexport class ContentEditorSidebarBlockClipboard {\n  @bindable() public contextIdentifier = CONTENT_EDITOR_PRESENTATION_CONTEXT;\n  @bindable() public clipboardContent: Array<IClipboardContentElementLayoutContainer> = [];\n\n  private editorMode: ContentEditorMode = ContentEditorMode.PRESENTATION;\n  private dndActiveAndClipboardEnabled = false;\n  private dndActive = false;\n  private dndActiveSourceOutsideClipboard = false;\n  private dndActiveSourceClipboard = false;\n  private dndClipboardContentTargeted = false;\n  private dndClipboardTrashTargeted = false;\n  private dndContext = DndContext.ContentEditorLayout;\n  private BlockType = BlockType;\n  private dndPayload: DndPayloadUniversal<ContentElementType> = null;\n  private dndManager: DndManager<ContentElementType> | null = null;\n  private draggedElements: HTMLElement[] = [];\n  private DndSubContext = DndSubContext;\n  private dndContentDropzoneConfig: IDndDropzoneConfig = {\n    identifier: { superId: CLIPBOARD_CONTENT_DROPZONE_ID, subId: ALL_DND_IDS },\n    subContext: DndSubContext.Clipboard,\n    insertModeSelector: ContentEditingInsertModeSelector.Plain,\n  };\n  private dndTrashDropzoneConfig: IDndDropzoneConfig = {\n    identifier: { superId: CLIPBOARD_TRASH_DROPZONE_ID, subId: ALL_DND_IDS },\n    subContext: DndSubContext.Clipboard,\n    insertModeSelector: ContentEditingInsertModeSelector.Plain,\n  };\n\n  constructor(\n    private storeService: StoreService<IState>,\n    private customDialogService: CustomDialogService,\n    private dndManagerService: DndManagerService,\n    private contentEditorApi: ContentEditorApiService,\n  ) { }\n\n  protected attached(): void {\n    this.dndManager = this.dndManagerService.getDndManager<ContentElementType>(this.dndContext);\n    this.dndManager.subscribePayloadChanged(this, this.dndPayloadChangedHandler.bind(this));\n    this.dndManager.subscribeDrop(this, this.dndOnDropHandler.bind(this), null, DndSubContext.Clipboard);\n    this.dndManager.subscribeToDragActivity(this, this.dndActivityHandler.bind(this), null, DndSubContext.Clipboard);\n\n    this.storeService.subscribeToDetailedChanges(this, [\n      state => state.contentEditor.dragAndDrop.active,\n      state => state.contentEditor.mode\n    ], (_state, active, editorMode) => {\n      this.dndActive = active;\n      this.editorMode = editorMode;\n    });\n  }\n\n  protected detached(): void {\n    this.storeService.disposeAllSubscriptions(this);\n    this.dndManager?.disposeSubscriptions(this);\n  }\n\n  private dndPayloadChangedHandler(payload: DndPayloadUniversal<ContentElementType>): void {\n    this.dndPayload = payload;\n\n    this.dndActiveAndClipboardEnabled = (this.dndPayload != null && (\n      this.dndPayload.excludedTargetSubContexts == null\n      ||(this.dndPayload.excludedTargetSubContexts\n        .findIndex(forbidden => forbidden === DndSubContext.Clipboard) === -1)));\n\n    this.dndActiveSourceOutsideClipboard = payload != null\n      && payload.source === DndSubContext.Content;\n    this.dndActiveSourceClipboard = payload != null\n      && payload.source === DndSubContext.Clipboard;\n  }\n\n  @computedFrom('clipboardContent')\n  private get clipboardEmpty(): boolean {\n    if(this.clipboardContent == null) return false;\n    return this.clipboardContent.length === 0;\n  }\n\n  private async dndOnDropHandler(dropData: DndDropEventData<ContentElementType>): Promise<void> {\n    // stop dnd before BE-API activity\n\n    this.dndClipboardContentTargeted = false;\n    this.dndClipboardTrashTargeted = false;\n\n    if(dropData.dndPayload?.existingPayloadId == null\n      || (dropData.dndPayload.excludedTargetSubContexts != null\n        && (dropData.dndPayload.excludedTargetSubContexts\n          .findIndex(forbidden => forbidden === DndSubContext.Clipboard) !== -1))\n      || dropData.dndPayload?.source === DndSubContext.New\n      || dropData.dropTarget.subContext !== DndSubContext.Clipboard) {\n      this.stopDnd();\n      return;\n    }\n\n    if(dropData.dropTarget.identifier.superId === CLIPBOARD_CONTENT_DROPZONE_ID) {\n      const changes = await this.contentEditorApi.moveElementToClipboard(dropData.dndPayload?.existingPayloadId);\n\n      await this.storeService\n        .pipe(ContentContextMutations.applyLayoutChanges, this.contextIdentifier, changes.sourceLayoutChanges)\n        .pipe(\n          ClipboardMutations.addItem,\n          changes.clipboardNewLayoutContainer,\n          changes.clipboardNewCardContent,\n          changes.clipboardNewBlock,\n        ).pipe(ContentEditorMutations.updateContainerStructure, this.contextIdentifier)\n        .dispatch();\n    } else if(dropData.dropTarget.identifier.superId === CLIPBOARD_TRASH_DROPZONE_ID) {\n      const changes = await this.contentEditorApi.deleteElementFromClipboard(dropData.dndPayload?.existingPayloadId);\n\n      await this.storeService.dispatch(\n        ClipboardMutations.applyRemoveChanges,\n        changes.clipboardLayoutContainerChanges,\n        changes.clipboardCardContentChanges,\n        changes.deletedBlockId,\n      );\n    }\n    this.stopDnd();\n  }\n\n  private stopDnd(): void {\n    this.storeService.dispatch(ContentEditorMutations.updateContainerStylesState, {\n      showExtraContainerSpace: false,\n    });\n    this.storeService\n      .pipe(ContentEditorMutations.updateDragAndDropState, { active: false })\n      .pipe(ContentEditorMutations.resetContainerHoverState)\n      .dispatch();\n  }\n\n  private dndActivityHandler(dndInfo: DndActivityEventData<IDndDropzoneConfig>): void {\n    if(this.dndPayload == null || !this.dndActiveAndClipboardEnabled) {\n      this.dndClipboardContentTargeted = false;\n      return;\n    }\n    this.dndClipboardContentTargeted = dndInfo.enterEvent\n      && this.dndPayload.source === DndSubContext.Content\n      && dndInfo.dropTarget.subContext === DndSubContext.Clipboard\n      && dndInfo.dropTarget.identifier.superId === CLIPBOARD_CONTENT_DROPZONE_ID;\n\n    this.dndClipboardTrashTargeted = dndInfo.enterEvent\n      && this.dndPayload.source === DndSubContext.Clipboard\n      && dndInfo.dropTarget.subContext === DndSubContext.Clipboard\n      && dndInfo.dropTarget.identifier.superId === CLIPBOARD_TRASH_DROPZONE_ID;\n  }\n\n  private async showPreview(layoutContainerId: number): Promise<void> {\n    await this.customDialogService.showLayoutContainerPreviewDialog({\n      layoutContainerId,\n    });\n  }\n\n  private getIconName(elementType: ContentElementType): string {\n    return BlockUtils.getIconName(elementType);\n  }\n\n  private getModifierIconName(elementType: ContentElementType): string | null {\n    return BlockUtils.getModifierIconName(elementType);\n  }\n}\n","import { autoinject, bindable, computedFrom } from 'aurelia-framework';\nimport { selectState } from 'common/aurelia-decorators/select-state';\nimport { StoreService } from 'common/aurelia-services/StoreService';\nimport { ContentElementType, NonBlockElementType } from 'common/dto/content-editor/content.editor.types';\nimport { BlockType } from 'common/dto/course/content/course.types';\nimport { RoleName } from 'common/enum/role.enums';\nimport { Config } from 'common/fe-config/config';\nimport { AppFlavour } from 'common/fe-config/config.interface';\nimport { isIasSelectorCreator } from 'common/store/selectors/course.context.selectors';\nimport { BlockUtils } from 'common/utils/BlockUtils';\nimport {\n  DndContext,\n  DndManager,\n  DndManagerService,\n  DndPayloadUniversal,\n  DndSubContext\n} from '../../../../services/DndManagerService';\nimport { IState } from '../../../../store/state';\nimport { ContentEditorMode } from '../../../pages/content-editor/content-editor';\n\n@autoinject()\nexport class ContentEditorSidebarBlockSelect {\n  @bindable() public contextIdentifier: string | null = null;\n  @selectState(ContentEditorSidebarBlockSelect, isIasSelectorCreator, ['contextIdentifier']) private isIas?: boolean = false;\n\n  private editorMode: ContentEditorMode = ContentEditorMode.PRESENTATION;\n  private dndActive = false;\n  private dndContext = DndContext.ContentEditorLayout;\n  private BlockType = BlockType;\n  private dndPayload: DndPayloadUniversal<ContentElementType> = null;\n  private dndManager: DndManager<ContentElementType> | null = null;\n  private DndPayloadSource = DndSubContext;\n  private isProvider = false;\n\n  constructor(\n    private storeService: StoreService<IState>,\n    private dndManagerService: DndManagerService,\n  ) { }\n\n  protected attached(): void {\n    this.dndManager = this.dndManagerService.getDndManager<ContentElementType>(this.dndContext);\n    this.dndManager.subscribePayloadChanged(this, this.dndPayloadChangedHandler.bind(this));\n\n    this.storeService.subscribeToDetailedChanges(this, [\n      state => state.contentEditor.dragAndDrop.active,\n      state => state.contentEditor.mode,\n      state => state.currentRole?.role === RoleName.Provider,\n    ], (_state, active, editorMode, isProvider) => {\n      this.dndActive = active;\n      this.editorMode = editorMode;\n      this.isProvider = isProvider;\n    });\n  }\n\n  protected detached(): void {\n    this.storeService.disposeAllSubscriptions(this);\n    this.dndManager?.disposeSubscriptions(this);\n  }\n\n  private dndPayloadChangedHandler(payload: DndPayloadUniversal<ContentElementType>): void {\n    this.dndPayload = payload;\n  }\n\n  @computedFrom('BlockType', 'NonBlockElementType', 'editorMode', 'isIas', 'isProvider')\n  private get availableBlockTypes(): ContentElementType[] {\n    if(this.isIas) {\n      return [\n        BlockType.TEXT,\n        BlockType.PICTURE,\n        BlockType.AUDIO,\n        BlockType.VIDEO,\n        BlockType.MP_INTERACTIVE_TEXT,\n        BlockType.MP_QUESTION\n      ];\n    }\n\n    const showAiBlocks = Config().ai.enabled && this.isProvider && this.editorMode === ContentEditorMode.COURSE;\n    return [\n      BlockType.TEXT,\n      ...showAiBlocks ? [NonBlockElementType.AiText] : [],\n      BlockType.PICTURE,\n      BlockType.AUDIO,\n      BlockType.VIDEO,\n      BlockType.MP_TILES,\n      BlockType.FILE,\n      BlockType.SPACER,\n      BlockType.FORMULA,\n      NonBlockElementType.Card,\n      ...(this.editorMode === ContentEditorMode.COURSE ? [\n        BlockType.QUESTION,\n        ...showAiBlocks ? [NonBlockElementType.AiQuiz] : [],\n        BlockType.MP_SEQUENCE_START,\n        ...Config().scorm.enabled && this.isProvider ? [BlockType.MP_SCORM] : [],\n        BlockType.MP_RFG,\n        BlockType.MP_INTERACTIVE_TEXT,\n        BlockType.MP_HOT_SPOT,\n        BlockType.MP_FLIPPING_CARD,\n        ...[AppFlavour.Frp, AppFlavour.Yarrive].includes(Config().appFlavour) ? [BlockType.MP_RISI] : [],\n      ] : [\n        BlockType.MP_QUESTION,\n      ]),\n    ];\n  }\n\n  private getIconName(elementType: ContentElementType): string {\n    return BlockUtils.getIconName(elementType);\n  }\n\n  private getModifierIconName(elementType: ContentElementType): string | null {\n    return BlockUtils.getModifierIconName(elementType);\n  }\n}\n","import { autoinject, bindable } from 'aurelia-framework';\nimport { selectState } from 'common/aurelia-decorators/select-state';\nimport { CONTENT_EDITOR_PRESENTATION_CONTEXT } from 'common/constants/content.editor.constants';\nimport { INavigationTab } from 'resources/elements/navigation-tabs/navigation-tabs';\nimport { clipboardContentSelectorCreator, IClipboardContentElementLayoutContainer } from 'store/clipboard.selectors';\n\n@autoinject()\nexport class ContentEditorSidebarBlocks {\n  @bindable() public contextIdentifier = CONTENT_EDITOR_PRESENTATION_CONTEXT;\n\n  @selectState(ContentEditorSidebarBlocks, clipboardContentSelectorCreator, []) private clipboardContent: Array<IClipboardContentElementLayoutContainer> | null = null;\n  private showBlockClipboard = false;\n  private navigationTabs: INavigationTab[] = [];\n\n  protected attached(): void {\n    this.navigationTabs = [\n      {\n        title: { titleTk: 'pages.content-editor.blocks' },\n        active: !this.showBlockClipboard,\n        callback: () => { this.showBlockClipboard = false; },\n      },\n      {\n        title: { titleTk: 'globals.clipboard' },\n        active: this.showBlockClipboard,\n        callback: () => { this.showBlockClipboard = true; },\n      },\n    ];\n  }\n}\n","import { UceSidebarContent } from './../../../store/state';\nimport { autoinject, bindable, computedFrom } from 'aurelia-framework';\nimport { StoreService } from 'common/aurelia-services/StoreService';\nimport { CONTENT_EDITOR_PRESENTATION_CONTEXT } from 'common/constants/content.editor.constants';\nimport { IState } from 'store/state';\n\n@autoinject()\nexport class ContentEditorSidebarRight {\n  @bindable() public contextIdentifier = CONTENT_EDITOR_PRESENTATION_CONTEXT;\n  private sidebarContent: UceSidebarContent | null = null;\n  private sidebarOpen = false;\n\n  constructor(\n    private storeService: StoreService<IState>,\n  ) { }\n\n  protected attached(): void {\n    this.storeService.subscribeToDetailedChanges(this, [\n      state => state.contentEditor.sidebarRight,\n    ], (_state, sidebarRight) => {\n      this.sidebarContent = sidebarRight.content;\n      this.sidebarOpen = sidebarRight.open;\n    });\n  }\n\n  protected detached(): void {\n    this.storeService.disposeAllSubscriptions(this);\n  }\n\n  @computedFrom('sidebarContent', 'sidebarOpen')\n  private get isSidebarBlocksActive(): boolean | null {\n    return this.sidebarContent === UceSidebarContent.Blocks && this.sidebarOpen;\n  }\n\n  @computedFrom('sidebarContent', 'sidebarOpen')\n  private get isSidebarTemplatesActive(): boolean | null {\n    return this.sidebarContent === UceSidebarContent.Templates && this.sidebarOpen;\n  }\n\n  @computedFrom('sidebarContent', 'sidebarOpen')\n  private get isSidebarInspectorActive(): boolean | null {\n    return this.sidebarContent === UceSidebarContent.Inspector && this.sidebarOpen;\n  }\n}\n","import { autoinject, bindable, computedFrom, ObserverLocator, Parser } from 'aurelia-framework';\nimport { selectState, SelectStateObservableComponent } from 'common/aurelia-decorators/select-state';\nimport { StoreService } from 'common/aurelia-services/StoreService';\nimport { ContentEditingInsertModeSelector } from 'common/constants/content.editing.constants';\nimport { CONTENT_EDITOR_PRESENTATION_CONTEXT } from 'common/constants/content.editor.constants';\nimport {\n  courseIdSelectorCreator,\n  isPageEmptySelectorCreator,\n  isSingleBlockPageSelectorCreator,\n  pageIdSelectorCreator\n} from 'common/store/selectors/course.context.selectors';\nimport {\n  presentationIdSelectorCreator,\n  selectIsSlideEmpty,\n  slideIdSelectorCreator\n} from 'common/store/selectors/presentation.context.selectors';\nimport { ContentEditorApiService } from '../../../../services/api/ContentEditorApiService';\nimport {\n  ALL_DND_IDS,\n  DndActivityEventData,\n  DndContext,\n  DndDropEventData, DndManager,\n  DndManagerService,\n  DndPayloadUniversal,\n  DndSubContext, IDndDropzoneConfig, TEMPLATE_TRASH_DROPZONE_ID\n} from '../../../../services/DndManagerService';\nimport * as ContentEditorMutations from '../../../../store/content.editor.mutations';\nimport { IState } from '../../../../store/state';\nimport * as TemplateMutations from '../../../../store/template.mutations';\nimport { publicTemplatesSelectorCreator, userOwnedTemplatesSelectorCreator } from '../../../../store/template.selectors';\nimport { AppUtils } from 'common/utils/AppUtils';\nimport { INavigationTab } from '../../../elements/navigation-tabs/navigation-tabs';\nimport { ContentEditorMode } from '../../../pages/content-editor/content-editor';\n\n@autoinject()\nexport class ContentEditorSidebarTemplates extends SelectStateObservableComponent {\n  @bindable() public contextIdentifier = CONTENT_EDITOR_PRESENTATION_CONTEXT;\n\n  @selectState(ContentEditorSidebarTemplates, courseIdSelectorCreator, ['contextIdentifier']) private courseId?: number | null;\n  @selectState(ContentEditorSidebarTemplates, presentationIdSelectorCreator, ['contextIdentifier']) private presentationId?: number | null = null;\n  @selectState(ContentEditorSidebarTemplates, pageIdSelectorCreator, ['contextIdentifier']) private selectedPageId?: number | null = null;\n  @selectState(ContentEditorSidebarTemplates, slideIdSelectorCreator, ['contextIdentifier']) private selectedSlideId?: number | null = null;\n  @selectState(ContentEditorSidebarTemplates, isPageEmptySelectorCreator, ['contextIdentifier']) private isPageEmpty?: boolean;\n  @selectState(ContentEditorSidebarTemplates, isSingleBlockPageSelectorCreator, ['contextIdentifier']) private isSingleBlockPage?: boolean;\n  private createTemplateDisabled = false;\n\n  @selectState(ContentEditorSidebarTemplates, publicTemplatesSelectorCreator, []) private publicTemplateIds: Array<number> | null = null;\n  @selectState(ContentEditorSidebarTemplates, userOwnedTemplatesSelectorCreator, []) private userOwnedTemplateIds: Array<number> | null = null;\n  private templateIds: Array<number> = [];\n\n  private showPublicTemplates = true;\n  private navigationTabs: INavigationTab[] = [];\n  private editorMode: ContentEditorMode = ContentEditorMode.PRESENTATION;\n  private dndActive = false;\n  private dndActiveSourceTemplate = false;\n  private dndTemplateTrashTargeted = false;\n  private dndContext = DndContext.ContentEditorLayout;\n  private dndPayload: DndPayloadUniversal<null> = null;\n  private dndManager: DndManager<null> | null = null;\n  private draggedElements: HTMLElement[] = [];\n  private DndSubContext = DndSubContext;\n  private dndTrashDropzoneConfig: IDndDropzoneConfig = {\n    identifier: { superId: TEMPLATE_TRASH_DROPZONE_ID, subId: ALL_DND_IDS },\n    subContext: DndSubContext.Template,\n    insertModeSelector: ContentEditingInsertModeSelector.Plain,\n  };\n\n  constructor(\n    private storeService: StoreService<IState>,\n    private dndManagerService: DndManagerService,\n    private contentEditorApi: ContentEditorApiService,\n    parser: Parser,\n    observerLocator: ObserverLocator,\n  ) {\n    super(parser, observerLocator);\n  }\n\n  protected attached(): void {\n    this.navigationTabs = [\n      {\n        title: { titleText: AppUtils.getAppName() ?? '' },\n        active: this.showPublicTemplates,\n        callback: () => {\n          this.showPublicTemplates = true;\n          this.updateTemplateList();\n        },\n      },\n      {\n        title: { titleTk: 'pages.content-editor.templatesUserOwned' },\n        active: !this.showPublicTemplates,\n        callback: () => {\n          this.showPublicTemplates = false;\n          this.updateTemplateList();\n        },\n      },\n    ];\n\n    this.dndManager = this.dndManagerService.getDndManager<null>(this.dndContext);\n    this.dndManager.subscribePayloadChanged(this, this.dndPayloadChangedHandler.bind(this));\n    this.dndManager.subscribeDrop(this, this.dndOnDropHandler.bind(this), undefined, DndSubContext.Template);\n    this.dndManager.subscribeToDragActivity(this, this.dndActivityHandler.bind(this), undefined, DndSubContext.Template);\n\n    this.storeService.subscribeToDetailedChanges(this, [\n      state => state.contentEditor.dragAndDrop.active,\n      state => state.contentEditor.mode\n    ], (_state, active, editorMode) => {\n      this.dndActive = active;\n      this.editorMode = editorMode;\n    });\n\n    const { contextIdentifier } = this;\n    if(contextIdentifier === CONTENT_EDITOR_PRESENTATION_CONTEXT) {\n      this.storeService.subscribeMemoized(\n        this,\n        selectIsSlideEmpty,\n        isSlideEmpty => {\n          this.createTemplateDisabled = isSlideEmpty;\n        }, contextIdentifier,\n      );\n    }\n  }\n\n  protected detached(): void {\n    this.storeService.disposeAllSubscriptions(this);\n    this.dndManager?.disposeSubscriptions(this);\n  }\n\n  private isPageEmptyChanged(): void {\n    this.updateCreateTemplateDisabled();\n  }\n\n  private isSingleBlockPageChanged(): void {\n    this.updateCreateTemplateDisabled();\n  }\n\n  private updateCreateTemplateDisabled(): void {\n    this.createTemplateDisabled = (this.isSingleBlockPage != null && this.isSingleBlockPage)\n      || (this.isPageEmpty != null && this.isPageEmpty);\n  }\n\n  private publicTemplateIdsChanged(): void {\n    this.updateTemplateList();\n  }\n\n  private userOwnedTemplateIdsChanged(): void {\n    this.updateTemplateList();\n  }\n\n  private updateTemplateList(): void {\n    this.templateIds = this.showPublicTemplates ? this.publicTemplateIds ?? [] : this.userOwnedTemplateIds ?? [];\n  }\n\n  @computedFrom('contextIdentifier', 'showPublicTemplates')\n  private get descriptionTextTk(): string {\n    return `pages.content-editor.template${this.showPublicTemplates ? 'Select' : 'Add'}Description${this.contextIdentifier === CONTENT_EDITOR_PRESENTATION_CONTEXT ? 'Slide' : 'Page'}`;\n  }\n\n  @computedFrom('contextIdentifier')\n  private get addNewTextTk(): string {\n    return `pages.content-editor.templateAddNew${this.contextIdentifier === CONTENT_EDITOR_PRESENTATION_CONTEXT ? 'Slide' : 'Page'}`;\n  }\n\n  private dndPayloadChangedHandler(payload: DndPayloadUniversal<null>): void {\n    this.dndPayload = payload;\n\n    this.dndActiveSourceTemplate = payload != null\n      && payload.source === DndSubContext.Template;\n  }\n\n  private async dndOnDropHandler(dropData: DndDropEventData<null>): Promise<void> {\n    // stop dnd before BE-API activity\n    await this.storeService\n      .pipe(ContentEditorMutations.updateContainerStylesState, { showExtraContainerSpace: false })\n      .pipe(ContentEditorMutations.updateDragAndDropState, { active: false })\n      .pipe(ContentEditorMutations.resetContainerHoverState)\n      .dispatch();\n    this.dndTemplateTrashTargeted = false;\n\n    if(dropData.dndPayload?.existingPayloadId == null\n      || (dropData.dndPayload.excludedTargetSubContexts != null\n        && (dropData.dndPayload.excludedTargetSubContexts\n          .findIndex(forbidden => forbidden === DndSubContext.Template) !== -1))\n      || dropData.dndPayload?.source === DndSubContext.New\n      || dropData.dropTarget.subContext !== DndSubContext.Template\n      || dropData.dropTarget.identifier.superId !== TEMPLATE_TRASH_DROPZONE_ID) return;\n\n    const removedInfos = await this.contentEditorApi.deleteTemplate(dropData.dndPayload.existingPayloadId);\n    await this.storeService.dispatch(TemplateMutations.applyRemoveChanges, removedInfos);\n  }\n\n  private dndActivityHandler(dndInfo: DndActivityEventData<IDndDropzoneConfig>): void {\n    if(this.dndPayload == null) return;\n\n    this.dndTemplateTrashTargeted = dndInfo.enterEvent\n      && this.dndPayload.source === DndSubContext.Template\n      && dndInfo.dropTarget.subContext === DndSubContext.Template\n      && dndInfo.dropTarget.identifier.superId === TEMPLATE_TRASH_DROPZONE_ID;\n  }\n\n  private async onAddTemplate(): Promise<void> {\n    const contentMacroId = this.contextIdentifier === CONTENT_EDITOR_PRESENTATION_CONTEXT\n      ? this.presentationId : this.courseId;\n    const pageOrSlideId = this.contextIdentifier === CONTENT_EDITOR_PRESENTATION_CONTEXT\n      ? this.selectedSlideId : this.selectedPageId;\n    if(contentMacroId == null || pageOrSlideId == null) return;\n    const newTemplateContent = await this.contentEditorApi.addTemplate(contentMacroId, pageOrSlideId);\n    await this.storeService.dispatch(TemplateMutations.addItem, newTemplateContent);\n  }\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/elements/content-editor-sidebar-right/content-editor-sidebar-right'));\n  config.globalResources(PLATFORM.moduleName('resources/elements/content-editor-sidebar-right/content-editor-sidebar-blocks/content-editor-sidebar-blocks'));\n  config.globalResources(PLATFORM.moduleName('resources/elements/content-editor-sidebar-right/content-editor-sidebar-block-select/content-editor-sidebar-block-select'));\n  config.globalResources(PLATFORM.moduleName('resources/elements/content-editor-sidebar-right/content-editor-sidebar-block-clipboard/content-editor-sidebar-block-clipboard'));\n  config.globalResources(PLATFORM.moduleName('resources/elements/content-editor-sidebar-right/content-editor-sidebar-templates/content-editor-sidebar-templates'));\n}\n","import { autoinject, bindable, computedFrom, observable, ObserverLocator, Parser } from 'aurelia-framework';\nimport { selectState, SelectStateObservableComponent } from 'common/aurelia-decorators/select-state';\nimport { StoreService } from 'common/aurelia-services/StoreService';\nimport { ContentEditingInsertModeSelector } from 'common/constants/content.editing.constants';\nimport { DISTINAR_PRESENTATION_CONTEXT } from 'common/constants/content.editor.constants';\nimport { IPresentationSlideDto } from 'common/dto/course/content/slide.interface';\nimport { ICreateSlideConfigurationDto } from 'common/dto/course/management/course.management.interface';\nimport * as ContentContextMutations from 'common/store/mutations/content.context.mutations';\nimport * as CourseContextMutations from 'common/store/mutations/course.context.mutations';\nimport * as PresentationContextMutations from 'common/store/mutations/presentation.context.mutations';\nimport {\n  iasSlideIdSelectorCreator,\n  iasSlideIdsOrderedSelectorCreator,\n  isIasSelectorCreator,\n  pageIdSelectorCreator\n} from 'common/store/selectors/course.context.selectors';\nimport {\n  presentationIdSelectorCreator,\n  slideIdSelectorCreator,\n  slideIdsOrderedSelectorCreator\n} from 'common/store/selectors/presentation.context.selectors';\nimport { assertNotNullOrUndefined } from 'common/utils/assert';\nimport { ContentEditorApiService } from '../../../services/api/ContentEditorApiService';\nimport {\n  DndActivityEventData,\n  DndContext,\n  DndDropEventData,\n  DndInsertModeSequential,\n  DndManager,\n  DndManagerService,\n  DndPayloadUniversal,\n  DndSimpleTargetDisplaySetting,\n  DndSubContext,\n  DndTargetDisplay,\n  IDndDropzoneConfig\n} from '../../../services/DndManagerService';\nimport { ToastService } from '../../../services/ToastService';\nimport * as ContentEditorMutations from '../../../store/content.editor.mutations';\nimport { updateDistinarPresentationResumeData } from '../../../store/lecturer.distinar.mutations';\nimport { IState } from '../../../store/state';\n\n@autoinject()\nexport class ContentEditorSlideSelection extends SelectStateObservableComponent {\n  @bindable() public contextIdentifier: string | null = null;\n  @bindable() public editable = false;\n\n  @selectState(ContentEditorSlideSelection, pageIdSelectorCreator, ['contextIdentifier']) private pageId: number | null = null;\n  @selectState(ContentEditorSlideSelection, isIasSelectorCreator, ['contextIdentifier']) private isIas: boolean = false;\n  @selectState(ContentEditorSlideSelection, slideIdsOrderedSelectorCreator, ['contextIdentifier']) private orderedSlideIds: Array<number> = [];\n  @selectState(ContentEditorSlideSelection, iasSlideIdsOrderedSelectorCreator, ['contextIdentifier']) private orderedIasSlideIds: Array<number> = [];\n  @selectState(ContentEditorSlideSelection, iasSlideIdSelectorCreator, ['contextIdentifier']) private selectedIasSlideId?: number | null = null;\n  @selectState(ContentEditorSlideSelection, slideIdSelectorCreator, ['contextIdentifier']) private selectedSlideId: number | null = null;\n  @selectState(ContentEditorSlideSelection, presentationIdSelectorCreator, ['contextIdentifier']) private presentationId: number | null = null;\n\n  private domElement: HTMLElement;\n\n  @observable() private dndActive = false;\n  private dndContext: DndContext = DndContext.ContentEditorSlides;\n  private dndManager: DndManager<IPresentationSlideDto> | null = null;\n  private dndPayload: DndPayloadUniversal<IPresentationSlideDto> = null;\n  private dndLastEnteredInfo: IDndDropzoneConfig | null = null;\n  private dndTargetDisplaySettings: Array<DndSimpleTargetDisplaySetting> = [];\n  private dndTargetDisplaySettingsBuffer: Array<DndSimpleTargetDisplaySetting> = [];\n  private DndInsertModeSequential = DndInsertModeSequential;\n  private DndTargetDisplay = DndTargetDisplay;\n  private DndPayloadSource = DndSubContext;\n\n  constructor(\n    element: Element,\n    parser: Parser,\n    observerLocator: ObserverLocator,\n    private storeService: StoreService<IState>,\n    private dndManagerService: DndManagerService,\n    private contentEditorApi: ContentEditorApiService,\n    private toastService: ToastService\n  ) {\n    super(parser, observerLocator);\n    this.domElement = element as HTMLElement;\n  }\n\n  public dndPayloadChangedHandler(payload: DndPayloadUniversal<IPresentationSlideDto>): void {\n    this.dndPayload = payload;\n    this.dndActive = payload != null;\n\n    // perform initial target display setting calculation with dummy data\n    if(this.dndPayload != null && this.dndPayload.existingPayloadId != null) {\n      const dropTargetInfo: IDndDropzoneConfig = {\n        identifier: {\n          superId: this.dndPayload.existingPayloadId ?? 0,\n          subId: 0,\n        },\n        insertModeSelector: ContentEditingInsertModeSelector.Sequential,\n        insertModeSequential: DndInsertModeSequential.Before,\n      };\n      this.calcDndTargetDisplaySettings(dropTargetInfo, true);\n    } else this.resetTargetDisplaySettings();\n  }\n\n  public dndDragActivityHandler(dndInfo: DndActivityEventData<IDndDropzoneConfig>): void {\n    if(this.dndPayload == null) return;\n    const config = dndInfo.dropTarget;\n    if(dndInfo && this.dndLastEnteredInfo === null) this.dndLastEnteredInfo = dndInfo.dropTarget;\n    if(dndInfo.enterEvent && this.dndLastEnteredInfo) {\n      this.dndLastEnteredInfo = this.dndLastEnteredInfo !== dndInfo.dropTarget ? dndInfo.dropTarget : this.dndLastEnteredInfo;\n      this.calcDndTargetDisplaySettings(config, false);\n    } else {\n      // calculate display settings when all drop-zone area is left and thus the latest targets need to be reset\n      if(this.dndLastEnteredInfo && this.dndLastEnteredInfo === dndInfo.dropTarget) {\n        this.calcDndTargetDisplaySettings(config, true);\n      }\n    }\n  }\n\n  protected attached(): void {\n    this.dndManager = this.dndManagerService.getDndManager<IPresentationSlideDto>(this.dndContext);\n    this.dndManager.subscribePayloadChanged(this, this.dndPayloadChangedHandler.bind(this));\n    this.dndManager.subscribeToDragActivity(this, this.dndDragActivityHandler.bind(this));\n    this.dndManager.subscribeDrop(this, this.onDrop.bind(this));\n  }\n\n  protected detached(): void {\n    this.dndManager?.disposeSubscriptions(this);\n    this.dndLastEnteredInfo = null;\n  }\n\n  public async onDrop(dropInfo: DndDropEventData<IPresentationSlideDto>): Promise<void> {\n    // TODO: implementation point for differentiated DnDState\n    this.dndActive = false;\n    if(dropInfo.dndPayload == null\n      || dropInfo.dndPayload.existingPayloadId == null\n      || dropInfo.dndPayload.existingPayloadId === dropInfo.dropTarget.identifier.superId\n      || dropInfo.dropTarget.sequentialOrder == null) return;\n    assertNotNullOrUndefined(this.contextIdentifier, 'contextIdentifier cannot be null or undefined');\n\n    if(this.isIas) {\n      const changes = await this.contentEditorApi.moveIasSlide(dropInfo.dndPayload.existingPayloadId, dropInfo.dropTarget.sequentialOrder);\n      await this.storeService.dispatch(CourseContextMutations.applyIasSlideOrderChanges, this.contextIdentifier, changes.updated);\n    } else {\n      const changes = await this.contentEditorApi.moveSlide(dropInfo.dndPayload.existingPayloadId, dropInfo.dropTarget.sequentialOrder);\n      await this.storeService.dispatch(PresentationContextMutations.applySlideOrderChanges, this.contextIdentifier, changes);\n    }\n  }\n\n  private resetTargetDisplaySettings(): void {\n    // reset target displays to be hidden\n    const slides = this.isIas ? this.orderedIasSlideIds : this.orderedSlideIds;\n    this.dndTargetDisplaySettingsBuffer = [];\n    slides.forEach(() => {\n      this.dndTargetDisplaySettingsBuffer.push({\n        before: DndTargetDisplay.Hidden,\n        after: DndTargetDisplay.Hidden,\n      });\n    });\n    this.dndTargetDisplaySettings = this.dndTargetDisplaySettingsBuffer;\n  }\n\n  /**\n   * Slides have an order starting at 1\n   * Index is used equivalent to order (order = index + 1)\n   * Thanks to this similarity aquiring actual slide dtos in here is not required.\n   *\n   * Dropzone config - sequentialOrder for drop information should be\n   * in case of insert before: the same order as the item to insert before\n   * in case of insert after: the order + 1 of the item to insert after\n   */\n  private getDropzoneConfig(\n    index: number,\n    ias: boolean,\n    insertMode: DndInsertModeSequential\n  ): IDndDropzoneConfig {\n    const config: IDndDropzoneConfig = {\n      identifier: {\n        superId: ias ? this.orderedIasSlideIds[index] : this.orderedSlideIds[index],\n        subId: 0,\n      },\n      insertModeSelector: ContentEditingInsertModeSelector.Sequential,\n      insertModeSequential: insertMode,\n      sequentialOrder: insertMode === DndInsertModeSequential.Before ? index + 1 : index + 2,\n    };\n    return config;\n  }\n\n  private calcDndTargetDisplaySettings(dndInfo: IDndDropzoneConfig, resetTargets: boolean): void {\n    const slides = this.isIas ? this.orderedIasSlideIds : this.orderedSlideIds;\n    let targetDisplaySettingBefore = DndTargetDisplay.Hidden;\n    let targetDisplaySettingAfter = DndTargetDisplay.Hidden;\n    this.dndTargetDisplaySettingsBuffer = [];\n    slides.forEach((slide, index) => {\n      targetDisplaySettingBefore = this.dndPayload == null\n        || slide === this.dndPayload.existingPayloadId\n        || (index > 0 && slides[index - 1] === this.dndPayload.existingPayloadId)\n        ? DndTargetDisplay.Hidden\n        : DndTargetDisplay.Allowed;\n      if(targetDisplaySettingBefore === DndTargetDisplay.Allowed) {\n        targetDisplaySettingBefore = !resetTargets\n          && ((index > 0\n            && dndInfo.identifier.superId === slides[index - 1]\n            && dndInfo.insertModeSequential === DndInsertModeSequential.After)\n            || (dndInfo.identifier.superId === slides[index]\n              && dndInfo.insertModeSequential === DndInsertModeSequential.Before))\n          ? DndTargetDisplay.Target\n          : targetDisplaySettingBefore;\n      }\n      targetDisplaySettingAfter = this.dndPayload == null\n        || slide === this.dndPayload.existingPayloadId\n        || (index < slides.length - 1 && slides[index + 1] === this.dndPayload.existingPayloadId)\n        ? DndTargetDisplay.Hidden\n        : DndTargetDisplay.Allowed;\n      if(targetDisplaySettingAfter === DndTargetDisplay.Allowed) {\n        targetDisplaySettingAfter = !resetTargets\n          && ((index < slides.length - 1\n            && dndInfo.identifier.superId === slides[index + 1]\n            && dndInfo.insertModeSequential === DndInsertModeSequential.Before)\n            || (dndInfo.identifier.superId === slides[index]\n              && dndInfo.insertModeSequential === DndInsertModeSequential.After))\n          ? DndTargetDisplay.Target\n          : targetDisplaySettingAfter;\n      }\n      this.dndTargetDisplaySettingsBuffer.push({\n        before: targetDisplaySettingBefore,\n        after: targetDisplaySettingAfter,\n      });\n    });\n    this.dndTargetDisplaySettings = this.dndTargetDisplaySettingsBuffer;\n  }\n\n  private resetInspectorContent(): void {\n    this.storeService.dispatch(ContentEditorMutations.updateInspector, null);\n  }\n\n  private async onSelectSlide(slideId: number): Promise<void> {\n    assertNotNullOrUndefined(this.contextIdentifier, 'contextIdentifier cannot be null or undefined');\n    if(this.isIas) {\n      await this.storeService.dispatch(CourseContextMutations.setIasSlideId, this.contextIdentifier, slideId);\n    } else {\n      await this.storeService.dispatch(PresentationContextMutations.setSlideId, this.contextIdentifier, slideId);\n      if(this.contextIdentifier === DISTINAR_PRESENTATION_CONTEXT) {\n        await this.storeService.dispatch(updateDistinarPresentationResumeData, slideId, this.presentationId);\n      }\n      this.resetInspectorContent();\n    }\n  }\n\n  private async onAddSlide(insertAfterSlideId?: number): Promise<void> {\n    assertNotNullOrUndefined(this.contextIdentifier, 'contextIdentifier cannot be null or undefined');\n    const dto: ICreateSlideConfigurationDto = { referenceSlideId: this.selectedSlideId, insertAfterSlideId };\n    if(this.isIas) {\n      assertNotNullOrUndefined(this.pageId, 'pageId cannot be null or undefined');\n      const changes = await this.contentEditorApi.createEmptyIasSlide(this.pageId, dto);\n      await this.storeService\n        .pipe(\n          CourseContextMutations.addIasSlideAndUpdateOrders,\n          this.contextIdentifier,\n          changes.newSlide,\n          changes.updatedSlideOrders\n        )\n        .pipe(CourseContextMutations.setIasSlideId, this.contextIdentifier, changes.newSlide.id)\n        .dispatch();\n    } else {\n      assertNotNullOrUndefined(this.presentationId, 'presentationId cannot be null or undefined');\n      const changes = await this.contentEditorApi.createEmptySlide(this.presentationId, dto);\n      await this.storeService\n        .pipe(\n          PresentationContextMutations.addSlideAndUpdateOrders,\n          this.contextIdentifier,\n          changes.newSlide,\n          changes.updatedSlideOrders\n        )\n        .pipe(PresentationContextMutations.setSlideId, this.contextIdentifier, changes.newSlide.id)\n        .dispatch();\n      this.resetInspectorContent();\n    }\n  }\n\n  private async onCloneSlide(slideId: number): Promise<void> {\n    assertNotNullOrUndefined(this.contextIdentifier, 'contextIdentifier cannot be null or undefined');\n    if(this.isIas) {\n      assertNotNullOrUndefined(this.pageId, 'pageId cannot be null or undefined');\n      const changes = await this.contentEditorApi.cloneIasSlide(slideId);\n      await this.storeService.dispatch(\n        CourseContextMutations.addIasSlideAndUpdateOrders,\n        this.contextIdentifier,\n        changes.newSlide,\n        changes.updatedSlideOrders\n      );\n      await this.storeService.dispatch(\n        ContentContextMutations.applyLayoutChanges,\n        this.contextIdentifier,\n        changes.layoutChanges\n      );\n    } else {\n      assertNotNullOrUndefined(this.presentationId, 'presentationId cannot be null or undefined');\n      const changes = await this.contentEditorApi.cloneSlide(slideId);\n      await this.storeService.dispatch(\n        PresentationContextMutations.addSlideAndUpdateOrders,\n        this.contextIdentifier,\n        changes.newSlide,\n        changes.updatedSlideOrders\n      );\n      await this.storeService.dispatch(\n        ContentContextMutations.applyLayoutChanges,\n        this.contextIdentifier,\n        changes.layoutChanges\n      );\n      this.resetInspectorContent();\n    }\n  }\n\n  private async onDeleteSlide(slideId: number): Promise<void> {\n    assertNotNullOrUndefined(this.contextIdentifier, 'contextIdentifier cannot be null or undefined');\n    try {\n      const changes = await this.contentEditorApi.deleteSlide(slideId);\n\n      if(this.isIas) {\n        await this.storeService\n          .pipe(CourseContextMutations.applyIasSlideOrderChanges, this.contextIdentifier, changes.updatedSlideOrders)\n          .pipe(ContentContextMutations.applyLayoutChanges, this.contextIdentifier, changes.layoutChanges)\n          .pipe(CourseContextMutations.removeIasSlide, this.contextIdentifier, slideId)\n          .dispatch();\n      } else {\n        await this.storeService\n          .pipe(PresentationContextMutations.applySlideOrderChanges, this.contextIdentifier, { added: [], removedIds: [], updated: changes.updatedSlideOrders })\n          .pipe(ContentContextMutations.applyLayoutChanges, this.contextIdentifier, changes.layoutChanges)\n          .pipe(PresentationContextMutations.removeSlide, this.contextIdentifier, slideId)\n          .dispatch();\n        this.resetInspectorContent();\n      }\n    } catch(e) {\n      this.toastService.error('elements.presentationSideBar.removeSlideError');\n    }\n  }\n\n  @computedFrom('isIas', 'editable')\n  private get styleClasses(): string {\n    const classes: Array<string> = [''];\n    if(this.isIas) classes.push('content-editor-slide-selection--is-ias');\n    if(this.editable) classes.push('content-editor-slide-selection--editable');\n    return classes.join(' ');\n  }\n}\n","import { FrameworkConfiguration, PLATFORM } from 'aurelia-framework';\n\nexport function configure(config: FrameworkConfiguration): void {\n  config.globalResources(PLATFORM.moduleName('resources/elements/content-editor-slide-selection/content-editor-slide-selection'));\n}\n","import { autoinject, bindable, computedFrom } from 'aurelia-framework';\nimport { selectState, SelectStateObservableComponent } from 'common/aurelia-decorators/select-state';\nimport { IPageSlideTemplateDto } from 'common/dto/content-editor/content.editor.interface';\nimport { ContentEditorMode } from 'resources/pages/content-editor/content-editor';\nimport { templateByIdSelectorCreator } from 'store/template.selectors';\n\n@autoinject()\nexport class ContentEditorTemplatePreview extends SelectStateObservableComponent {\n  @bindable() public templateId: number | null = null;\n  @bindable() public editorMode: ContentEditorMode = ContentEditorMode.PRESENTATION;\n\n  @selectState(ContentEditorTemplatePreview, templateByIdSelectorCreator, ['templateId']) private template?: IPageSlideTemplateDto | null;\n\n  @computedFrom('editorMode')\n  private get ratio(): number {\n    if(this.editorMode === ContentEditorMode.COURSE) {\n      return 9 / 16;\n    }\n    return 16 / 9;\n  }\n}\n","import { FrameworkConfiguration, PLATFORM } from 'aurelia-framework';\n\nexport function configure(config: FrameworkConfiguration): void {\n  config.globalResources(PLATFORM.moduleName('resources/elements/content-editor-template-preview/content-editor-template-preview'));\n  config.globalResources(PLATFORM.moduleName('resources/elements/content-editor-template-preview/template-preview-layout-container/template-preview-layout-container'));\n}\n","import { autoinject, bindable, ObserverLocator, Parser } from 'aurelia-framework';\nimport { LayoutStyles } from 'common/aurelia-components/content-editor/universal-layout-container/universal-layout-container';\nimport { selectState, SelectStateObservableComponent } from 'common/aurelia-decorators/select-state';\nimport { SubscriptionManager, SubscriptionManagerService } from 'common/aurelia-services/SubscriptionManagerService';\nimport { ILayoutContainerChildInfo, ITemplateLayoutContainerDto, LayoutContainerDirection } from 'common/dto/course/layout.container.interface';\nimport { layoutContainerByIdSelectorCreator, layoutContainerChildIdsOrderedSelectorCreator, layoutContainerChildInfoOrderedSelectorCreator } from 'store/template.selectors';\n\nenum Mode {\n  Block = 'block',\n  Children = 'children',\n}\n\n@autoinject()\nexport class TemplatePreviewLayoutContainer extends SelectStateObservableComponent {\n  @bindable() public layoutContainerId: number | null = null;\n\n  @selectState(TemplatePreviewLayoutContainer, layoutContainerByIdSelectorCreator, ['layoutContainerId']) private layoutContainer?: ITemplateLayoutContainerDto | null;\n  @selectState(TemplatePreviewLayoutContainer, layoutContainerChildIdsOrderedSelectorCreator, ['layoutContainerId']) private orderedChildIds?: Array<number>;\n  @selectState(TemplatePreviewLayoutContainer, layoutContainerChildInfoOrderedSelectorCreator, ['layoutContainerId']) private orderedChildInfos?: Array<ILayoutContainerChildInfo>;\n  private styleClasses: string | null = null;\n  private layoutStyles: LayoutStyles | null = null;\n  private mode: Mode | null = null;\n  private domElement: HTMLElement;\n  private scaleFactor = 1;\n\n  private subscriptionManager: SubscriptionManager;\n\n  constructor(\n    element: Element,\n    parser: Parser,\n    observerLocator: ObserverLocator,\n    subscriptionManagerService: SubscriptionManagerService,\n  ) {\n    super(parser, observerLocator);\n    this.domElement = element as HTMLElement;\n    this.subscriptionManager = subscriptionManagerService.createSubscriptionManager();\n  }\n\n  protected attached(): void {\n    this.subscribeToResize();\n  }\n\n  protected detached(): void {\n    this.subscriptionManager.disposeSubscriptions();\n  }\n\n  private layoutContainerChanged(): void {\n    this.updateStyles();\n    this.updateContentStyles();\n  }\n\n  private orderedChildInfosChanged(): void {\n    this.updateContentStyles();\n  }\n\n  private updateStyles(): void {\n    const styles = ['template-preview-layout-container'];\n\n    if(this.layoutContainer != null) {\n      this.mode = this.layoutContainer.templateBlockType == null ? Mode.Children : Mode.Block;\n      styles.push(`template-preview-layout-container--content-mode-${this.mode}`);\n\n      const {\n        border,\n        shadow,\n        spacing,\n      } = this.layoutContainer.style;\n      const prefix = 'template-preview-layout-container--style';\n\n      if(border) styles.push(`${prefix}-border`);\n      if(shadow) styles.push(`${prefix}-shadow`);\n      if(spacing) styles.push(`${prefix}-spacing`);\n\n      if(this.domElement.isConnected) {\n        const { backgroundColor } = this.layoutContainer.style;\n        this.domElement.style.setProperty('--template-preview-layout-container-background-color', backgroundColor != null\n          ? backgroundColor\n          : 'transparent');\n      }\n    }\n    this.styleClasses = styles.join(' ');\n  }\n\n  /**\n   * for templates content mode is always extrinsic as we don't have any intrinsic block sizes\n   */\n  private updateContentStyles(): void {\n    if(this.layoutContainer == null || this.orderedChildInfos == null) {\n      this.layoutStyles = null;\n      return;\n    }\n\n    const { templateBlockType, layout: { direction } } = this.layoutContainer;\n\n    if(templateBlockType != null) {\n      this.layoutStyles = null;\n      return;\n    }\n\n    const horizontal = direction === LayoutContainerDirection.Horizontal;\n    const gridValue = this.orderedChildInfos.map(childInfo => {\n      const adjustedSize = Math.abs(childInfo.itemSize).toFixed(0);\n      return `minmax(0, ${adjustedSize}fr)`;\n    }).join(' ');\n\n    this.layoutStyles = {\n      'grid-template-columns': horizontal ? gridValue : '1fr',\n      'grid-template-rows': horizontal ? '1fr' : gridValue,\n    };\n  }\n\n  private subscribeToResize(): void {\n    if(!this.domElement.isConnected) return;\n    this.subscriptionManager.disposeSubscriptions();\n\n    this.subscriptionManager.subscribeToElementResize(this.domElement, this.updateScaleFactor.bind(this));\n  }\n\n  private updateScaleFactor(): void {\n    const width = parseFloat(window.getComputedStyle(this.domElement).width);\n    this.scaleFactor = width / 1920; // magic number, by design everything has normal/default size when the slide is 1920px wide\n  }\n}\n","import { ICourseGroupResponseDto } from 'common/dto/course/course.group.interface';\nimport { ICourseEditResponseDto } from 'common/dto/course/management/course.management.interface';\nimport { autoinject, bindable } from 'aurelia-framework';\nimport { DomUtils } from 'common/utils/DomUtils';\n\n/**\n * @event {RemoveCourseAndGroupClickedEvent} remove-course-and-group-clicked\n */\n@autoinject()\nexport class CourseAndGroupListItem {\n  @bindable() public course: ICourseEditResponseDto | null = null;\n  @bindable() public courseGroup: ICourseGroupResponseDto<string> | null = null;\n  @bindable() public removable: boolean = true;\n  private domElement: HTMLElement;\n\n  constructor(\n    element: Element,\n  ) {\n    this.domElement = element as HTMLElement;\n  }\n\n  private handleRemoveCourseAndGroupClick(): void {\n    DomUtils.fireEvent(this.domElement, {\n      name: 'remove-course-and-group-clicked',\n      bubbles: false,\n      detail: {\n        course: this.course,\n        courseGroup: this.courseGroup\n      }\n    });\n  }\n}\n\nexport type RemoveCourseAndGroupClickedEvent = CustomEvent<{\n  course: ICourseEditResponseDto | null;\n  courseGroup: ICourseGroupResponseDto<string> | null;\n}>;","import { autoinject, bindable, computedFrom } from 'aurelia-framework';\nimport { ICourseGroupResponseDto } from 'common/dto/course/course.group.interface';\nimport { ICourseEditResponseDto } from 'common/dto/course/management/course.management.interface';\nimport { ArrayUtils } from 'common/utils/ArrayUtils';\nimport { I18N } from 'aurelia-i18n';\nimport { assertNotNullOrUndefined } from 'common/utils/assert';\nimport { DomUtils } from 'common/utils/DomUtils';\nimport { ValidationUtils } from '../../../utils/ValidationUtils';\nimport { TenantApiService } from '../../../services/api/TenantApiService';\nimport { StudentService } from '../../../services/StudentService';\nimport { DateService } from '../../../services/DateService';\nimport { DEFAULT_MAX_DAYS_TO_EXAM, DEFAULT_MIN_DAYS_TO_EXAM } from 'common/constants/course.constants';\nimport { LearnGoalUtils } from 'common/utils/LearnGoalUtils';\n\ninterface ICourseWithAvailability extends ICourseEditResponseDto {\n  info: string;\n  available: boolean;\n}\n\n/**\n * @event {AddCourseAndGroupClickedEvent} add-course-and-group-clicked\n */\n@autoinject()\nexport class CourseAndGroupSelect {\n  @bindable() public unavailableCourseIds: number[] = [];\n  @bindable() public examDate: string | null = null;\n  private allCourses: ICourseEditResponseDto[] = [];\n  private availableCourses: Array<ICourseWithAvailability> = [];\n  private availableCourseCourseGroups: Array<ICourseGroupResponseDto<string>> = [];\n  private selectedCourse: ICourseEditResponseDto | null = null;\n  private selectedCourseGroup: ICourseGroupResponseDto<string> | null = null;\n  private examDateError: string | null = null;\n\n  private noGroupInCourse = false;\n  private domElement: HTMLElement;\n\n  constructor(\n    private tenantApi: TenantApiService,\n    private studentService: StudentService,\n    private dateService: DateService,\n    private i18N: I18N,\n    element: Element,\n  ) {\n    this.domElement = element as HTMLElement;\n  }\n\n  protected async attached(): Promise<void> {\n    this.allCourses = await this.tenantApi.getAvailableRegularCourses();\n    this.updateAvailableCourses();\n  }\n\n  private unavailableCourseIdsChanged(): void {\n    if(!this.domElement.isConnected) return;\n    this.updateAvailableCourses();\n  }\n\n  private async updateAvailableCourses(): Promise<void> {\n    const coursesWithAvailability: ICourseWithAvailability[] = this.allCourses.map(c => {\n      const available = !this.unavailableCourseIds.includes(c.id);\n      const infoText = this.i18N.tr('dialogs.invitationCreate.alreadyParticipant');\n      return {\n        ...c,\n        available,\n        info: `${c.title} (${infoText})`,\n      };\n    });\n\n    this.availableCourses = ArrayUtils.immutableSort(coursesWithAvailability, (a, b) => {\n      const aAvailable = a.available ? -1 : 1;\n      const bAvailable = b.available ? -1 : 1;\n      return aAvailable - bAvailable || a.title.localeCompare(b.title);\n    });\n\n    if(!this.availableCourses.length) return;\n    this.selectedCourse = this.isInvitedToAllCourses ? null : this.availableCourses[0];\n    await this.handleSelectedCourseChanged(this.availableCourses[0].id, null);\n  }\n\n  private async handleSelectedCourseChanged(courseId: number, createdCourseGroup: ICourseGroupResponseDto<string> | null = null): Promise<void> {\n    this.availableCourseCourseGroups = [];\n    this.noGroupInCourse = true;\n    this.selectedCourseGroup = null;\n    this.availableCourseCourseGroups = await this.getCourseGroupsForCourse(courseId);\n    if(!this.availableCourseCourseGroups.length && createdCourseGroup) {\n      this.availableCourseCourseGroups.push(createdCourseGroup);\n    }\n    this.availableCourseCourseGroups = ArrayUtils.immutableSort(this.availableCourseCourseGroups, (a, b) => a.name.localeCompare(b.name));\n    this.noGroupInCourse = !this.availableCourseCourseGroups.length;\n  }\n\n  private async getCourseGroupsForCourse(courseId: number): Promise<Array<ICourseGroupResponseDto<string>>> {\n    const filterDate = this.dateService.formatDateForApiFilter(new Date());\n    return this.tenantApi.getAvailableCourseGroups([courseId], filterDate);\n  }\n\n  private async handleCreateCourseGroupClick(): Promise<void> {\n    const { selectedCourse } = this;\n    assertNotNullOrUndefined(selectedCourse, 'can not create a courseGroup without a course');\n    const createdCourseGroup = await this.studentService.createCourseGroup([selectedCourse]);\n    if(createdCourseGroup?.to && !this.dateService.isInPast(createdCourseGroup?.to)) {\n      await this.handleSelectedCourseChanged(selectedCourse.id, createdCourseGroup);\n    }\n  }\n\n  private handleAddCourseAndGroupClick(): void {\n    this.examDateError = ValidationUtils.isExamDateValid(this.examDate, this.selectedCourse?.minDaysToExam ?? DEFAULT_MIN_DAYS_TO_EXAM, this.maxDaysToExam);\n    if(this.examDateError) return;\n\n    DomUtils.fireEvent(this.domElement, {\n      name: 'add-course-and-group-clicked',\n      bubbles: false,\n      detail: {\n        selectedCourse: this.selectedCourse,\n        examDate: this.examDate,\n        selectedCourseGroup: this.selectedCourseGroup\n      }\n    });\n  }\n\n  @computedFrom('availableCourses.length')\n  private get isInvitedToAllCourses(): boolean {\n    return this.availableCourses.every(c => !c.available);\n  }\n\n  @computedFrom('selectedCourse')\n  private get maxDaysToExam(): number {\n    if(!this.selectedCourse) return DEFAULT_MAX_DAYS_TO_EXAM;\n    return LearnGoalUtils.getDaysToExamMaximumFromExpirationInMonths(this.selectedCourse?.accessCodeExpirationInMonths);\n  }\n}\n\nexport type AddCourseAndGroupClickedEvent = CustomEvent<{\n  selectedCourse: ICourseEditResponseDto | null;\n  examDate: string | null;\n  selectedCourseGroup: ICourseGroupResponseDto<string> | null;\n}>;"],"names":[],"sourceRoot":""}