{"version":3,"file":"app-2a47f399.f584a1446b2fc034d810.bundle.js","mappings":"AAGA","sources":["webpack://lernplattform-admin-frontend/../common/store/actions/cache.actions.ts","webpack://lernplattform-admin-frontend/../common/store/actions/chat.actions.ts","webpack://lernplattform-admin-frontend/../common/store/actions/distinar.actions.ts","webpack://lernplattform-admin-frontend/../common/store/configure.ts","webpack://lernplattform-admin-frontend/../common/store/initial.state.ts","webpack://lernplattform-admin-frontend/../common/store/middlewares/content-performance-data.middleware.ts","webpack://lernplattform-admin-frontend/../common/store/middlewares/store-storage.middleware.ts","webpack://lernplattform-admin-frontend/../common/store/middlewares/update-rate.middleware.ts","webpack://lernplattform-admin-frontend/../common/store/mutations/conference.context.mutations.ts","webpack://lernplattform-admin-frontend/../common/store/mutations/content.context.mutations.ts","webpack://lernplattform-admin-frontend/../common/store/mutations/course.content.mutations.ts","webpack://lernplattform-admin-frontend/../common/store/mutations/course.context.mutations.ts","webpack://lernplattform-admin-frontend/../common/store/mutations/distinar.connection.mutations.ts","webpack://lernplattform-admin-frontend/../common/store/mutations/distinar.mutations.ts","webpack://lernplattform-admin-frontend/../common/store/mutations/mutations.ts","webpack://lernplattform-admin-frontend/../common/store/mutations/presentation.content.mutations.ts","webpack://lernplattform-admin-frontend/../common/store/mutations/presentation.context.mutations.ts","webpack://lernplattform-admin-frontend/../common/store/selectors/conference.context.selectors.ts","webpack://lernplattform-admin-frontend/../common/store/selectors/content.context.selectors.ts","webpack://lernplattform-admin-frontend/../common/store/selectors/course.context.selectors.ts","webpack://lernplattform-admin-frontend/../common/store/selectors/distinar.connection.selectors.ts","webpack://lernplattform-admin-frontend/../common/store/selectors/presentation.context.selectors.ts","webpack://lernplattform-admin-frontend/../common/store/selectors/selectors.ts","webpack://lernplattform-admin-frontend/../common/store/selectors/user.course.interaction.selectors.ts","webpack://lernplattform-admin-frontend/../common/utils/AnimationUtils.ts","webpack://lernplattform-admin-frontend/../common/utils/ApiQueryStringUtils.ts","webpack://lernplattform-admin-frontend/../common/utils/AppUtils.ts","webpack://lernplattform-admin-frontend/../common/utils/ApplicationErrorUtils.ts","webpack://lernplattform-admin-frontend/../common/utils/ArrayUtils.ts","webpack://lernplattform-admin-frontend/../common/utils/BlockUtils.ts","webpack://lernplattform-admin-frontend/../common/utils/BrowserUtils.ts","webpack://lernplattform-admin-frontend/../common/utils/BunnyUtils.ts"],"sourcesContent":["import { ICourseIntersection } from 'common/dto/user/user.management.interface';\nimport { ICommonState } from '../store.interface';\n\nexport function addCourseIntersectionToCache<T extends ICommonState>(state: T, userId: number, intersection: Array<ICourseIntersection>): T {\n  return {\n    ...state,\n    cache: {\n      ...state.cache,\n      courseIntersectionByUserId: {\n        ...state.cache.courseIntersectionByUserId,\n        [userId]: intersection,\n      },\n    },\n  };\n}\n","import { IChatRoomConversationDto, IChatRoomIdWithUsersDto, IUnreadChatRoomDto } from 'common/dto/user/chat/chat.interface';\nimport { ICommonState } from '../store.interface';\n\nexport function addChatRoomIdWithUsersToCache<T extends ICommonState>(state: T, chatRoomId: number, chatRoom: IChatRoomIdWithUsersDto<string>): T {\n  return {\n    ...state,\n    cache: {\n      ...state.cache,\n      chatRoomIdWithUsersByChatRoomId: {\n        ...state.cache.chatRoomIdWithUsersByChatRoomId,\n        [chatRoomId]: chatRoom,\n      },\n    },\n  };\n}\n\nexport function setLatestConversationInCache<T extends ICommonState>(state: T, chatRoomId: number, conversation: IChatRoomConversationDto<string>): T {\n  return {\n    ...state,\n    cache: {\n      ...state.cache,\n      conversationsByChatRoomId: {\n        ...state.cache.conversationsByChatRoomId,\n        [chatRoomId]: {\n          currentPreviousMessagesOffset: 0,\n          hasPrevious: conversation.messages.count > conversation.messages.rows.length,\n          partialConversations: {\n            0: conversation.messages,\n          },\n          event: conversation.event,\n          users: conversation.users,\n        },\n      },\n    },\n  };\n}\n\nexport function setPreviousConversationInCache<T extends ICommonState>(state: T, chatRoomId: number, conversation: IChatRoomConversationDto<string>): T {\n  const newMessagesOffset = (state.cache.conversationsByChatRoomId[chatRoomId]?.currentPreviousMessagesOffset ?? 0) + 1;\n  return {\n    ...state,\n    cache: {\n      ...state.cache,\n      conversationsByChatRoomId: {\n        ...state.cache.conversationsByChatRoomId,\n        [chatRoomId]: {\n          ...state.cache.conversationsByChatRoomId[chatRoomId],\n          currentPreviousMessagesOffset: newMessagesOffset,\n          hasPrevious: conversation.messages.count > (25 * (newMessagesOffset + 1)),\n          partialConversations: {\n            ...state.cache.conversationsByChatRoomId[chatRoomId]?.partialConversations,\n            [newMessagesOffset]: conversation.messages,\n          },\n          event: conversation.event,\n          users: conversation.users,\n        },\n      },\n    },\n  };\n}\n\nexport function setAllChatRoomsCache<T extends ICommonState>(state: T, chatRooms: IChatRoomIdWithUsersDto<string>[]): T {\n  return {\n    ...state,\n    cache: {\n      ...state.cache,\n      chatRooms,\n    },\n  };\n}\n\nexport function readConversationWithChatRoomId<T extends ICommonState>(state: T, chatRoomId: number): T {\n  return {\n    ...state,\n    chat: {\n      ...state.chat,\n      unreadChatRooms: state.chat.unreadChatRooms.filter(room => room.id !== chatRoomId),\n    },\n  };\n}\n\nexport function setUnreadChatRooms<T extends ICommonState>(state: T, chatRooms: Array<IUnreadChatRoomDto>): T {\n  return {\n    ...state,\n    chat: {\n      ...state.chat,\n      unreadChatRooms: chatRooms,\n    },\n  };\n}\n","import { Container } from 'aurelia-dependency-injection';\nimport { PipedDispatch, Store } from 'aurelia-store';\nimport { IAttendeeDto, ISafeAttendeeDto } from 'common/dto/distinar/distinar.interface';\nimport * as Mutations from '../mutations/distinar.mutations';\nimport * as ConferenceContextMutations from '../mutations/conference.context.mutations';\nimport { ICommonState } from '../store.interface';\n\nfunction getStore(): Store<ICommonState> {\n  return Container.instance.get(Store) as Store<ICommonState>;\n}\n\nexport async function updateAttendees<T extends ICommonState>(\n  attendees: Array<IAttendeeDto | ISafeAttendeeDto>,\n  pipedDispatch?: PipedDispatch<T>,\n): Promise<void> {\n  const store = getStore();\n  await (pipedDispatch ?? store).pipe(Mutations.updateAttendees, attendees)\n    .pipe(ConferenceContextMutations.updateBindingContexts)\n    .dispatch();\n}\n","import { Store } from 'aurelia-store';\nimport * as CacheMutations from './actions/cache.actions';\nimport * as ChatMutations from './actions/chat.actions';\nimport * as ConferenceMutations from './mutations/conference.context.mutations';\nimport * as ContentContextMutations from './mutations/content.context.mutations';\nimport * as CourseContentMutations from './mutations/course.content.mutations';\nimport * as CourseContextMutations from './mutations/course.context.mutations';\nimport * as DistinarConnectionMutations from './mutations/distinar.connection.mutations';\nimport * as DistinarMutations from './mutations/distinar.mutations';\nimport * as OtherMutations from './mutations/mutations';\nimport * as PresentationContentMutations from './mutations/presentation.content.mutations';\nimport * as PresentationContextMutations from './mutations/presentation.context.mutations';\nimport { ICommonState } from './store.interface';\n\nexport function registerCommonStoreActions<T extends ICommonState>(store: Store<T>): void {\n  // chat\n  store.registerAction('CHAT:addChatRoomIdWithUsersToCache', ChatMutations.addChatRoomIdWithUsersToCache);\n  store.registerAction('CHAT:readConversationWithChatRoomId', ChatMutations.readConversationWithChatRoomId);\n  store.registerAction('CHAT:setAllChatRoomsCache', ChatMutations.setAllChatRoomsCache);\n  store.registerAction('CHAT:setLatestConversationInCache', ChatMutations.setLatestConversationInCache);\n  store.registerAction('CHAT:setPreviousConversationInCache', ChatMutations.setPreviousConversationInCache);\n  store.registerAction('CHAT:setUnreadChatRooms', ChatMutations.setUnreadChatRooms);\n  // distinar\n  store.registerAction('DISTINAR:markDistinarChatConversationAsRead', DistinarMutations.markDistinarChatConversationAsRead);\n  store.registerAction('DISTINAR:receivedDistinarChatMessage', DistinarMutations.receivedDistinarChatMessage);\n  store.registerAction('DISTINAR:setConferenceAttendeeInfo', DistinarMutations.setConferenceAttendeeInfo);\n  store.registerAction('DISTINAR:markInitializationFinished', DistinarMutations.markInitializationFinished);\n  store.registerAction('DISTINAR:setConferenceShareScreen', DistinarMutations.setConferenceShareScreen);\n  store.registerAction('DISTINAR:setConferenceStreamAudio', DistinarMutations.setConferenceStreamAudio);\n  store.registerAction('DISTINAR:setConferenceStreamVideo', DistinarMutations.setConferenceStreamVideo);\n  store.registerAction('DISTINAR:setSelectedAudioInputDevice', DistinarMutations.setSelectedAudioInputDevice);\n  store.registerAction('DISTINAR:setSelectedAudioOutputDevice', DistinarMutations.setSelectedAudioOutputDevice);\n  store.registerAction('DISTINAR:setSelectedVideoInputDevice', DistinarMutations.setSelectedVideoInputDevice);\n  store.registerAction('DISTINAR:markDistinarChatConversationAsRead', DistinarMutations.markDistinarChatConversationAsRead);\n  store.registerAction('DISTINAR:receivedDistinarChatMessage', DistinarMutations.receivedDistinarChatMessage);\n  store.registerAction('DISTINAR:setConferenceAttendeeInfo', DistinarMutations.setConferenceAttendeeInfo);\n  store.registerAction('DISTINAR:updateActiveLecture', DistinarMutations.updateActiveLecture);\n  store.registerAction('DISTINAR:updateAttendees', DistinarMutations.updateAttendees);\n  store.registerAction('DISTINAR:updateStatus', DistinarMutations.updateStatus);\n  // distinar connection\n  store.registerAction('DISTINAR_CONNECTION:disconnected', DistinarConnectionMutations.disconnected);\n  store.registerAction('DISTINAR_CONNECTION:failedConnecting', DistinarConnectionMutations.failedConnecting);\n  store.registerAction('DISTINAR_CONNECTION:failedConnectionAttempt', DistinarConnectionMutations.failedConnectionAttempt);\n  store.registerAction('DISTINAR_CONNECTION:finishedConnecting', DistinarConnectionMutations.finishedConnecting);\n  store.registerAction('DISTINAR_CONNECTION:finishedDisconnecting', DistinarConnectionMutations.finishedDisconnecting);\n  store.registerAction('DISTINAR_CONNECTION:resetConnectionState', DistinarConnectionMutations.resetConnectionState);\n  store.registerAction('DISTINAR_CONNECTION:setData', DistinarConnectionMutations.setData);\n  store.registerAction('DISTINAR_CONNECTION:startConnectionAttempt', DistinarConnectionMutations.startConnectionAttempt);\n  store.registerAction('DISTINAR_CONNECTION:startDisconnecting', DistinarConnectionMutations.startDisconnecting);\n  // cache\n  store.registerAction('CACHE:addCourseIntersectionToCache', CacheMutations.addCourseIntersectionToCache);\n  // conference\n  store.registerAction('CONFERENCE:addOrUpdateTileState', ConferenceMutations.addOrUpdateTileState);\n  store.registerAction('CONFERENCE:removeTileState', ConferenceMutations.removeTileState);\n  store.registerAction('CONFERENCE:setActiveSpeakers', ConferenceMutations.setActiveSpeakers);\n  store.registerAction('CONFERENCE:setActiveSpeakerScores', ConferenceMutations.setActiveSpeakerScores);\n  store.registerAction('CONFERENCE:setHasActiveMeeting', ConferenceMutations.setHasActiveMeeting);\n  store.registerAction('CONFERENCE:setOnScreenScores', ConferenceMutations.setOnScreenScores);\n  store.registerAction('CONFERENCE:updateAttendeeScores', ConferenceMutations.updateAttendeeScores);\n  store.registerAction('CONFERENCE:updateBindingContexts', ConferenceMutations.updateBindingContexts);\n  store.registerAction('CONFERENCE:resetConferenceState', ConferenceMutations.resetConferenceState);\n  store.registerAction('CONFERENCE:resetConferenceStateWithoutConfiguration', ConferenceMutations.resetConferenceStateWithoutConfiguration);\n  store.registerAction('CONFERENCE:connectionDidBecomeGood', ConferenceMutations.connectionDidBecomeGood);\n  store.registerAction('CONFERENCE:connectionDidBecomePoor', ConferenceMutations.connectionDidBecomePoor);\n  // binding\n  // conference binding\n  store.registerAction('CONFERENCE:decrementElementCountForAttendeeId', ConferenceMutations.decrementElementCountForAttendeeId);\n  store.registerAction('CONFERENCE:incrementElementCountForAttendeeId', ConferenceMutations.incrementElementCountForAttendeeId);\n  store.registerAction('CONFERENCE:removeBoundTileForAttendeeId', ConferenceMutations.removeBoundTileForAttendeeId);\n  store.registerAction('CONFERENCE:removeVideoElementForTileId', ConferenceMutations.removeVideoElementForTileId);\n  store.registerAction('CONFERENCE:setBoundTileForAttendeeId', ConferenceMutations.setBoundTileForAttendeeId);\n  store.registerAction('CONFERENCE:setVideoElementForTileId', ConferenceMutations.setVideoElementForTileId);\n  store.registerAction('CONFERENCE:removeBoundTileForAttendeeId', ConferenceMutations.removeBoundTileForAttendeeId);\n  store.registerAction('CONFERENCE:incrementElementCountForAttendeeId', ConferenceMutations.incrementElementCountForAttendeeId);\n  store.registerAction('CONFERENCE:decrementElementCountForAttendeeId', ConferenceMutations.decrementElementCountForAttendeeId);\n  store.registerAction('CONFERENCE:setMatrixItemCount', ConferenceMutations.setMatrixItemCount);\n  store.registerAction('CONFERENCE:setMatrixAutoResize', ConferenceMutations.setMatrixAutoResize);\n  store.registerAction('CONFERENCE:updateHideMenus', ConferenceMutations.updateHideMenus);\n\n  // course content\n  store.registerAction('COURSE_CONTENT:setContent', CourseContentMutations.setContent);\n\n  // course context\n  store.registerAction('COURSE_CONTEXT:addIasSlide', CourseContextMutations.addIasSlide);\n  store.registerAction('COURSE_CONTEXT:addIasSlide', CourseContextMutations.addIasSlide);\n  store.registerAction('COURSE_CONTEXT:addIasSlideAndUpdateOrders', CourseContextMutations.addIasSlideAndUpdateOrders);\n  store.registerAction('COURSE_CONTEXT:applyIasSlideOrderChanges', CourseContextMutations.applyIasSlideOrderChanges);\n  store.registerAction('COURSE_CONTEXT:navigateBack', CourseContextMutations.navigateBack);\n  store.registerAction('COURSE_CONTEXT:navigateForward', CourseContextMutations.navigateForward);\n  store.registerAction('COURSE_CONTEXT:removeIasSlide', CourseContextMutations.removeIasSlide);\n  store.registerAction('COURSE_CONTEXT:removeIasSlide', CourseContextMutations.removeIasSlide);\n  store.registerAction('COURSE_CONTEXT:resetToRootPageId', CourseContextMutations.resetToRootPageId);\n  store.registerAction('COURSE_CONTEXT:setIasSlideId', CourseContextMutations.setIasSlideId);\n  store.registerAction('COURSE_CONTEXT:setInitialState', CourseContextMutations.setInitialState);\n  store.registerAction('COURSE_CONTEXT:setPageId', CourseContextMutations.setPageId);\n  store.registerAction('COURSE_CONTEXT:updatePage', CourseContextMutations.updatePage);\n\n  // presentation content\n  store.registerAction('PRESENTATION_CONTENT:setContent', PresentationContentMutations.setContent);\n\n  // presentation context\n  store.registerAction('PRESENTATION_CONTEXT:addFirstBlockOnSlide', PresentationContextMutations.addFirstBlockOnSlide);\n  store.registerAction('PRESENTATION_CONTEXT:addFirstBlockOnSlide', PresentationContextMutations.addFirstBlockOnSlide);\n  store.registerAction('PRESENTATION_CONTEXT:addSlide', PresentationContextMutations.addSlide);\n  store.registerAction('PRESENTATION_CONTEXT:addSlideAndUpdateOrders', PresentationContextMutations.addSlideAndUpdateOrders);\n  store.registerAction('PRESENTATION_CONTEXT:applySlideOrderChanges', PresentationContextMutations.applySlideOrderChanges);\n  store.registerAction('PRESENTATION_CONTEXT:navigateBack', PresentationContextMutations.navigateBack);\n  store.registerAction('PRESENTATION_CONTEXT:navigateForward', PresentationContextMutations.navigateForward);\n  store.registerAction('PRESENTATION_CONTEXT:removeSlide', PresentationContextMutations.removeSlide);\n  store.registerAction('PRESENTATION_CONTEXT:setFullscreenPictureId', PresentationContextMutations.setFullscreenPictureId);\n  store.registerAction('PRESENTATION_CONTEXT:setHasControls', PresentationContextMutations.setHasControls);\n  store.registerAction('PRESENTATION_CONTEXT:setInitialState', PresentationContextMutations.setInitialState);\n  store.registerAction('PRESENTATION_CONTEXT:setIsFullscreen', PresentationContextMutations.setIsFullscreen);\n  store.registerAction('PRESENTATION_CONTEXT:setPresentationId', PresentationContextMutations.setPresentationId);\n  store.registerAction('PRESENTATION_CONTEXT:setRevealedAnswerBlockId', PresentationContextMutations.setRevealedAnswerBlockId);\n  store.registerAction('PRESENTATION_CONTEXT:setShowWhiteboard', PresentationContextMutations.setShowWhiteboard);\n  store.registerAction('PRESENTATION_CONTEXT:setSlideHeading', PresentationContextMutations.setSlideHeading);\n  store.registerAction('PRESENTATION_CONTEXT:setSlideId', PresentationContextMutations.setSlideId);\n  store.registerAction('PRESENTATION_CONTEXT:setSlideSubheading', PresentationContextMutations.setSlideSubheading);\n  store.registerAction('PRESENTATION_CONTEXT:updateSlide', PresentationContextMutations.updateSlide);\n\n  // universal content context\n  store.registerAction('CONTENT_CONTEXT:applyLayoutChanges', ContentContextMutations.applyLayoutChanges);\n  store.registerAction('CONTENT_CONTEXT:destroyContext', ContentContextMutations.destroyContext);\n  store.registerAction('CONTENT_CONTEXT:setExtrinsicScaleFactor', ContentContextMutations.setExtrinsicScaleFactor);\n  store.registerAction('CONTENT_CONTEXT:setLayoutContainerLayout', ContentContextMutations.setLayoutContainerLayout);\n  store.registerAction('CONTENT_CONTEXT:setLayoutContainerStyle', ContentContextMutations.setLayoutContainerStyle);\n  store.registerAction('CONTENT_CONTEXT:setTitle', ContentContextMutations.setTitle);\n  store.registerAction('CONTENT_CONTEXT:setTitle', ContentContextMutations.setTitle);\n  store.registerAction('CONTENT_CONTEXT:updateBlock', ContentContextMutations.updateBlock);\n\n  // other mutations\n  store.registerAction('OTHER:setFullscreenContainerNoSideMenu', OtherMutations.setFullscreenContainerNoSideMenu);\n  store.registerAction('OTHER:updateVisitedHotSpotIdsByBlockId', OtherMutations.updateVisitedHotSpotIdsByBlockId);\n}\n","import { ICommonState } from './store.interface';\n\nexport const initialCommonState: ICommonState = {\n  cache: {\n    chatRoomIdWithUsersByChatRoomId: {},\n    chatRooms: [],\n    conversationsByChatRoomId: {},\n    courseIntersectionByUserId: {},\n    coursesByCourseId: {},\n  },\n  contentContexts: {},\n  chat: {\n    unreadChatRooms: [],\n  },\n  fullscreenContainer: {\n    noSideMenu: false,\n  },\n  courseContentById: {},\n  conferenceContext: {\n    activeSpeakers: [],\n    activeSpeakerScores: {},\n    attendeeInfoByAttendeeId: {},\n    attendeeScores: {},\n    bindingContexts: {\n      contentShare: {\n        attendeeId: null,\n        hasTile: false,\n      },\n      lecturer: {\n        attendeeId: null,\n        hasTile: false,\n      },\n      local: {\n        attendeeId: null,\n        hasTile: false,\n      },\n      matrix: {\n        allAttendeeIds: [],\n        attendeeIds: [],\n        autoResize: true,\n        itemCount: 1,\n        rotation: false,\n      },\n    },\n    boundTiles: {},\n    connectionIsGood: true,\n    hideMenus: false,\n    contentShareTileIdByAttendeeId: {},\n    elementCountByChimeAttendeeId: {},\n    isActiveSpeakerByAttendeeId: {},\n    onScreenScores: {},\n    tiles: [],\n    videoElements: {},\n    videoTileIdByAttendeeId: {},\n  },\n  csrfToken: null,\n  distinar: {\n    activeLecture: null,\n    attendees: [],\n    chatRoomLastMessageReceived: null,\n    chatRoomLastRead: null,\n    conference: {\n      chimeAttendeeId: null,\n      chimeExternalUserId: null,\n      hasActiveMeeting: false,\n      meetingSessionStatusCode: null,\n    },\n    conferenceConfiguration: {\n      selectedDevices: {\n        audioInput: null,\n        audioOutput: null,\n        videoInput: null,\n      },\n      shareScreen: false,\n      streamAudio: false,\n      streamVideo: false,\n    },\n    lectureStatus: null,\n  },\n  distinarConnectionData: null,\n  distinarInitializing: true,\n  distinarConnection: {\n    attempts: [],\n    connected: false,\n    connecting: false,\n    disconnecting: false,\n    disconnectReason: null,\n    failedConnecting: false,\n  },\n  presentationContentById: {},\n};","import { Middleware } from 'aurelia-store';\nimport { StateMutationUtils } from 'common/utils/StateMutationUtils';\nimport { ICommonState, ICourseContentWithPerformanceData, IPresentationContentWithPerformanceData } from '../store.interface';\n\nexport function createContentPerformanceDataMiddleware<T extends ICommonState>(): Middleware<T> {\n  return (state, originalState) => {\n    const changedCourseContents = Object.values(state.courseContentById).filter((c): c is ICourseContentWithPerformanceData => !!c && c !== originalState?.courseContentById[c.id]);\n    const changedPresentationContents = Object.values(state.presentationContentById).filter((c): c is IPresentationContentWithPerformanceData => !!c && c !== originalState?.presentationContentById[c.id]);\n\n    let newState = state;\n    for(const content of changedCourseContents) newState = StateMutationUtils.updateContentPerformanceData(newState, 'course', content.id);\n    for(const content of changedPresentationContents) newState = StateMutationUtils.updateContentPerformanceData(newState, 'presentation', content.id);\n\n    return newState;\n  };\n}\n","import { MiddlewarePlacement, Store } from 'aurelia-store';\nimport { DataStorageService } from 'common/aurelia-services/DataStorageService';\nimport { StringUtils } from 'common/utils/StringUtils';\n\nexport interface IOptions<TStateType extends Record<string, any>> {\n  /**\n   * prefix of the DataStorage keys\n   */\n  storageKeyPrefix: string;\n\n  /**\n   * properties of the store which should not get stored in the DataStorage\n   */\n  blockedPropertyNames?: Set<keyof TStateType>;\n\n  handleError: () => void;\n}\n\nexport class StoreStorageMiddleware<TStateType extends Record<string, any>> {\n  private dataStorageService: DataStorageService;\n  private options: IOptions<TStateType>;\n  private oldState: TStateType | null = null;\n\n  constructor(dataStorageService: DataStorageService, options: IOptions<TStateType>) {\n    this.dataStorageService = dataStorageService;\n    this.options = options;\n  }\n\n  public async run(currentState: TStateType): Promise<void> {\n    const keys = this.getChangedKeys(currentState);\n    this.oldState = currentState;\n    const filteredKeys = this.filterBlocklistedKeys(keys);\n\n    for(const key of filteredKeys) {\n      const storageKey = this.generateDataStorageKeyName(key);\n      await this.dataStorageService.setItem(storageKey, currentState[key]);\n    }\n  }\n\n  /**\n   * set a hardcoded initial state\n   * the initial state will not get stored since only changes will be stored\n   */\n  public setInitialState(initialState: TStateType): void {\n    this.oldState = initialState;\n  }\n\n  public async loadStoredState(): Promise<Partial<TStateType>> {\n    const partial: Partial<TStateType> = {};\n    let storageKeys: IDBValidKey[] = [];\n    try {\n      storageKeys = await this.dataStorageService.getKeys();\n    } catch(error) {\n      this.handleDbConnectError();\n    }\n\n    const escapedPrefix = StringUtils.escapeStringForRegex(this.options.storageKeyPrefix);\n    const regex = new RegExp(`^${escapedPrefix} - (.*)$`);\n    for(const storageKey of storageKeys) {\n      if(typeof storageKey !== 'string') {\n        continue;\n      }\n\n      const result = regex.exec(storageKey);\n      const propertyName = result ? result[1] : null;\n      if(!propertyName) {\n        continue;\n      }\n\n      if(this.options.blockedPropertyNames?.has(propertyName)) {\n        await this.dataStorageService.deleteItem(storageKey);\n        continue;\n      }\n\n      (partial as any)[propertyName] = await this.dataStorageService.getItem(storageKey);\n    }\n\n    return partial;\n  }\n\n  public register(store: Store<TStateType>): void {\n    store.registerMiddleware(async currentState => {\n      await this.run(currentState);\n      return undefined;\n    }, MiddlewarePlacement.After);\n  }\n\n  private getChangedKeys(currentState: TStateType): Array<keyof TStateType & string> {\n    const keys = Object.keys(currentState) as Array<keyof TStateType & string>;\n\n    return keys.filter(key => {\n      return !this.oldState || this.oldState[key] !== currentState[key];\n    });\n  }\n\n  private filterBlocklistedKeys(keys: Array<keyof TStateType & string>): Array<keyof TStateType & string> {\n    const blocklistedPropertyNames = this.options.blockedPropertyNames;\n\n    return keys.filter(key => !blocklistedPropertyNames?.has(key));\n  }\n\n  private generateDataStorageKeyName(stateKeyName: keyof TStateType & string): string {\n    return `${this.options.storageKeyPrefix} - ${stateKeyName}`;\n  }\n\n  private handleDbConnectError(): void {\n    this.options.handleError();\n  }\n}\n","import { Middleware } from 'aurelia-store';\n\nexport function createUpdateRateMiddleware<T>(config?: Partial<{\n  /** how many frames to sample */\n  maxSamples: number;\n  /** the length of one frame in ms */\n  sampleFrameMs: number;\n  /** show a warning if this threshold is exceeded, or don't care about threshold if false */\n  threshold: number | false;\n  /** log on every sample frame if true */\n  logOnSampleFrame: boolean;\n}>): Middleware<T> {\n  const samples = [0];\n  const maxSamples = config?.maxSamples ?? 10;\n  const sampleFrameMs = config?.sampleFrameMs ?? 1000;\n  const threshold = config?.threshold ?? 10;\n  const logOnSampleFrame = config?.logOnSampleFrame ?? false;\n\n  let currentSample = 0;\n  let nextSampleMs = Date.now() + sampleFrameMs;\n\n  return () => {\n    const now = Date.now();\n\n    if(now > nextSampleMs) {\n      const average = samples.reduce((sum, s) => sum + s, 0) / samples.length;\n      if(logOnSampleFrame) {\n        // eslint-disable-next-line no-console\n        console.info(`Store: ${average} ops/s (${samples.join(',')})`);\n      }\n\n      if(threshold !== false) {\n        if(average > threshold) {\n          // eslint-disable-next-line no-console\n          console.warn(`Store: exceeded ops rate threshold with ${average} ops/s, (${samples.join(',')})`);\n        }\n      }\n\n      if(now > nextSampleMs + sampleFrameMs * maxSamples) {\n        samples.fill(0);\n        nextSampleMs = now + sampleFrameMs;\n      } else while(now > nextSampleMs) {\n        currentSample++;\n        if(currentSample >= maxSamples) currentSample = 0;\n        samples[currentSample] = 0;\n        nextSampleMs += sampleFrameMs;\n      }\n    }\n\n    samples[currentSample]++;\n  };\n}\n","import { MeetingSessionStatusCode } from 'amazon-chime-sdk-js';\nimport { CHIME_MAX_ATTENDEE_COUNT } from 'common/constants/lecture.constants';\nimport { IAttendeeDto, ISafeAttendeeDto } from 'common/dto/distinar/distinar.interface';\nimport { StateMutationUtils } from 'common/utils/StateMutationUtils';\nimport { ICommonState, IAttendeeTileState } from '../store.interface';\nimport { initialCommonState } from '../initial.state';\n\nexport function connectionDidBecomeGood<T extends ICommonState>(state: T): T {\n  return {\n    ...state,\n    conferenceContext: {\n      ...state.conferenceContext,\n      connectionIsGood: true,\n    },\n  };\n}\n\nexport function connectionDidBecomePoor<T extends ICommonState>(state: T): T {\n  return {\n    ...state,\n    conferenceContext: {\n      ...state.conferenceContext,\n      connectionIsGood: false,\n    },\n  };\n}\n\nexport function setMatrixItemCount<T extends ICommonState>(\n  state: T,\n  itemCount: number,\n): T {\n  return {\n    ...state,\n    conferenceContext: {\n      ...state.conferenceContext,\n      bindingContexts: {\n        ...state.conferenceContext.bindingContexts,\n        matrix: {\n          ...state.conferenceContext.bindingContexts.matrix,\n          itemCount,\n          attendeeIds: state.conferenceContext.bindingContexts.matrix.allAttendeeIds.slice(0, itemCount),\n        },\n      },\n    },\n  };\n}\n\nexport function setMatrixAutoResize<T extends ICommonState>(\n  state: T,\n  autoResize: boolean,\n): T {\n  return {\n    ...state,\n    conferenceContext: {\n      ...state.conferenceContext,\n      bindingContexts: {\n        ...state.conferenceContext.bindingContexts,\n        matrix: {\n          ...state.conferenceContext.bindingContexts.matrix,\n          autoResize,\n        },\n      },\n    },\n  };\n}\n\nexport function setVideoElementForTileId<T extends ICommonState>(\n  state: T,\n  tileId: number,\n  videoElement: HTMLVideoElement,\n): T {\n  if(!state.distinar.lectureStatus?.conference.active) return { ...state };\n\n  return {\n    ...state,\n    conferenceContext: {\n      ...state.conferenceContext,\n      videoElements: {\n        ...state.conferenceContext.videoElements,\n        [tileId]: videoElement,\n      },\n    },\n  };\n}\n\nexport function removeVideoElementForTileId<T extends ICommonState>(\n  state: T,\n  tileId: number,\n): T {\n  const videoElements = { ...state.conferenceContext.videoElements };\n  delete videoElements[tileId];\n\n  return {\n    ...state,\n    conferenceContext: {\n      ...state.conferenceContext,\n      videoElements,\n    },\n  };\n}\n\nexport function setBoundTileForAttendeeId<T extends ICommonState>(\n  state: T,\n  attendeeId: string,\n  tileId: number,\n): T {\n  return {\n    ...state,\n    conferenceContext: {\n      ...state.conferenceContext,\n      boundTiles: {\n        ...state.conferenceContext.boundTiles,\n        [attendeeId]: tileId,\n      },\n    },\n  };\n}\n\nexport function removeBoundTileForAttendeeId<T extends ICommonState>(\n  state: T,\n  attendeeId: string,\n): T {\n  const boundTiles = { ...state.conferenceContext.boundTiles };\n  delete boundTiles[attendeeId];\n\n  return {\n    ...state,\n    conferenceContext: {\n      ...state.conferenceContext,\n      boundTiles,\n    },\n  };\n}\n\nexport function addOrUpdateTileState<T extends ICommonState>(state: T, tileState: IAttendeeTileState): T {\n  if(!state.distinar.lectureStatus?.conference.active) return { ...state };\n  const tileIndex = state.conferenceContext.tiles.findIndex(tile => tile.tileId === tileState.tileId);\n\n  return StateMutationUtils.updateConferencePerformanceData({\n    ...state,\n    conferenceContext: {\n      ...state.conferenceContext,\n      tiles: tileIndex !== -1\n        ? [\n          ...state.conferenceContext.tiles.slice(0, tileIndex),\n          {\n            ...state.conferenceContext.tiles[tileIndex],\n            ...tileState,\n          },\n          ...state.conferenceContext.tiles.slice(tileIndex + 1),\n        ] : [\n          ...state.conferenceContext.tiles,\n          tileState,\n        ],\n    },\n  }, {\n    updateTilePerformanceData: true,\n  });\n}\n\nexport function removeTileState<T extends ICommonState>(state: T, tileId: number): T {\n  if(!state.distinar.lectureStatus?.conference.active) return { ...state };\n  const tileIndex = state.conferenceContext.tiles.findIndex(tile => tile.tileId === tileId);\n  if(tileIndex === -1) return { ...state };\n\n  return StateMutationUtils.updateConferencePerformanceData({\n    ...state,\n    conferenceContext: {\n      ...state.conferenceContext,\n      tiles: [\n        ...state.conferenceContext.tiles.slice(0, tileIndex),\n        ...state.conferenceContext.tiles.slice(tileIndex + 1),\n      ],\n    },\n  }, {\n    updateTilePerformanceData: true,\n  });\n}\n\nexport function resetConferenceStateWithoutConfiguration<T extends ICommonState>(state: T): T {\n  return {\n    ...state,\n    distinar: {\n      ...state.distinar,\n      conference: initialCommonState.distinar.conference,\n    },\n    conferenceContext: initialCommonState.conferenceContext,\n  };\n}\n\nexport function resetConferenceState<T extends ICommonState>(state: T): T {\n  return {\n    ...state,\n    distinar: {\n      ...state.distinar,\n      conferenceConfiguration: initialCommonState.distinar.conferenceConfiguration,\n      conference: initialCommonState.distinar.conference,\n    },\n    conferenceContext: initialCommonState.conferenceContext,\n  };\n}\n\n/**\n * dispatched when the chime meetings starts or stops\n *\n * this is unrelated to the lecture status (and it's `conference.active` state)\n */\nexport function setHasActiveMeeting<T extends ICommonState>(\n  state: T,\n  hasActiveMeeting: boolean,\n  meetingSessionStatusCode: MeetingSessionStatusCode | null,\n): T {\n  /**\n   * reset the local conference state if the meeting has ended\n   * but don't mind the configuration - it is reset when `conference.active` in lecture status changes.\n   */\n  const intermediateState = !hasActiveMeeting\n    ? resetConferenceStateWithoutConfiguration(state)\n    : state;\n\n  const conferenceActive = state.distinar.lectureStatus?.conference.active;\n\n  return {\n    ...intermediateState,\n    distinar: {\n      ...intermediateState.distinar,\n      conference: {\n        ...intermediateState.distinar.conference,\n        hasActiveMeeting,\n        // only keep the status code if there is the demand for an active meeting\n        meetingSessionStatusCode: conferenceActive\n          ? meetingSessionStatusCode\n          : null,\n      },\n    },\n  };\n}\n\nexport function setActiveSpeakers<T extends ICommonState>(state: T, activeSpeakers: Array<string>): T {\n  return StateMutationUtils.updateConferencePerformanceData({\n    ...state,\n    conferenceContext: {\n      ...state.conferenceContext,\n      activeSpeakers,\n    },\n  }, {\n    updateActiveSpeakerPerformanceData: true,\n  });\n}\n\nexport function setActiveSpeakerScores<T extends ICommonState>(\n  state: T,\n  activeSpeakerScores: { [chimeAttendeeId: string]: number | undefined },\n): T | false {\n  const { conferenceContext: { bindingContexts: { matrix: { rotation } } }, distinar: { conference: { hasActiveMeeting } } } = state;\n  if(!hasActiveMeeting || !rotation) return false;\n\n  return {\n    ...state,\n    conferenceContext: {\n      ...state.conferenceContext,\n      activeSpeakerScores,\n    },\n  };\n}\n\nexport function setOnScreenScores<T extends ICommonState>(state: T, onScreenScores: { [chimeAttendeeId: string]: number | undefined }): T {\n  return {\n    ...state,\n    conferenceContext: {\n      ...state.conferenceContext,\n      onScreenScores,\n    },\n  };\n}\n\nexport function incrementElementCountForAttendeeId<T extends ICommonState>(state: T, chimeAttendeeId: string): T {\n  const count = state.conferenceContext.elementCountByChimeAttendeeId[chimeAttendeeId];\n\n  return {\n    ...state,\n    conferenceContext: {\n      ...state.conferenceContext,\n      elementCountByChimeAttendeeId: {\n        ...state.conferenceContext.elementCountByChimeAttendeeId,\n        [chimeAttendeeId]: (count ?? 0) + 1,\n      },\n    },\n  };\n}\n\nexport function decrementElementCountForAttendeeId<T extends ICommonState>(state: T, chimeAttendeeId: string): T {\n  const count = state.conferenceContext.elementCountByChimeAttendeeId[chimeAttendeeId];\n  if(count == null) return { ...state };\n\n  const elementCountByChimeAttendeeId = { ...state.conferenceContext.elementCountByChimeAttendeeId };\n  if(count - 1 === 0) {\n    delete elementCountByChimeAttendeeId[chimeAttendeeId];\n  } else {\n    elementCountByChimeAttendeeId[chimeAttendeeId] = count - 1;\n  }\n\n  return {\n    ...state,\n    conferenceContext: {\n      ...state.conferenceContext,\n      elementCountByChimeAttendeeId,\n    },\n  };\n}\n\nexport function updateBindingContexts<T extends ICommonState>(state: T): T {\n  if(!state.distinar.lectureStatus?.conference.active) return { ...state };\n\n  const attendees = [...state.distinar.attendees];\n\n  const contentShareAttendeeId = state.conferenceContext.tiles.find(tile => tile.isContent)?.boundAttendeeId ?? null;\n  const lecturerAttendeeId = state.distinar.lectureStatus.conference.chimeLecturerAttendeeId ?? null;\n  const localAttendeeId = state.distinar.conference.chimeAttendeeId;\n\n  /**\n   * we only want to show local, lecturer and content-share attendees if they have active tiles,\n   * so we track `hasTile` as convenience prop in bindingContext\n   * (content-share attendee id is only set when a tile is found - we don't have to evaluate again)\n   */\n  const contentShareHasTile = contentShareAttendeeId != null;\n  const lecturerHasTile = lecturerAttendeeId != null && state.conferenceContext.tiles.some(tile => tile.boundAttendeeId === lecturerAttendeeId);\n  const localHasTile = localAttendeeId != null && state.conferenceContext.tiles.some(tile => tile.boundAttendeeId === localAttendeeId);\n\n  // add lecturer to attendees. if lecturer equals local it will be cancelled out by filtering automatically\n  if(lecturerAttendeeId != null) {\n    attendees.push({\n      avatarSvgDownloadLink: null,\n      chimeAttendeeId: lecturerAttendeeId,\n      chimeExternalUserId: state.distinar.lectureStatus.conference.chimeLecturerExternalUserId ?? null,\n      points: 0,\n      id: -1,\n      online: true,\n      nickname: null,\n      firstname: state.distinar.activeLecture?.lecturer?.firstname ?? null,\n      lastname: state.distinar.activeLecture?.lecturer?.lastname ?? null,\n      isPhysicallyPresent: false,\n    });\n  }\n\n  // make sure `lecturerAttendeeId` and `localAttendeeId` are set to ensure correct filtering/sorting\n  const matrixAttendees = lecturerAttendeeId == null || localAttendeeId == null\n    ? []\n    : attendees.filter((attendee): attendee is (IAttendeeDto | ISafeAttendeeDto) & { chimeAttendeeId: string } => attendee.chimeAttendeeId != null\n      && attendee.chimeAttendeeId !== localAttendeeId\n      && attendee.online\n      && !attendee.isPhysicallyPresent);\n\n  if(lecturerAttendeeId != null && localAttendeeId != null) {\n    matrixAttendees.sort(getAttendeePriorityComparator(lecturerAttendeeId, state.conferenceContext.attendeeScores));\n  }\n\n  const matrixAttendeeIds = matrixAttendees.map(attendee => attendee.chimeAttendeeId);\n  const { itemCount } = state.conferenceContext.bindingContexts.matrix;\n\n  return {\n    ...state,\n    conferenceContext: {\n      ...state.conferenceContext,\n      bindingContexts: {\n        ...state.conferenceContext.bindingContexts,\n        contentShare: {\n          ...state.conferenceContext.bindingContexts.contentShare,\n          attendeeId: contentShareAttendeeId,\n          hasTile: contentShareHasTile,\n        },\n        lecturer: {\n          ...state.conferenceContext.bindingContexts.lecturer,\n          attendeeId: lecturerAttendeeId,\n          hasTile: lecturerHasTile,\n        },\n        local: {\n          ...state.conferenceContext.bindingContexts.local,\n          attendeeId: localAttendeeId,\n          hasTile: localHasTile,\n        },\n        matrix: {\n          ...state.conferenceContext.bindingContexts.matrix,\n          rotation: matrixAttendees.length > itemCount,\n          allAttendeeIds: matrixAttendeeIds,\n          attendeeIds: matrixAttendeeIds.slice(0, itemCount),\n        },\n      },\n    },\n  };\n}\n\nexport function updateAttendeeScores<T extends ICommonState>(state: T): T {\n  const { conferenceContext: { activeSpeakerScores, onScreenScores } } = state;\n  const speakerAttendeeIds = Object.keys(activeSpeakerScores);\n  const onScreenAttendeeIds = Object.keys(onScreenScores);\n\n  const attendeeIds = Array.from(new Set([...speakerAttendeeIds, ...onScreenAttendeeIds]));\n\n  const attendeeScores = attendeeIds.reduce((acc, attendeeId) => {\n    const activeSpeakerScore = activeSpeakerScores[attendeeId] != null && activeSpeakerScores[attendeeId]! > 0\n      ? CHIME_MAX_ATTENDEE_COUNT + activeSpeakerScores[attendeeId]!\n      : 0;\n    const onScreenScore = onScreenScores[attendeeId] ?? 0;\n\n    return {\n      ...acc,\n      [attendeeId]: activeSpeakerScore + onScreenScore,\n    };\n  }, {});\n\n  return {\n    ...state,\n    conferenceContext: {\n      ...state.conferenceContext,\n      attendeeScores,\n    },\n  };\n}\n\nexport function updateHideMenus<T extends ICommonState>(state: T, hideMenus?: boolean): T {\n  const menuState = state.conferenceContext.hideMenus;\n  return {\n    ...state,\n    conferenceContext: {\n      ...state.conferenceContext,\n      hideMenus: hideMenus ?? !menuState,\n    },\n  };\n}\n\nfunction getAttendeePriorityComparator<T extends (IAttendeeDto | ISafeAttendeeDto) & { chimeAttendeeId: string }>(\n  lecturerAttendeeId: string,\n  attendeeScores: { [chimeAttendeeId: string]: number | undefined },\n): (a: T, b: T) => number {\n  return (a: T, b: T) => {\n    // prioritise lecturer over all other attendees\n    if(a.chimeAttendeeId === lecturerAttendeeId) return -1;\n    if(b.chimeAttendeeId === lecturerAttendeeId) return 1;\n\n    const calcAttendeePriority = (userId: number, attendeeId: string): number => {\n      const baseScore = userId * 0.01;\n      const attendeeScore = attendeeScores[attendeeId] ?? 0;\n\n      return baseScore + attendeeScore;\n    };\n\n    // sort descending\n    return calcAttendeePriority(\n      b.id,\n      b.chimeAttendeeId,\n    ) - calcAttendeePriority(\n      a.id,\n      a.chimeAttendeeId,\n    );\n  };\n}\n","import {\n  ILayoutChanges,\n  UniversalBlock,\n  UniversalLayoutContainer,\n} from 'common/dto/content-editor/content.editor.interface';\nimport { ICourseBlockDto, IPresentationBlockDto } from 'common/dto/course/content/block.interface';\nimport {\n  ICourseLayoutContainerDto,\n  ILayoutContainerDto,\n  ILayoutContainerLayoutDto,\n  ILayoutContainerStyleDto,\n  IPresentationLayoutContainerDto,\n} from 'common/dto/course/layout.container.interface';\nimport { ArrayUtils } from 'common/utils/ArrayUtils';\nimport { StateMutationUtils } from 'common/utils/StateMutationUtils';\nimport { ICommonState } from '../store.interface';\n\nexport function destroyContext<T extends ICommonState>(\n  state: T,\n  contextIdentifier: string,\n): T {\n  const contexts = { ...state.contentContexts };\n  delete contexts[contextIdentifier];\n\n  return {\n    ...state,\n    contentContexts: {\n      ...contexts,\n    },\n  };\n}\n\nexport function applyLayoutChanges<T extends ICommonState>(\n  state: T,\n  contextIdentifier: string,\n  layoutChanges: ILayoutChanges<UniversalLayoutContainer, UniversalBlock>,\n): T {\n  return StateMutationUtils.updateUniversalContentForContext(state, contextIdentifier, content => {\n    let contentBlocks = content.blocks;\n    let contentLayoutContainers = content.layoutContainers;\n    // apply changes to current content\n    const blockRemoveIndices = ArrayUtils.findIndices(\n      contentBlocks,\n      block => layoutChanges.blockChanges.removedIds.includes(block.id),\n    );\n    contentBlocks = StateMutationUtils.removeItemsInArray(\n      contentBlocks,\n      blockRemoveIndices,\n    );\n\n    const blockUpdateIndices = ArrayUtils.findIndices(\n      contentBlocks,\n      block => layoutChanges.blockChanges.updated.find(updatedBlock => updatedBlock.id === block.id) != null,\n    );\n    contentBlocks = StateMutationUtils.updateItemsInArray(\n      contentBlocks,\n      blockUpdateIndices,\n      block => ({\n        ...layoutChanges.blockChanges.updated.find(updatedBlock => updatedBlock.id === block.id)!,\n      }),\n    );\n\n    contentBlocks = [\n      ...contentBlocks,\n      ...layoutChanges.blockChanges.added,\n    ];\n\n    const layoutContainerRemoveIndices = ArrayUtils.findIndices(\n      contentLayoutContainers,\n      layoutContainer => layoutChanges.layoutContainerChanges.removedIds.includes(layoutContainer.id),\n    );\n    contentLayoutContainers = StateMutationUtils.removeItemsInArray(\n      contentLayoutContainers,\n      layoutContainerRemoveIndices,\n    );\n\n    const layoutContainerUpdateIndices = ArrayUtils.findIndices(\n      contentLayoutContainers,\n      layoutContainer => layoutChanges.layoutContainerChanges.updated.find(\n        updatedLayoutContainer => updatedLayoutContainer.id === layoutContainer.id,\n      ) != null,\n    );\n    contentLayoutContainers = StateMutationUtils.updateItemsInArray(\n      contentLayoutContainers,\n      layoutContainerUpdateIndices,\n      layoutContainer => ({\n        ...layoutChanges.layoutContainerChanges.updated.find(updatedLayoutContainer => updatedLayoutContainer.id === layoutContainer.id)!,\n      }),\n    );\n\n    contentLayoutContainers = [\n      ...contentLayoutContainers,\n      ...layoutChanges.layoutContainerChanges.added,\n    ];\n\n    return {\n      blocks: contentBlocks,\n      layoutContainers: contentLayoutContainers,\n    };\n  });\n}\n\nexport function setLayoutContainerStyle<T extends ICommonState>(\n  state: T,\n  contextIdentifier: string,\n  layoutContainerId: number,\n  style: Partial<ILayoutContainerStyleDto>,\n): T {\n  const context = state.contentContexts[contextIdentifier];\n  if(context == null) return { ...state };\n  const content = (context.type === 'course'\n    ? state.courseContentById[context.state.courseId]\n    : state.presentationContentById[context.state.presentationId]) ?? null;\n  if(content == null) return { ...state };\n\n  const { layoutContainers }: { layoutContainers: Array<ILayoutContainerDto> } = content;\n\n  const containerIndex = layoutContainers.findIndex(layoutContainer => layoutContainer.id === layoutContainerId);\n  if(containerIndex === -1) return { ...state };\n\n  if(context.type === 'course') {\n    const courseLayoutContainers = content.layoutContainers as Array<ICourseLayoutContainerDto>;\n    return StateMutationUtils.updateCourseContent(\n      state,\n      context.state.courseId,\n      () => ({\n        layoutContainers: [\n          ...courseLayoutContainers.slice(0, containerIndex),\n          {\n            ...courseLayoutContainers[containerIndex],\n            style: {\n              ...courseLayoutContainers[containerIndex].style,\n              ...style,\n            },\n          },\n          ...courseLayoutContainers.slice(containerIndex + 1),\n        ],\n      }),\n    );\n  }\n\n  const presentationLayoutContainers = content.layoutContainers as Array<IPresentationLayoutContainerDto>;\n\n  return StateMutationUtils.updatePresentationContent(\n    state,\n    context.state.presentationId,\n    () => ({\n      layoutContainers: [\n        ...presentationLayoutContainers.slice(0, containerIndex),\n        {\n          ...presentationLayoutContainers[containerIndex],\n          style: {\n            ...presentationLayoutContainers[containerIndex].style,\n            ...style,\n          },\n        },\n        ...presentationLayoutContainers.slice(containerIndex + 1),\n      ],\n    }),\n  );\n}\n\nexport function setLayoutContainerLayout<T extends ICommonState>(\n  state: T,\n  contextIdentifier: string,\n  layoutContainerId: number,\n  layout: Partial<ILayoutContainerLayoutDto>,\n): T {\n  const context = state.contentContexts[contextIdentifier];\n  if(context == null) return { ...state };\n  const content = (context.type === 'course'\n    ? state.courseContentById[context.state.courseId]\n    : state.presentationContentById[context.state.presentationId]) ?? null;\n  if(content == null) return { ...state };\n\n  const { layoutContainers }: { layoutContainers: Array<ILayoutContainerDto> } = content;\n  const containerIndex = layoutContainers.findIndex(layoutContainer => layoutContainer.id === layoutContainerId);\n  if(containerIndex === -1) return { ...state };\n\n  if(context.type === 'course') {\n    const courseLayoutContainers = content.layoutContainers as Array<ICourseLayoutContainerDto>;\n    return StateMutationUtils.updateCourseContent(\n      state,\n      context.state.courseId,\n      () => ({\n        layoutContainers: [\n          ...courseLayoutContainers.slice(0, containerIndex),\n          {\n            ...courseLayoutContainers[containerIndex],\n            layout: {\n              ...courseLayoutContainers[containerIndex].layout,\n              ...layout,\n            },\n          },\n          ...courseLayoutContainers.slice(containerIndex + 1),\n        ],\n      }),\n    );\n  }\n\n  const presentationLayoutContainers = content.layoutContainers as Array<IPresentationLayoutContainerDto>;\n\n  return StateMutationUtils.updatePresentationContent(\n    state,\n    context.state.presentationId,\n    () => ({\n      layoutContainers: [\n        ...presentationLayoutContainers.slice(0, containerIndex),\n        {\n          ...presentationLayoutContainers[containerIndex],\n          layout: {\n            ...presentationLayoutContainers[containerIndex].layout,\n            ...layout,\n          },\n        },\n        ...presentationLayoutContainers.slice(containerIndex + 1),\n      ],\n    }),\n  );\n}\n\nexport function updateBlock<T extends ICommonState>(\n  state: T,\n  contextIdentifier: string,\n  block: UniversalBlock,\n): T | false {\n  const context = state.contentContexts[contextIdentifier];\n  if(context == null) return false;\n\n  if(context.type === 'course') {\n    return StateMutationUtils.updateCourseContent(state, context.state.courseId, courseContent => ({\n      blocks: StateMutationUtils.updateItemsInArray(\n        courseContent.blocks,\n        b => b.id === block.id,\n        () => ({ ...block as ICourseBlockDto }),\n      ),\n    }));\n  }\n\n  return StateMutationUtils.updatePresentationContent(state, context.state.presentationId, courseContent => ({\n    blocks: StateMutationUtils.updateItemsInArray(\n      courseContent.blocks,\n      b => b.id === block.id,\n      () => ({ ...block as IPresentationBlockDto }),\n    ),\n  }));\n}\n\nexport function setTitle<T extends ICommonState>(\n  state: T,\n  contextIdentifier: string,\n  title: string,\n): T {\n  return StateMutationUtils.updateUniversalContentForContext(\n    state,\n    contextIdentifier,\n    () => ({ title }),\n  );\n}\n\nexport function setExtrinsicScaleFactor<T extends ICommonState>(\n  state: T,\n  extrinsicScaleFactor: number,\n): T {\n  return {\n    ...state,\n    contentEditor: {\n      ...state.contentEditor,\n      extrinsicScaleFactor,\n    },\n  };\n}\n","import { ICourseContentDto as CourseContentMutations } from 'common/dto/course/content/course.interface';\nimport { StateMutationUtils } from 'common/utils/StateMutationUtils';\nimport { ICommonState } from '../store.interface';\n\nexport function setContent<T extends ICommonState>(state: T, courseContent: CourseContentMutations): T {\n  return StateMutationUtils.updateContentPerformanceData({\n    ...state,\n    courseContentById: {\n      ...state.courseContentById,\n      [courseContent.id]: {\n        ...courseContent,\n      },\n    },\n  }, 'course', courseContent.id);\n}\n","import { IOrderById } from 'common/dto/content-editor/content.editor.interface';\nimport { BlockType } from 'common/dto/course/content/course.types';\nimport { ICoursePageDto } from 'common/dto/course/content/page.interface';\nimport { IIasSlideDto } from 'common/dto/course/content/slide.interface';\nimport { ArrayUtils } from 'common/utils/ArrayUtils';\nimport { StateMutationUtils } from 'common/utils/StateMutationUtils';\nimport { StateSelectorUtils } from 'common/utils/StateSelectorUtils';\nimport { ICommonState } from '../store.interface';\n\nexport function setInitialState<T extends ICommonState>(\n  state: T,\n  contextIdentifier: string,\n  courseId: number,\n  pageId?: number,\n): T | false {\n  const courseContent = state.courseContentById[courseId];\n  const rootPageId = courseContent?.rootPageId ?? null;\n  const pId = pageId ?? rootPageId;\n\n  const rootLayoutContainerId = pId != null ? courseContent?.rootLayoutContainerIdByPageId[pId] : null;\n  const block = StateSelectorUtils.selectBlockByLayoutContainerId(courseContent, rootLayoutContainerId);\n\n  const iasSlideId = block?.type === BlockType.MP_SEQUENCE_START\n    ? block.successSlideId\n    : null;\n\n  return StateMutationUtils.updateCourseContextState(state, contextIdentifier, () => ({\n    courseId,\n    pageId: pageId ?? rootPageId,\n    iasSlideId,\n  }));\n}\n\nexport function resetToRootPageId<T extends ICommonState>(state: T, contextIdentifier: string): T {\n  return StateMutationUtils.updateCourseContextState(state, contextIdentifier, context => ({\n    pageId: context?.courseId != null\n      ? state.courseContentById[context.courseId]?.rootPageId ?? null\n      : null,\n  }));\n}\n\nexport function setPageId<T extends ICommonState>(state: T, contextIdentifier: string, pageId: number): T {\n  return StateMutationUtils.updateCourseContextState(state, contextIdentifier, () => ({\n    pageId,\n  }));\n}\n\nexport function setIasSlideId<T extends ICommonState>(state: T, contextIdentifier: string, iasSlideId: number | null): T {\n  return StateMutationUtils.updateCourseContextState(state, contextIdentifier, () => ({\n    iasSlideId,\n  }));\n}\n\nexport function updatePage<T extends ICommonState>(\n  state: T,\n  contextIdentifier: string,\n  page: ICoursePageDto,\n): T | false {\n  const context = state.contentContexts[contextIdentifier];\n  if(context == null || context.type !== 'course') return false;\n  const { courseId } = context.state;\n\n  return StateMutationUtils.updateCourseContent(state, courseId, courseContent => ({\n    pages: StateMutationUtils.updateItemsInArray(\n      courseContent.pages,\n      [courseContent.pages.findIndex(p => p.id === page.id)],\n      () => ({ ...page }),\n    ),\n  }));\n}\n\nexport function addIasSlide<T extends ICommonState>(\n  state: T,\n  contextIdentifier: string,\n  slide: IIasSlideDto,\n): T {\n  return StateMutationUtils.updateCourseContentForContext(state, contextIdentifier, content => ({\n    iasSlides: [...content.iasSlides, slide],\n  }));\n}\n\nexport function removeIasSlide<T extends ICommonState>(\n  state: T,\n  contextIdentifier: string,\n  slideId: number,\n): T {\n  return StateMutationUtils.updateCourseContentForContext(state, contextIdentifier, content => ({\n    iasSlides: StateMutationUtils.removeItemsInArray(\n      content.iasSlides,\n      slide => slide.id === slideId,\n    ),\n  }));\n}\n\nexport function addIasSlideAndUpdateOrders<T extends ICommonState>(\n  state: T,\n  contextIdentifier: string,\n  newSlide: IIasSlideDto,\n  updatedOrders: Array<IOrderById>,\n): T {\n  return StateMutationUtils.updateCourseContentForContext(\n    applyIasSlideOrderChanges(state, contextIdentifier, updatedOrders),\n    contextIdentifier,\n    content => ({\n      iasSlides: [...content.iasSlides, newSlide],\n    }),\n  );\n}\n\nexport function applyIasSlideOrderChanges<T extends ICommonState>(\n  state: T,\n  contextIdentifier: string,\n  updatedOrders: Array<IOrderById>,\n): T {\n  const updatedOrdersBySlideId = ArrayUtils.toRecord(updatedOrders, o => o.id, o => o);\n  return StateMutationUtils.updateCourseContentForContext(state, contextIdentifier, content => ({\n    iasSlides: StateMutationUtils.updateItemsInArray(content.iasSlides, slide => !!updatedOrdersBySlideId[slide.id], slide => ({\n      order: updatedOrdersBySlideId[slide.id]?.order,\n    })),\n  }));\n}\n\nexport function navigateForward<T extends ICommonState>(state: T, contextIdentifier: string): T | false {\n  const context = state.contentContexts[contextIdentifier];\n  if(context == null || context.type !== 'course') return false;\n\n  const courseContent = state.courseContentById[context.state.courseId];\n  if(courseContent == null) return false;\n\n  const { pageId } = context.state;\n  const { orderedPageIds } = courseContent;\n  const pageIdx = orderedPageIds.findIndex(id => id === pageId);\n  const nextPageId = orderedPageIds[pageIdx + 1];\n  if(nextPageId == null) return false;\n\n  const rootLayoutContainerId = courseContent.rootLayoutContainerIdByPageId[nextPageId];\n  const block = StateSelectorUtils.selectBlockByLayoutContainerId(courseContent, rootLayoutContainerId);\n\n  return StateMutationUtils.updateCourseContextState(state, contextIdentifier, () => ({\n    pageId: nextPageId,\n    iasSlideId: block?.type === BlockType.MP_SEQUENCE_START\n      ? block?.successSlideId ?? null // set ias slide to success slide of mp sequence start\n      : null,\n  }));\n}\n\nexport function navigateBack<T extends ICommonState>(state: T, contextIdentifier: string): T | false {\n  const context = state.contentContexts[contextIdentifier];\n  if(context == null || context.type !== 'course') return false;\n\n  const courseContent = state.courseContentById[context.state.courseId];\n  if(courseContent == null) return false;\n\n  const { pageId } = context.state;\n  const { orderedPageIds } = courseContent;\n  const pageIdx = orderedPageIds.findIndex(id => id === pageId);\n  const previousPageId = orderedPageIds[pageIdx - 1];\n  if(previousPageId == null) return false;\n\n  const rootLayoutContainerId = courseContent.rootLayoutContainerIdByPageId[previousPageId];\n  const block = StateSelectorUtils.selectBlockByLayoutContainerId(courseContent, rootLayoutContainerId);\n\n  return StateMutationUtils.updateCourseContextState(state, contextIdentifier, () => ({\n    pageId: previousPageId,\n    iasSlideId: block?.type === BlockType.MP_SEQUENCE_START\n      ? block?.successSlideId ?? null // set ias slide to success slide of mp sequence start\n      : null,\n  }));\n}\n","import { Socket } from 'socket.io-client';\nimport { initialCommonState } from '../initial.state';\nimport { ICommonDistinarConnectionData, ICommonState, IDistinarConnectionAttempt } from '../store.interface';\n\nexport function startDisconnecting<TState extends ICommonState>(state: TState): TState {\n  return {\n    ...state,\n    distinarConnectionData: null,\n    distinarConnection: {\n      ...state.distinarConnection,\n      disconnecting: true,\n    },\n  };\n}\n\nexport function finishedDisconnecting<TState extends ICommonState>(state: TState): TState {\n  return {\n    ...state,\n    distinarConnection: initialCommonState.distinarConnection,\n  };\n}\n\nexport function setData<TState extends ICommonState, TConnectionData extends ICommonDistinarConnectionData>(\n  state: TState,\n  data: TConnectionData | null,\n): TState {\n  return {\n    ...state,\n    distinarConnectionData: data,\n  };\n}\n\nexport function resetConnectionState<T extends ICommonState>(state: T): T {\n  return {\n    ...state,\n    distinarConnection: initialCommonState.distinarConnection,\n  };\n}\n\nexport function startConnectionAttempt<T extends ICommonState>(state: T, attemptNo = 0): T {\n  const attempt: IDistinarConnectionAttempt = {\n    attemptNo,\n    error: null,\n    finishedAt: null,\n    startedAt: Date.now(),\n    successful: null,\n  };\n\n  return {\n    ...state,\n    distinarConnection: {\n      ...state.distinarConnection,\n      connecting: true,\n      failedConnecting: false,\n      connected: false,\n      disconnectReason: null,\n      disconnecting: false,\n      attempts: [\n        attempt,\n        ...state.distinarConnection.attempts,\n      ],\n    },\n  };\n}\n\nexport function finishedConnecting<T extends ICommonState>(state: T, attemptNo = 0): T | false {\n  const attemptIdx = state.distinarConnection.attempts.findIndex(a => a.finishedAt == null && a.attemptNo === attemptNo);\n  const attempt = state.distinarConnection.attempts[attemptIdx];\n  if(attempt == null) return false;\n\n  return {\n    ...state,\n    distinarConnection: {\n      ...state.distinarConnection,\n      connected: true,\n      connecting: false,\n      attempts:  [\n        ...state.distinarConnection.attempts.slice(0, attemptIdx),\n        {\n          ...attempt,\n          finishedAt: Date.now(),\n          successful: true,\n        },\n        ...state.distinarConnection.attempts.slice(attemptIdx + 1),\n      ],\n    },\n  };\n}\n\nexport function failedConnecting<T extends ICommonState>(state: T, message?: string): T | false {\n  const attemptIdx = state.distinarConnection.attempts.findIndex(a => a.finishedAt == null);\n  const attempt = state.distinarConnection.attempts[attemptIdx];\n  if(attempt == null) return false;\n\n  return {\n    ...state,\n    distinarConnection: {\n      ...state.distinarConnection,\n      connected: false,\n      connecting: false,\n      failedConnecting: true,\n      attempts:  [\n        ...state.distinarConnection.attempts.slice(0, attemptIdx),\n        {\n          ...attempt,\n          error: message ?? null,\n          finishedAt: Date.now(),\n          successful: false,\n        },\n        ...state.distinarConnection.attempts.slice(attemptIdx + 1),\n      ],\n    },\n  };\n}\n\nexport function failedConnectionAttempt<T extends ICommonState>(state: T, error: string): T | false {\n  const attemptIdx = state.distinarConnection.attempts.findIndex(a => a.finishedAt == null);\n  const attempt = state.distinarConnection.attempts[attemptIdx];\n  if(attempt == null) return false;\n\n  return {\n    ...state,\n    distinarConnection: {\n      ...state.distinarConnection,\n      attempts:  [\n        ...state.distinarConnection.attempts.slice(0, attemptIdx),\n        {\n          ...attempt,\n          finishedAt: Date.now(),\n          successful: false,\n          error,\n        },\n        ...state.distinarConnection.attempts.slice(attemptIdx + 1),\n      ],\n    },\n  };\n}\n\nexport function disconnected<T extends ICommonState>(state: T, reason: Socket.DisconnectReason): T {\n  return {\n    ...state,\n    distinarConnection: {\n      ...state.distinarConnection,\n      attempts: [],\n      connected: false,\n      connecting: false,\n      disconnecting: false,\n      disconnectReason: reason,\n      failedConnecting: false,\n    },\n  };\n}\n","import { ApplicationError } from 'common/aurelia-classes/Error';\nimport { IAttendeeDto, IDistinarStatusUpdateDto, ILectureQuizStatusDto, ISafeAttendeeDto } from 'common/dto/distinar/distinar.interface';\nimport { IEventDto, IStudentEventDto } from 'common/dto/tenant/event/tenant.event.interface';\nimport { StateMutationUtils } from 'common/utils/StateMutationUtils';\nimport { ICommonState } from '../store.interface';\n\nexport function markInitializationFinished<T extends ICommonState>(state: T): T {\n  return {\n    ...state,\n    distinarInitializing: false,\n  };\n}\n\nexport function setConferenceShareScreen<T extends ICommonState>(state: T, shareScreen: boolean): T {\n  return {\n    ...state,\n    distinar: {\n      ...state.distinar,\n      conferenceConfiguration: {\n        ...state.distinar.conferenceConfiguration,\n        shareScreen,\n      },\n    },\n  };\n}\n\nexport function updateActiveLecture<T extends ICommonState>(state: T, activeLecture: IStudentEventDto<string> | IEventDto<string>): T {\n  return StateMutationUtils.updateConferencePerformanceData({\n    ...state,\n    distinar: {\n      ...state.distinar,\n      activeLecture,\n    },\n  }, { updateAttendeeInfoPerformanceData: true });\n}\n\n/**\n * applies changes to the lecture status\n */\nexport function updateStatus<T extends ICommonState>(state: T, changes: IDistinarStatusUpdateDto): T | false {\n  const { lectureStatus } = state.distinar;\n\n  // don't update if there are no changes or lectureStatus\n  if(Object.keys(changes).length === 0 || lectureStatus == null) return false;\n\n  // we expect updates to be applied sequentially\n  if(lectureStatus.version !== changes.version - 1) {\n    throw new ApplicationError(`🚨 tried to apply update v${changes.version} to status with v${lectureStatus.version}`);\n  }\n\n  const lecturerChimeAttendeeIdUpdated = changes.conference !== undefined\n    && changes.conference.chimeLecturerAttendeeId !== undefined;\n\n  function mergeQuiz(\n    quizChanges: Partial<ILectureQuizStatusDto> | undefined | null,\n    quizStatus: ILectureQuizStatusDto | null,\n  ): Partial<ILectureQuizStatusDto | null> {\n    if(quizChanges === undefined) return quizStatus;\n    if(quizChanges === null) return null;\n\n    return {\n      ...quizStatus,\n      ...quizChanges,\n      lastQuestionAnswers: quizChanges.lastQuestionAnswers !== undefined\n        ? quizChanges.lastQuestionAnswers\n        : quizStatus?.lastQuestionAnswers ?? null,\n      quizQuestion: quizChanges.quizQuestion !== undefined\n        ? quizChanges.quizQuestion\n        : quizStatus?.quizQuestion ?? null,\n      scores: quizChanges.scores !== undefined\n        ? quizChanges.scores ?? []\n        : quizStatus?.scores ?? [],\n    };\n  }\n\n  return StateMutationUtils.updateConferencePerformanceData({\n    ...state,\n    distinar: {\n      ...state.distinar,\n      lectureStatus: {\n        ...lectureStatus,\n        ...changes,\n        conference: changes.conference !== undefined ? {\n          ...lectureStatus.conference,\n          ...changes.conference,\n        } : lectureStatus.conference,\n        course: changes.course !== undefined ? {\n          ...lectureStatus.course,\n          ...changes.course,\n          revealedAnswerBlockIds: changes.course?.revealedAnswerBlockIds !== undefined\n            ? [...changes.course.revealedAnswerBlockIds]\n            : lectureStatus.course.revealedAnswerBlockIds,\n        } : lectureStatus.course,\n        qrCode: {\n          ...lectureStatus.qrCode,\n          ...changes.qrCode,\n        },\n        quiz: mergeQuiz(changes.quiz, lectureStatus.quiz),\n        whiteboard: changes.whiteboard !== undefined ? {\n          ...lectureStatus.whiteboard,\n          ...changes.whiteboard,\n        } : lectureStatus.whiteboard,\n        version: changes.version,\n      },\n    },\n  }, { updateAttendeeInfoPerformanceData: lecturerChimeAttendeeIdUpdated });\n}\n\nexport function updateAttendees<T extends ICommonState>(state: T, attendees: Array<IAttendeeDto | ISafeAttendeeDto>): T {\n  return StateMutationUtils.updateConferencePerformanceData({\n    ...state,\n    distinar: {\n      ...state.distinar,\n      attendees: [\n        ...attendees,\n      ],\n    },\n  }, { updateAttendeeInfoPerformanceData: true });\n}\n\n/**\n * should only be used directly as the lecturer, use `updateConferenceAudioState` (UFE) as student instead\n */\nexport function setConferenceStreamAudio<T extends ICommonState>(state: T, streamAudio: boolean): T {\n  return {\n    ...state,\n    distinar: {\n      ...state.distinar,\n      conferenceConfiguration: {\n        ...state.distinar.conferenceConfiguration,\n        streamAudio,\n      },\n    },\n  };\n}\n\n/**\n * should only be used directly as the lecturer, use `updateConferenceVideoState` (UFE) as student instead\n */\nexport function setConferenceStreamVideo<T extends ICommonState>(state: T, streamVideo: boolean): T {\n  return {\n    ...state,\n    distinar: {\n      ...state.distinar,\n      conferenceConfiguration: {\n        ...state.distinar.conferenceConfiguration,\n        streamVideo,\n      },\n    },\n  };\n}\n\nexport function setSelectedAudioInputDevice<T extends ICommonState>(state: T, device: MediaDeviceInfo | null): T {\n  return {\n    ...state,\n    distinar: {\n      ...state.distinar,\n      conferenceConfiguration: {\n        ...state.distinar.conferenceConfiguration,\n        selectedDevices: {\n          ...state.distinar.conferenceConfiguration.selectedDevices,\n          audioInput: device,\n        },\n      },\n    },\n  };\n}\n\nexport function setSelectedAudioOutputDevice<T extends ICommonState>(state: T, device: MediaDeviceInfo | null): T {\n  return {\n    ...state,\n    distinar: {\n      ...state.distinar,\n      conferenceConfiguration: {\n        ...state.distinar.conferenceConfiguration,\n        selectedDevices: {\n          ...state.distinar.conferenceConfiguration.selectedDevices,\n          audioOutput: device,\n        },\n      },\n    },\n  };\n}\n\nexport function setSelectedVideoInputDevice<T extends ICommonState>(state: T, device: MediaDeviceInfo | null): T {\n  return {\n    ...state,\n    distinar: {\n      ...state.distinar,\n      conferenceConfiguration: {\n        ...state.distinar.conferenceConfiguration,\n        selectedDevices: {\n          ...state.distinar.conferenceConfiguration.selectedDevices,\n          videoInput: device,\n        },\n      },\n    },\n  };\n}\n\nexport function receivedDistinarChatMessage<T extends ICommonState>(state: T): T {\n  return {\n    ...state,\n    distinar: {\n      ...state.distinar,\n      chatRoomLastMessageReceived: new Date().toISOString(),\n    },\n  };\n}\n\nexport function markDistinarChatConversationAsRead<T extends ICommonState>(state: T): T {\n  return {\n    ...state,\n    distinar: {\n      ...state.distinar,\n      chatRoomLastRead: new Date().toISOString(),\n    },\n  };\n}\n\nexport function setConferenceAttendeeInfo<T extends ICommonState>(state: T, chimeAttendeeId: string, chimeExternalUserId: string): T {\n  return {\n    ...state,\n    distinar: {\n      ...state.distinar,\n      conference: {\n        chimeAttendeeId,\n        chimeExternalUserId,\n      },\n    },\n  };\n}\n","import { ArrayUtils } from 'common/utils/ArrayUtils';\nimport { ICommonState } from '../store.interface';\n\nexport function setFullscreenContainerNoSideMenu<T extends ICommonState>(state: T, noSideMenu: boolean): T | false {\n  if(state.fullscreenContainer.noSideMenu === noSideMenu) return false;\n  return {\n    ...state,\n    fullscreenContainer: {\n      ...state.fullscreenContainer,\n      noSideMenu,\n    },\n  };\n}\n\nexport function updateVisitedHotSpotIdsByBlockId<T extends ICommonState>(state: T, blockId: number, visitedHotSpotId: number): T | false {\n  const visitedHotSpotIdsByBlockId = state?.userCourseInteraction?.visitedHotSpotIdsByBlockId ?? {};\n  const visitedHotSpotIds = ArrayUtils.uniquePrimitive([\n    ...visitedHotSpotIdsByBlockId[blockId] ?? [],\n    visitedHotSpotId,\n  ]);\n\n  return {\n    ...state,\n    userCourseInteraction: {\n      ...state.userCourseInteraction,\n      visitedHotSpotIdsByBlockId: {\n        ...visitedHotSpotIdsByBlockId,\n        [blockId]: visitedHotSpotIds,\n      },\n    },\n  };\n}\n","import { IPresentationContentDto } from 'common/dto/course/content/course.interface';\nimport { StateMutationUtils } from 'common/utils/StateMutationUtils';\nimport { ICommonState } from '../store.interface';\n\nexport function setContent<T extends ICommonState>(state: T, presentationContent: IPresentationContentDto): T {\n  return StateMutationUtils.updateContentPerformanceData({\n    ...state,\n    presentationContentById: {\n      ...state.presentationContentById,\n      [presentationContent.id]: { ...presentationContent },\n    },\n  }, 'presentation', presentationContent.id);\n}\n","import { IChanges, IOrderById } from 'common/dto/content-editor/content.editor.interface';\nimport { IPresentationBlockDto } from 'common/dto/course/content/block.interface';\nimport { IPresentationSlideDto } from 'common/dto/course/content/slide.interface';\nimport {\n  IPresentationLayoutContainerDto,\n  LayoutContainerBlockSize,\n  LayoutContainerDirection,\n  LayoutContainerHAlignment,\n  LayoutContainerVAlignment,\n} from 'common/dto/course/layout.container.interface';\nimport { ArrayUtils } from 'common/utils/ArrayUtils';\nimport { StateMutationUtils } from 'common/utils/StateMutationUtils';\nimport { ICommonState } from '../store.interface';\n\nexport function setInitialState<T extends ICommonState>(\n  state: T,\n  contextIdentifier: string,\n  presentationId: number,\n): T | false {\n  const rootSlideId = state.presentationContentById[presentationId]?.rootSlideId ?? null;\n\n  return StateMutationUtils.updatePresentationContextState(\n    state,\n    contextIdentifier,\n    () => ({\n      dndActive: false,\n      isFullscreen: false,\n      presentationId,\n      showWhiteboard: false,\n      hasControls: false,\n      slideId: rootSlideId,\n      fullscreenPictureId: null,\n      videoSync: null,\n    }),\n  );\n}\n\nexport function setPresentationId<T extends ICommonState>(state: T, contextIdentifier: string, presentationId: number): T {\n  return StateMutationUtils.updatePresentationContextState(\n    state,\n    contextIdentifier,\n    () => ({ presentationId }),\n  );\n}\n\nexport function setSlideId<T extends ICommonState>(state: T, contextIdentifier: string, slideId: number | null): T {\n  return StateMutationUtils.updatePresentationContextState(\n    state,\n    contextIdentifier,\n    () => ({ slideId }),\n  );\n}\n\nexport function setIsFullscreen<T extends ICommonState>(state: T, contextIdentifier: string, isFullscreen: boolean): T {\n  return StateMutationUtils.updatePresentationContextState(\n    state,\n    contextIdentifier,\n    () => ({ isFullscreen }),\n  );\n}\n\nexport function setShowWhiteboard<T extends ICommonState>(state: T, contextIdentifier: string, showWhiteboard: boolean): T {\n  return StateMutationUtils.updatePresentationContextState(\n    state,\n    contextIdentifier,\n    () => ({ showWhiteboard }),\n  );\n}\n\nexport function setHasControls<T extends ICommonState>(state: T, contextIdentifier: string, hasControls: boolean): T {\n  return StateMutationUtils.updatePresentationContextState(\n    state,\n    contextIdentifier,\n    () => ({ hasControls }),\n  );\n}\n\nexport function setFullscreenPictureId<T extends ICommonState>(state: T, contextIdentifier: string, fullscreenPictureId: number | null): T {\n  return StateMutationUtils.updatePresentationContextState(\n    state,\n    contextIdentifier,\n    () => ({ fullscreenPictureId }),\n  );\n}\n\nexport function setRevealedAnswerBlockId<T extends ICommonState>(state: T, contextIdentifier: string, blockId: number, revealed: boolean): T | false {\n  const ctx = state.contentContexts[contextIdentifier];\n  if(ctx?.type === 'presentation' && revealed === !!ctx.state.revealedAnswerBlockIds?.includes(blockId)) return false;\n\n  return StateMutationUtils.updatePresentationContextState(\n    state,\n    contextIdentifier,\n    ctxState => ({\n      revealedAnswerBlockIds: revealed\n        ? [...ctxState?.revealedAnswerBlockIds ?? [], blockId]\n        : ctxState?.revealedAnswerBlockIds?.filter(q => q !== blockId),\n    }),\n  );\n}\n\nexport function clearRevealedAnswerBlockIds<T extends ICommonState>(state: T, contextIdentifier: string): T {\n  return StateMutationUtils.updatePresentationContextState(\n    state,\n    contextIdentifier,\n    () => ({ revealedAnswerBlockIds: null }),\n  );\n}\n\nexport function addSlide<T extends ICommonState>(\n  state: T,\n  contextIdentifier: string,\n  slide: IPresentationSlideDto,\n): T {\n  return StateMutationUtils.updatePresentationContentForContext(state, contextIdentifier, content => ({\n    slides: [\n      ...content.slides,\n      slide,\n    ],\n  }));\n}\n\nexport function removeSlide<T extends ICommonState>(\n  state: T,\n  contextIdentifier: string,\n  slideId: number,\n): T {\n  return StateMutationUtils.updatePresentationContentForContext(state, contextIdentifier, content => ({\n    slides: StateMutationUtils.removeItemsInArray(\n      content.slides,\n      slide => slide.id === slideId,\n    ),\n  }));\n}\n\nexport function setSlideHeading<T extends ICommonState>(\n  state: T,\n  contextIdentifier: string,\n  slideId: number,\n  heading: string | null,\n): T {\n  return StateMutationUtils.updatePresentationContentForContext(state, contextIdentifier, content => ({\n    slides: StateMutationUtils.updateItemsInArray(\n      content.slides,\n      slide => slide.id === slideId,\n      () => ({ heading }),\n    ),\n  }));\n}\n\nexport function setSlideSubheading<T extends ICommonState>(\n  state: T,\n  contextIdentifier: string,\n  slideId: number,\n  subheading: string | null,\n): T {\n  return StateMutationUtils.updatePresentationContentForContext(state, contextIdentifier, content => ({\n    slides: StateMutationUtils.updateItemsInArray(\n      content.slides,\n      slide => slide.id === slideId,\n      () => ({ subheading }),\n    ),\n  }));\n}\n\nexport function addFirstBlockOnSlide<T extends ICommonState>(\n  state: T,\n  contextIdentifier: string,\n  slideId: number,\n  block: IPresentationBlockDto,\n): T {\n  const context = state.contentContexts[contextIdentifier];\n  if(context == null || context.type === 'course') return { ...state };\n  const { presentationId } = context.state;\n  const presentationContent = state.presentationContentById[presentationId] ?? null;\n  if(presentationContent == null) return { ...state };\n\n  return {\n    ...state,\n    presentationContentById: {\n      ...state.presentationContentById,\n      [presentationId]: {\n        ...presentationContent,\n        layoutContainers: [\n          ...presentationContent.layoutContainers,\n          {\n            id: Math.random(),\n            order: 0,\n            slideId,\n            parentLayoutContainerId: null,\n            blockId: block.id,\n            style: {\n              border: false,\n              shadow: false,\n              spacing: true,\n              horizontalAlignment: LayoutContainerHAlignment.Center,\n              verticalAlignment: LayoutContainerVAlignment.Center,\n              blockSize: LayoutContainerBlockSize.Medium,\n              backgroundColor: null,\n            },\n            layout: {\n              direction: LayoutContainerDirection.Horizontal,\n              itemSize: 100,\n            },\n          } as IPresentationLayoutContainerDto,\n        ],\n        blocks: [\n          ...presentationContent.blocks,\n          block,\n        ],\n      },\n    },\n  };\n}\n\nexport function updateSlide<T extends ICommonState>(\n  state: T,\n  contextIdentifier: string,\n  slide: IPresentationSlideDto,\n): T {\n  return StateMutationUtils.updatePresentationContentForContext(state, contextIdentifier, presentationContent => {\n    const slideIdx = presentationContent.slides.findIndex(s => s.id === slide.id);\n    if(slideIdx === -1) return {};\n\n    return {\n      slides: [\n        ...presentationContent.slides.slice(0, slideIdx),\n        slide,\n        ...presentationContent.slides.slice(slideIdx + 1),\n      ],\n    };\n  });\n}\n\nexport function applySlideOrderChanges<T extends ICommonState>(\n  state: T,\n  contextIdentifier: string,\n  changes: IChanges<IOrderById>,\n): T {\n  const updatedOrdersBySlideId = ArrayUtils.toRecord(changes.updated, o => o.id, o => o);\n  return StateMutationUtils.updatePresentationContentForContext(state, contextIdentifier, presentationContent => ({\n    slides: StateMutationUtils.updateItemsInArray(presentationContent.slides, slide => !!updatedOrdersBySlideId[slide.id], slide => ({\n      order: updatedOrdersBySlideId[slide.id]?.order,\n    })),\n  }));\n}\n\nexport function addSlideAndUpdateOrders<T extends ICommonState>(\n  state: T,\n  contextIdentifier: string,\n  newSlide: IPresentationSlideDto,\n  updatedOrders: Array<IOrderById>,\n): T {\n  return StateMutationUtils.updatePresentationContentForContext(\n    applySlideOrderChanges(state, contextIdentifier, { added: [], removedIds: [], updated: updatedOrders }),\n    contextIdentifier,\n    content => ({\n      slides: [...content.slides, newSlide],\n    }),\n  );\n}\n\nexport function navigateForward<T extends ICommonState>(state: T, contextIdentifier: string): T | false {\n  const context = state.contentContexts[contextIdentifier];\n  if(context == null || context.type === 'course') return false;\n\n  const presentationContent = state.presentationContentById[context.state.presentationId];\n  if(presentationContent == null) return false;\n\n  const { slideId } = context.state;\n  const { orderedSlideIds } = presentationContent;\n  const slideIdx = orderedSlideIds.findIndex(id => id === slideId);\n  const nextPageId = orderedSlideIds[slideIdx + 1];\n\n  if(nextPageId == null) return false;\n\n  return StateMutationUtils.updatePresentationContextState(\n    state,\n    contextIdentifier,\n    () => ({ slideId: nextPageId }),\n  );\n}\n\nexport function navigateBack<T extends ICommonState>(state: T, contextIdentifier: string): T | false {\n  const context = state.contentContexts[contextIdentifier];\n  if(context == null || context.type === 'course') return false;\n\n  const presentationContent = state.presentationContentById[context.state.presentationId];\n  if(presentationContent == null) return false;\n\n  const { slideId } = context.state;\n  const { orderedSlideIds } = presentationContent;\n  const slideIdx = orderedSlideIds.findIndex(id => id === slideId);\n  const previousSlideId = orderedSlideIds[slideIdx - 1];\n\n  if(previousSlideId == null) return false;\n\n  return StateMutationUtils.updatePresentationContextState(\n    state,\n    contextIdentifier,\n    () => ({ slideId: previousSlideId }),\n  );\n}\n","import { ICommonState, IConferenceAttendeeInfo } from '../store.interface';\n\ntype StateSelectorProps<T = unknown> = { state: ICommonState } & T;\ntype AttendeeStateSelectorProps = StateSelectorProps<{ attendeeId: string }>;\n\nexport const selectContentShareAttendeeId = ({ state }: StateSelectorProps): string | null => state.conferenceContext.bindingContexts.contentShare.attendeeId;\nexport const selectLecturerAttendeeId = ({ state }: StateSelectorProps): string | null => state.conferenceContext.bindingContexts.lecturer.attendeeId;\nexport const selectLocalAttendeeId = ({ state } : StateSelectorProps): string | null => state.conferenceContext.bindingContexts.local.attendeeId;\nexport const selectMatrixAttendeeIds = ({ state }: StateSelectorProps): Array<string> => state.conferenceContext.bindingContexts.matrix.attendeeIds;\nexport const selectMatrixAllAttendeeIds = ({ state }: StateSelectorProps): Array<string> => state.conferenceContext.bindingContexts.matrix.allAttendeeIds;\n\nexport const selectContentShareHasTile = ({ state }: StateSelectorProps): boolean => state.conferenceContext.bindingContexts.contentShare.hasTile;\nexport const selectLecturerHasTile = ({ state }: StateSelectorProps): boolean => state.conferenceContext.bindingContexts.lecturer.hasTile;\nexport const selectLocalHasTile = ({ state }: StateSelectorProps): boolean => state.conferenceContext.bindingContexts.local.hasTile;\n\nexport const selectIsLecturerAttendeeId = ({ state, attendeeId }: AttendeeStateSelectorProps): boolean | null => {\n  const lecturerAttendeeId = selectLecturerAttendeeId({ state });\n  if(lecturerAttendeeId == null) return null;\n  return attendeeId === lecturerAttendeeId;\n};\n\nexport const selectContentShareTileId = (\n  { state, attendeeId }: AttendeeStateSelectorProps,\n): number | null => state.conferenceContext.contentShareTileIdByAttendeeId[attendeeId] ?? null;\n\nexport const selectVideoTileId = (\n  { state, attendeeId }: AttendeeStateSelectorProps,\n): number | null => state.conferenceContext.videoTileIdByAttendeeId[attendeeId] ?? null;\n\nexport const selectIsActiveSpeaker = (\n  { state, attendeeId }: AttendeeStateSelectorProps,\n): boolean => !!state.conferenceContext.isActiveSpeakerByAttendeeId[attendeeId];\n\nexport const selectAttendeeInfo = (\n  { state, attendeeId }: AttendeeStateSelectorProps,\n): IConferenceAttendeeInfo | null => state.conferenceContext.attendeeInfoByAttendeeId[attendeeId] ?? null;\n","import { UniversalBlock, UniversalContent } from 'common/dto/content-editor/content.editor.interface';\nimport { BlockType } from 'common/dto/course/content/course.types';\nimport { IFormulaDto } from 'common/dto/course/content/formula.interface';\nimport { ContentLayoutMode } from 'common/enum/content.layout.mode.enum';\nimport { StateSelectorUtils } from 'common/utils/StateSelectorUtils';\nimport { ContentContextState, ICommonState, ICourseContentWithPerformanceData, IUniversalContentWithPerformanceData } from '../store.interface';\nimport { createSelectorCreator } from './selectors';\n\ninterface IBlockByIdFlow {\n  contextIdentifier: string;\n  blockId: number | null;\n  content?: IUniversalContentWithPerformanceData | null;\n  block?: UniversalBlock | null;\n}\n\nexport const blockByIdSelectorCreator = createSelectorCreator<ICommonState, IBlockByIdFlow, UniversalBlock | null>([\n  ({ state, contextIdentifier }) => {\n    const context = state.contentContexts[contextIdentifier] ?? null;\n    if(context == null) return {};\n    return {\n      content: context.type === 'course'\n        ? state.courseContentById[context.state.courseId]\n        : state.presentationContentById[context.state.presentationId],\n    };\n  },\n  ({ content, blockId }) => ({\n    block: StateSelectorUtils.selectBlock(content, blockId),\n  }),\n], result => result.block ?? null);\n\nexport const isPresentationSelectorCreator = createSelectorCreator<ICommonState, { contextIdentifier: string; contentContext?: ContentContextState | null }, boolean | null>([\n  ({ state, contextIdentifier }) => ({ contentContext: state.contentContexts[contextIdentifier] ?? null }),\n], result => result.contentContext == null ? null : result.contentContext.type === 'presentation');\n\ninterface IContentLayoutModeFlow {\n  contextIdentifier: string;\n  layoutContainerId: number;\n  courseId?: number | null;\n  isPresentation?: boolean | null;\n  isIas?: boolean | null;\n  isMpBlock?: boolean | null;\n  courseContent?: ICourseContentWithPerformanceData<string> | null;\n}\n\nexport const contentLayoutModeSelectorCreator = createSelectorCreator<ICommonState, IContentLayoutModeFlow, ContentLayoutMode | null>([\n  ({ state, contextIdentifier }) => {\n    const context = state.contentContexts[contextIdentifier] ?? null;\n    const courseId = context?.type === 'course' ? context.state.courseId : null;\n\n    return {\n      courseId,\n      courseContent: courseId != null\n        ? state.courseContentById[courseId] ?? null\n        : null,\n      isPresentation: context?.type === 'presentation' ?? null,\n    };\n  },\n  ({ courseContent, layoutContainerId }) => {\n    const layoutContainer = StateSelectorUtils.selectLayoutContainer(courseContent, layoutContainerId);\n    if(!layoutContainer) return { isIas: null, isMpBlock: null };\n\n    const block = StateSelectorUtils.selectBlock(courseContent, layoutContainer?.blockId);\n\n    return {\n      isIas: layoutContainer.slideId != null,\n      isMpBlock: block?.type === BlockType.MP_RFG\n        || block?.type === BlockType.MP_INTERACTIVE_TEXT\n        || block?.type === BlockType.MP_HOT_SPOT\n        || block?.type === BlockType.MP_FLIPPING_CARD\n        || block?.type === BlockType.MP_RISI,\n    };\n  },\n], result => {\n  if(result.isPresentation === true || result.isIas === true || result.isMpBlock === true) return ContentLayoutMode.Extrinsic;\n  if(result.isPresentation === false && result.isIas === false && result.isMpBlock === false) return ContentLayoutMode.Intrinsic;\n  return null;\n});\n\ninterface ICourseIdFlow {\n  contextIdentifier: string;\n  contextState?: ContentContextState;\n  courseId?: number | null;\n}\n\nexport const courseOrPresentationIdSelectorCreator = createSelectorCreator<ICommonState, ICourseIdFlow, number | null>([\n  ({ state, contextIdentifier }) => ({ contextState: state.contentContexts[contextIdentifier] }),\n  ({ contextState }) => {\n    return contextState ? {\n      courseId: contextState.type === 'presentation'\n        ? contextState.state.presentationId\n        : contextState.state.courseId,\n    } : {};\n  },\n], result => result.courseId ?? null);\n\ninterface IBlockFormulaFlow {\n  contextIdentifier: string;\n  formulaId: number | null;\n  contextState?: ContentContextState;\n  content?: UniversalContent;\n  block?: UniversalBlock | null;\n  formula: IFormulaDto | null;\n}\n\nexport const blockFormulaSelectorCreator = createSelectorCreator<ICommonState, IBlockFormulaFlow, IFormulaDto | null>([\n  ({ state, contextIdentifier }) => ({ contextState: state.contentContexts[contextIdentifier] }),\n  ({ state, contextState }) => {\n    if(contextState == null) return { content: undefined };\n    const content = contextState.type === 'course'\n      ? state.courseContentById[contextState.state.courseId]\n      : state.presentationContentById[contextState.state.presentationId];\n    return { content };\n  },\n  ({ content, formulaId }) => {\n    if(!content || !formulaId) return { block: null };\n    const block = content.blocks.find(b => b.formula?.id === formulaId);\n    return { block: block ?? null };\n  },\n  ({ block }) => {\n    return { formula: block ? block.formula : null };\n  },\n], result => result.formula);\n","import { IMpRapidfireEntryDto } from 'common/dto/content/methodpool/rapidfire.interface';\nimport { ICourseBlockDto } from 'common/dto/course/content/block.interface';\nimport { ICourseContentDto } from 'common/dto/course/content/course.interface';\nimport { BlockType } from 'common/dto/course/content/course.types';\nimport { ICoursePageDto } from 'common/dto/course/content/page.interface';\nimport { IQuestionDto } from 'common/dto/course/content/question.interface';\nimport { PageType } from 'common/enum/course.enum';\nimport { ArrayUtils } from 'common/utils/ArrayUtils';\nimport { BlockUtils } from 'common/utils/BlockUtils';\nimport { CourseUtils } from 'common/utils/CourseUtils';\nimport { StateSelectorUtils } from 'common/utils/StateSelectorUtils';\nimport { ICommonState, ICourseContentWithPerformanceData, ICourseContextState, ICourseNavigationInfo, IIasNavigationInfo } from '../store.interface';\nimport { createSelectorCreator } from './selectors';\n\nexport const pageIdSelectorCreator = createSelectorCreator<ICommonState, { contextIdentifier: string; pageId?: number | null }, number | null>([\n  ({ state, contextIdentifier }) => {\n    const context = state.contentContexts[contextIdentifier] ?? null;\n    if(context == null || context.type === 'presentation') return { pageId: null };\n\n    return { pageId: context.state.pageId ?? null };\n  },\n], result => result.pageId ?? null);\n\nexport const courseIdSelectorCreator = createSelectorCreator<ICommonState, { contextIdentifier: string; courseId?: number | null }, number | null>([\n  ({ state, contextIdentifier }) => {\n    const context = state.contentContexts[contextIdentifier] ?? null;\n    if(context == null || context.type === 'presentation') return { courseId: null };\n\n    return { courseId: context.state.courseId ?? null };\n  },\n], result => result.courseId ?? null);\n\ninterface IPageByIdFlow {\n  contextIdentifier: string;\n  pageId: number;\n  page?: ICoursePageDto | null;\n}\n\nexport const pageByIdSelectorCreator = createSelectorCreator<ICommonState, IPageByIdFlow, ICoursePageDto | null>([\n  ({ state, contextIdentifier, pageId }) => {\n    const context = state.contentContexts[contextIdentifier] ?? null;\n    if(context == null || context.type === 'presentation') return {};\n    const content = state.courseContentById[context.state.courseId] ?? null;\n    return { page: content?.pages.find(page => page.id === pageId) ?? null };\n  },\n], result => result.page ?? null);\n\nexport const pageByChildIdSelectorCreator = createSelectorCreator<ICommonState, IPageByIdFlow, ICoursePageDto | null>([\n  ({ state, contextIdentifier, pageId }) => {\n    const context = state.contentContexts[contextIdentifier] ?? null;\n    if(context == null || context.type === 'presentation') return {};\n    const content = state.courseContentById[context.state.courseId] ?? null;\n    const parentId = content?.pages.find(page => page.id === pageId)?.parentId ?? null;\n    if(parentId == null) return { page: null };\n    return { page: content?.pages.find(page => page.id === parentId) ?? null };\n  },\n], result => result.page ?? null);\n\ninterface INavigationInfoFlow {\n  state: ICommonState;\n  contextIdentifier: string;\n  contextState?: ICourseContextState | null;\n  courseId?: number | null;\n  pageId?: number | null;\n  orderedPageIds?: Array<number>;\n  hasPreviousPage?: boolean;\n  hasNextPage?: boolean;\n  orderedChapterPageIds?: Array<number>;\n  totalChapterPages?: number;\n  pageIndexInChapter?: number;\n  parentPageId?: number | null;\n}\n\nexport const navigationInfoSelectorCreator = createSelectorCreator<ICommonState, INavigationInfoFlow, ICourseNavigationInfo>([\n  ({ state, contextIdentifier }) => {\n    const context = state.contentContexts[contextIdentifier];\n    if(context == null || context.type === 'presentation') return { contextState: null };\n    return { contextState: context.state };\n  },\n  ({ contextState, pageId }) => ({\n    courseId: contextState?.courseId ?? null,\n    pageId: pageId ?? contextState?.pageId ?? null,\n  }),\n  ({ state, courseId }) => ({\n    orderedPageIds: (courseId != null\n      ? state.courseContentById[courseId] ?? null\n      : null)?.orderedPageIds,\n  }),\n  ({ state, courseId, pageId }) => {\n    const courseContent = courseId != null ? state.courseContentById[courseId] : null;\n    if(pageId == null || courseContent == null) return { parentPageId: undefined };\n\n    const page = courseContent.pages.find(p => p.id === pageId) ?? null;\n    const parentPageId = page ? page.parentId : null;\n\n    return { parentPageId };\n  },\n  ({ state, courseId, parentPageId }) => {\n    const courseContent = courseId != null ? state.courseContentById[courseId] : null;\n    if(courseContent == null) return { orderedChapterPageIds: undefined };\n    let currentChapterPages = [];\n\n    if(!parentPageId) {\n      currentChapterPages = ArrayUtils.immutableSort(\n        courseContent.pages.filter(p => p.parentId === parentPageId),\n        (a, b) => a.order - b.order,\n      ).filter(p => p.type === PageType.Page && p.parentId === parentPageId);\n    } else {\n      const rootChapterId = (CourseUtils.getRootChapterIdsByPageId(courseContent))[parentPageId];\n      currentChapterPages = CourseUtils.getAllSubPages(courseContent, rootChapterId);\n    }\n    const orderedChapterPages = currentChapterPages;\n    return { orderedChapterPageIds: orderedChapterPages.map(page => page.id) };\n  },\n  ({ orderedPageIds, pageId, orderedChapterPageIds }) => ({\n    hasNextPage: orderedPageIds != null && (orderedPageIds.findIndex(id => id === pageId) < orderedPageIds.length - 1),\n    hasPreviousPage: orderedPageIds != null && (orderedPageIds.findIndex(id => id === pageId) !== 0),\n    totalChapterPages: orderedChapterPageIds?.length ?? 1,\n    pageIndexInChapter: (orderedChapterPageIds?.findIndex(id => id === pageId) ?? 0) + 1,\n  }),\n], ({ hasNextPage, hasPreviousPage, totalChapterPages, pageIndexInChapter }) => ({\n  hasNextPage: !!hasNextPage,\n  hasPreviousPage: !!hasPreviousPage,\n  pageIndexInChapter: pageIndexInChapter ?? 1,\n  totalChapterPages: totalChapterPages ?? 1,\n}));\n\ninterface IRootLayoutContainerIdFlow {\n  contextIdentifier: string;\n  pageId: number;\n  contextState?: ICourseContextState | null;\n  courseId?: number | null;\n  courseContent?: ICourseContentWithPerformanceData | null;\n  rootLayoutContainerId?: number | null;\n}\n\nexport const rootLayoutContainerIdSelectorCreator = createSelectorCreator<ICommonState, IRootLayoutContainerIdFlow, number | null>([\n  ({ state, contextIdentifier }) => {\n    const context = state.contentContexts[contextIdentifier];\n    if(context == null || context.type === 'presentation') return { contextState: null };\n    return { contextState: context.state };\n  },\n  ({ contextState }) => ({ courseId: contextState?.courseId ?? null }),\n  ({ state, courseId }) => ({\n    courseContent: courseId != null\n      ? state.courseContentById[courseId] ?? null\n      : null,\n  }),\n  ({ courseContent, pageId }) => ({\n    rootLayoutContainerId: courseContent?.rootLayoutContainerIdByPageId[pageId],\n  }),\n], result => result.rootLayoutContainerId ?? null);\n\ninterface ISelectIsPageEmptyFlow {\n  contextIdentifier: string;\n  contextState?: ICourseContextState | null;\n  courseId?: number | null;\n  pageId?: number | null;\n  courseContent?: ICourseContentDto | null;\n  isPageEmpty?: boolean;\n}\n\nexport const isPageEmptySelectorCreator = createSelectorCreator<ICommonState, ISelectIsPageEmptyFlow, boolean>([\n  ({ state, contextIdentifier }) => {\n    const context = state.contentContexts[contextIdentifier];\n    if(context == null || context.type === 'presentation') return { contextState: null };\n    return { contextState: context.state };\n  },\n  ({ contextState }) => ({ courseId: contextState?.courseId ?? null, pageId: contextState?.pageId ?? null }),\n  ({ state, courseId }) => ({\n    courseContent: courseId != null\n      ? state.courseContentById[courseId] ?? null\n      : null,\n  }),\n  ({ courseContent, pageId }) => ({\n    isPageEmpty: pageId != null && courseContent?.layoutContainers.find(container => container.pageId === pageId\n      && container.parentLayoutContainerId == null) == null,\n  }),\n], result => result.isPageEmpty === true);\n\ninterface IIsIasSlideBlockFlow {\n  state: ICommonState;\n  contextIdentifier: string;\n  courseId?: number | null;\n  iasSlideId?: number | null;\n  courseContent?: ICourseContentWithPerformanceData | null;\n  layoutContainerId?: number | null;\n  block?: ICourseBlockDto | null;\n}\n\nexport const isIasSlideBlockSelectorCreator = createSelectorCreator<ICommonState, IIsIasSlideBlockFlow, ICourseBlockDto | null>([\n  ({ state, contextIdentifier }) => {\n    const context = state.contentContexts[contextIdentifier];\n    if(context == null || context.type === 'presentation') return { contextState: null };\n    return {\n      iasSlideId: context.state.iasSlideId ?? null,\n      courseId: context.state.courseId,\n    };\n  },\n  ({ state, courseId }) => ({\n    courseContent: courseId != null\n      ? state.courseContentById[courseId]\n      : null,\n  }),\n  ({ courseContent, iasSlideId }) => ({\n    layoutContainerId: iasSlideId != null && courseContent != null && courseContent.rootLayoutContainerIdBySlideId[iasSlideId] || null,\n  }),\n  ({ courseContent, layoutContainerId }) => ({\n    block: StateSelectorUtils.selectBlockByLayoutContainerId(courseContent, layoutContainerId),\n  }),\n], result => result.block ?? null);\n\n\ninterface IIsSingleBlockPageFlow {\n  contextIdentifier: string;\n  contextState?: ICourseContextState | null;\n  courseId?: number | null;\n  pageId?: number | null;\n  courseContent?: ICourseContentWithPerformanceData | null;\n  isSingleBlockPage?: boolean;\n}\n\nexport const isSingleBlockPageSelectorCreator = createSelectorCreator<ICommonState, IIsSingleBlockPageFlow, boolean>([\n  ({ state, contextIdentifier }) => {\n    const context = state.contentContexts[contextIdentifier];\n    if(context == null || context.type === 'presentation') return { contextState: null };\n    return { contextState: context.state };\n  },\n  ({ contextState }) => ({\n    courseId: contextState?.courseId ?? null,\n    pageId: contextState?.pageId ?? null,\n  }),\n  ({ state, courseId }) => ({\n    courseContent: courseId != null\n      ? state.courseContentById[courseId] ?? null\n      : null,\n  }),\n  ({ courseContent, pageId }) => {\n    const rootLayoutContainerId = pageId != null ? courseContent?.rootLayoutContainerIdByPageId[pageId] : undefined;\n    const block = StateSelectorUtils.selectBlockByLayoutContainerId(courseContent, rootLayoutContainerId);\n\n    return {\n      isSingleBlockPage: block ? BlockUtils.isSingleBlockPage(block.type) : false,\n    };\n  },\n], result => result.isSingleBlockPage === true);\n\ninterface IIsIasFlow {\n  contextIdentifier: string;\n  contextState?: ICourseContextState | null;\n  courseId?: number | null;\n  blockId?: number | null;\n  pageId?: number | null;\n  courseContent?: ICourseContentWithPerformanceData | null;\n  isIas?: boolean;\n}\n\nexport const isIasSelectorCreator = createSelectorCreator<ICommonState, IIsIasFlow, boolean>([\n  ({ state, contextIdentifier }) => {\n    const context = state.contentContexts[contextIdentifier];\n    if(context == null || context.type === 'presentation') return { contextState: null };\n    return { contextState: context.state };\n  },\n  ({ contextState }) => ({\n    courseId: contextState?.courseId ?? null,\n    pageId: contextState?.pageId ?? null,\n  }),\n  ({ state, courseId }) => ({\n    courseContent: courseId != null\n      ? state.courseContentById[courseId] ?? null\n      : null,\n  }),\n  ({ courseContent, blockId, pageId }) => ({\n    pageId: (blockId ? courseContent?.pageIdByBlockId?.[blockId] : pageId) ?? null,\n  }),\n  ({ courseContent, pageId }) => {\n    const rootLayoutContainerId = pageId != null ? courseContent?.rootLayoutContainerIdByPageId[pageId] : undefined;\n    const block = StateSelectorUtils.selectBlockByLayoutContainerId(courseContent, rootLayoutContainerId);\n\n    return { isIas: block?.type === BlockType.MP_SEQUENCE_START };\n  },\n], result => result.isIas === true);\n\ninterface IIsIasOrderedSlideIdsFlow {\n  contextIdentifier: string;\n  contextState?: ICourseContextState | null;\n  courseId?: number | null;\n  pageId?: number | null;\n  courseContent?: ICourseContentWithPerformanceData | null;\n  orderedIasSlideIds?: Array<number>;\n}\n\nexport const iasSlideIdsOrderedSelectorCreator = createSelectorCreator<ICommonState, IIsIasOrderedSlideIdsFlow, Array<number>>([\n  ({ state, contextIdentifier }) => {\n    const context = state.contentContexts[contextIdentifier];\n    if(context == null || context.type === 'presentation') return { contextState: null };\n    return { contextState: context.state };\n  },\n  ({ contextState }) => ({\n    courseId: contextState?.courseId ?? null,\n    pageId: contextState?.pageId ?? null,\n  }),\n  ({ state, courseId }) => ({\n    courseContent: courseId != null\n      ? state.courseContentById[courseId] ?? null\n      : null,\n  }),\n  ({ courseContent, pageId }) => ({ orderedIasSlideIds: pageId != null ? courseContent?.orderedIasSlideIdsByPageId[pageId] : undefined }),\n], result => result.orderedIasSlideIds ?? []);\n\ninterface IIasSlideRootLayoutContainerIdFlow {\n  contextIdentifier: string;\n  slideId: number;\n  contextState?: ICourseContextState | null;\n  courseId?: number | null;\n  courseContent?: ICourseContentWithPerformanceData | null;\n  rootLayoutContainerId?: number;\n}\n\nexport const iasSlideRootLayoutContainerIdSelectorCreator = createSelectorCreator<ICommonState, IIasSlideRootLayoutContainerIdFlow, number | null>([\n  ({ state, contextIdentifier }) => {\n    const context = state.contentContexts[contextIdentifier];\n    if(context == null || context.type === 'presentation') return { contextState: null };\n    return { contextState: context.state };\n  },\n  ({ contextState }) => ({\n    courseId: contextState?.courseId ?? null,\n  }),\n  ({ state, courseId }) => ({\n    courseContent: courseId != null\n      ? state.courseContentById[courseId] ?? null\n      : null,\n  }),\n  ({ courseContent, slideId }) => ({ rootLayoutContainerId: courseContent?.rootLayoutContainerIdBySlideId[slideId] ?? undefined }),\n], result => result.rootLayoutContainerId ?? null);\n\ninterface IIasSlideIdFlow {\n  contextIdentifier: string;\n  contextState?: ICourseContextState | null;\n  iasSlideId?: number | null;\n}\n\nexport const iasSlideIdSelectorCreator = createSelectorCreator<ICommonState, IIasSlideIdFlow, number | null>([\n  ({ state, contextIdentifier }) => {\n    const context = state.contentContexts[contextIdentifier];\n    if(context == null || context.type === 'presentation') return { contextState: null };\n    return { iasSlideId: context.state.iasSlideId ?? null };\n  },\n], result => result.iasSlideId ?? null);\n\ninterface IIsIasSlideEmptyFlow {\n  state: ICommonState;\n  contextIdentifier: string;\n  courseId?: number | null;\n  iasSlideId?: number | null;\n  courseContent?: ICourseContentWithPerformanceData | null;\n  isIasSlideEmpty?: boolean;\n}\n\nexport const isIasSlideEmptySelectorCreator = createSelectorCreator<ICommonState, IIsIasSlideEmptyFlow, boolean>([\n  ({ state, contextIdentifier }) => {\n    const context = state.contentContexts[contextIdentifier];\n    if(context == null || context.type === 'presentation') return { contextState: null };\n    return {\n      iasSlideId: context.state.iasSlideId ?? null,\n      courseId: context.state.courseId,\n    };\n  },\n  ({ state, courseId }) => ({\n    courseContent: courseId != null\n      ? state.courseContentById[courseId]\n      : null,\n  }),\n  ({ courseContent, iasSlideId }) => ({\n    isIasSlideEmpty: iasSlideId != null && courseContent != null && courseContent.rootLayoutContainerIdBySlideId[iasSlideId] == null,\n  }),\n], result => result.isIasSlideEmpty === true);\n\ninterface IQuestionByBlockIdFlow {\n  contextIdentifier: string;\n  blockId: number;\n  content?: ICourseContentWithPerformanceData | null;\n  question?: IQuestionDto | null;\n}\n\nexport const questionByBlockIdSelectorCreator = createSelectorCreator<ICommonState, IQuestionByBlockIdFlow, IQuestionDto | null>([\n  ({ state, contextIdentifier }) => {\n    const context = state.contentContexts[contextIdentifier] ?? null;\n    if(context == null || context.type === 'presentation') return {};\n    return { content: state.courseContentById[context.state.courseId] };\n  },\n  ({ content, blockId }) => {\n    if(content == null) return { question: null };\n    const questionId = content.questionIdByBlockId[blockId] ?? null;\n\n    return {\n      question: questionId != null\n        ? content.questions.find(q => q.id === questionId)\n        : null,\n    };\n  },\n], result => result.question ?? null);\n\ninterface IIasNavigationInfoFlow {\n  contextIdentifier: string;\n  slideId: number;\n  content?: ICourseContentWithPerformanceData | null;\n  navigationInfo?: IIasNavigationInfo | null;\n}\n\nexport const iasNavigationInfoSelectorCreator = createSelectorCreator<ICommonState, IIasNavigationInfoFlow, IIasNavigationInfo | null>([\n  ({ state, contextIdentifier }) => {\n    const context = state.contentContexts[contextIdentifier] ?? null;\n    if(context == null || context.type === 'presentation') return {};\n    return { content: state.courseContentById[context.state.courseId] };\n  },\n  ({ content, slideId }) => {\n    const rootLayoutContainerId = content?.rootLayoutContainerIdBySlideId[slideId] ?? null;\n    const block = StateSelectorUtils.selectBlockByLayoutContainerId(content, rootLayoutContainerId);\n\n    return {\n      navigationInfo: block && {\n        errorSlideId: block.errorSlideId,\n        jumpSlide1Id: block.jumpSlide1Id,\n        jumpSlide2Id: block.jumpSlide2Id,\n        jumpSlide3Id: block.jumpSlide3Id,\n        jumpSlide4Id: block.jumpSlide4Id,\n        successSlideId: block.successSlideId,\n      },\n    };\n  },\n], result => result.navigationInfo ?? null);\n\ninterface IIasSlideBlockFlow {\n  contextIdentifier: string;\n  slideId: number;\n  content?: ICourseContentWithPerformanceData | null;\n  block?: ICourseBlockDto | null;\n}\n\nexport const iasSlideBlockSelectorCreator = createSelectorCreator<ICommonState, IIasSlideBlockFlow, ICourseBlockDto | null>([\n  ({ state, contextIdentifier }) => {\n    const context = state.contentContexts[contextIdentifier] ?? null;\n    if(context == null || context.type === 'presentation') return {};\n    return { content: state.courseContentById[context.state.courseId] };\n  },\n  ({ content, slideId }) => {\n    const rootLayoutContainerId = content?.rootLayoutContainerIdBySlideId[slideId] ?? null;\n    return { block: StateSelectorUtils.selectBlockByLayoutContainerId(content, rootLayoutContainerId) };\n  },\n], result => result.block ?? null);\n\n\ninterface IIsIntroPageFlow {\n  contextIdentifier: string;\n  content?: ICourseContentWithPerformanceData | null;\n  pageId: number | null;\n  isIntro?: boolean | null;\n}\n\nexport const isIntroPageSelectorCreator = createSelectorCreator<ICommonState, IIsIntroPageFlow, boolean | null>([\n  ({ state, contextIdentifier }) => {\n    const context = state.contentContexts[contextIdentifier] ?? null;\n    if(context == null || context.type === 'presentation') return {};\n    return { content: state.courseContentById[context.state.courseId], pageId: context.state.pageId };\n  },\n  ({ content, pageId }) => ({\n    isIntro: content?.pages.find(page => page.id === pageId)?.isIntro ?? null,\n  }),\n], result => result.isIntro ?? null);\n\ninterface IIsTrainingOnlyPageFlow {\n  contextIdentifier: string;\n  content?: ICourseContentWithPerformanceData | null;\n  pageId: number | null;\n  isTrainingOnly?: boolean | null;\n}\n\nexport const isTrainingPageSelectorCreator = createSelectorCreator<ICommonState, IIsTrainingOnlyPageFlow, boolean | null>([\n  ({ state, contextIdentifier }) => {\n    const context = state.contentContexts[contextIdentifier] ?? null;\n    if(context == null || context.type === 'presentation') return {};\n    return { content: state.courseContentById[context.state.courseId], pageId: context.state.pageId };\n  },\n  ({ content, pageId }) => ({\n    isTrainingOnly: content?.pages.find(page => page.id === pageId)?.isTrainingOnly ?? null,\n  }),\n], result => result.isTrainingOnly ?? null);\n\nexport const mediaPercentageConsideredSeenSelectorCreator = createSelectorCreator<ICommonState, { contextIdentifier: string; mediaPercentageConsideredSeen?: number | null }, number | null>([\n  ({ state, contextIdentifier }) => {\n    const context = state.contentContexts[contextIdentifier] ?? null;\n    if(context == null || context.type !== 'course') return {};\n\n    const content = state.courseContentById[context.state.courseId];\n    return { mediaPercentageConsideredSeen: content?.mediaPercentageConsideredSeen ?? null };\n  },\n], result => result.mediaPercentageConsideredSeen ?? null);\n\ninterface ISinglePageBlockByPageIdFlow {\n  contextIdentifier: string;\n  courseContent?: ICourseContentWithPerformanceData | null;\n  pageId: number | null;\n  block?: ICourseBlockDto | null;\n}\n\nexport const singlePageBlockSelectorCreator = createSelectorCreator<ICommonState, ISinglePageBlockByPageIdFlow, ICourseBlockDto | null>([\n  ({ state, contextIdentifier }) => {\n    const context = state.contentContexts[contextIdentifier] ?? null;\n    if(context == null || context.type === 'presentation') return {};\n    return { courseContent: state.courseContentById[context.state.courseId], pageId: context.state.pageId };\n  },\n  ({ courseContent, pageId }) => {\n    const rootLayoutContainerId = pageId != null ? courseContent?.rootLayoutContainerIdByPageId[pageId] : undefined;\n    return { block: StateSelectorUtils.selectBlockByLayoutContainerId(courseContent, rootLayoutContainerId) };\n  },\n], result => result?.block ?? null);\n\ntype RFGEntriesByCategory = {\n  [key: string]: Array<IMpRapidfireEntryDto>;\n};\ninterface IRFGEntriesByCategoryFlow {\n  contextIdentifier: string;\n  rapidfireEntriesByCategory?: RFGEntriesByCategory | null;\n}\n\nexport const rapidfireEntriesByCategorySelectorCreator = createSelectorCreator<ICommonState, IRFGEntriesByCategoryFlow, RFGEntriesByCategory | null>([\n  ({ state, contextIdentifier }) => {\n    const context = state.contentContexts[contextIdentifier] ?? null;\n    if(context == null || context.type === 'presentation') return {};\n\n    const content = state.courseContentById[context.state.courseId];\n    return { rapidfireEntriesByCategory: content?.rapidfireEntriesByCategory ?? null };\n  },\n], result => result.rapidfireEntriesByCategory ?? null);\n\nexport const courseContentSelectorCreator = createSelectorCreator<ICommonState, { contextIdentifier: string; courseContent?: ICourseContentDto | null }, ICourseContentDto | null>([\n  ({ state, contextIdentifier }) => {\n    const context = state.contentContexts[contextIdentifier] ?? null;\n    if(context == null || context.type === 'presentation') return {};\n    return { courseContent: state.courseContentById[context.state.courseId] ?? null };\n  },\n], result => result.courseContent ?? null);\n\nexport const courseTitleSelectorCreator = createSelectorCreator<ICommonState, { contextIdentifier: string; courseContent?: ICourseContentDto | null }, string | null>([\n  ({ state, contextIdentifier }) => {\n    const context = state.contentContexts[contextIdentifier] ?? null;\n    if(context == null || context.type === 'presentation') return {};\n    return { courseContent: state.courseContentById[context.state.courseId] ?? null };\n  },\n], result => result.courseContent?.title ?? null);\n\ninterface IIsInspectorDisabled {\n  contextIdentifier: string;\n  contextState?: ICourseContextState | null;\n  courseId?: number | null;\n  blockId?: number | null;\n  pageId?: number | null;\n  courseContent?: ICourseContentWithPerformanceData | null;\n  isInspectorDisabled?: boolean;\n}\n\nexport const isInspectorDisabledSelectorCreator = createSelectorCreator<ICommonState, IIsInspectorDisabled, boolean>([\n  ({ state, contextIdentifier }) => {\n    const context = state.contentContexts[contextIdentifier];\n    if(context == null || context.type === 'presentation') return { contextState: null };\n    return { contextState: context.state };\n  },\n  ({ contextState }) => ({\n    courseId: contextState?.courseId ?? null,\n    pageId: contextState?.pageId ?? null,\n  }),\n  ({ state, courseId }) => ({\n    courseContent: courseId != null\n      ? state.courseContentById[courseId] ?? null\n      : null,\n  }),\n  ({ courseContent, blockId, pageId }) => ({\n    pageId: (blockId ? courseContent?.pageIdByBlockId?.[blockId] : pageId) ?? null,\n  }),\n  ({ courseContent, pageId }) => {\n    const rootLayoutContainerId = pageId != null ? courseContent?.rootLayoutContainerIdByPageId[pageId] : undefined;\n    const block = StateSelectorUtils.selectBlockByLayoutContainerId(courseContent, rootLayoutContainerId);\n\n    /**\n     * isIas (blockType === BlockType.MP_SEQUENCE_START) is handled separately, because in\n     * the future inspector will be used on ias slides.\n     */\n    return {\n      isInspectorDisabled: block?.type\n        && [\n          BlockType.MP_FLIPPING_CARD,\n          BlockType.MP_HOT_SPOT,\n          BlockType.MP_INTERACTIVE_TEXT,\n          BlockType.MP_RFG,\n          BlockType.MP_RISI,\n          BlockType.MP_SCORM,\n        ].includes(block.type),\n    };\n  },\n], result => result.isInspectorDisabled === true);","import { ICommonState } from '../store.interface';\n\nexport const isReconnectingSelector = <TState extends ICommonState>(\n  { state }: { state: TState},\n): boolean => state.distinarConnection.connecting && state.distinarConnection.attempts.length > 1;","import { SelectorCreator } from 'common/aurelia-decorators/select-state/definitions';\nimport { IBlockInfo } from 'common/dto/course/content/block.interface';\nimport { IPresentationContentDto } from 'common/dto/course/content/course.interface';\nimport { BlockType } from 'common/dto/course/content/course.types';\nimport { IPresentationSlideDto } from 'common/dto/course/content/slide.interface';\nimport { ILayoutContainerChildInfo, ILayoutContainerDto } from 'common/dto/course/layout.container.interface';\nimport { MathUtils } from 'common/utils/MathUtils';\nimport { StateSelectorUtils } from 'common/utils/StateSelectorUtils';\nimport memoize, { memoizedFlow } from 'memoize-state';\nimport {\n  ContentContextState,\n  ICommonState,\n  IPresentationContentWithPerformanceData,\n  IPresentationContextState,\n  IPresentationNavigationInfo,\n  IUniversalContentWithPerformanceData,\n} from '../store.interface';\nimport { createSelectorCreator } from './selectors';\n\nconst selectStateForContext = (): ((state: ICommonState, contextIdentifier: string) => IPresentationContextState | null) => memoize(\n  (state: ICommonState, contextIdentifier: string): IPresentationContextState | null => {\n    const context = state.contentContexts[contextIdentifier];\n    if(context == null || context.type === 'course') return null;\n    return context.state;\n  },\n);\n\nexport const presentationContentSelectorCreator = createSelectorCreator<ICommonState, { contextIdentifier: string; content?: IPresentationContentDto | null }, IPresentationContentDto | null>([\n  ({ state, contextIdentifier }) => {\n    const context = state.contentContexts[contextIdentifier] ?? null;\n    if(context == null || context.type === 'course') return {};\n    return { content: state.presentationContentById[context.state.presentationId] ?? null };\n  },\n], result => result.content ?? null);\n\nexport const presentationIdSelectorCreator: SelectorCreator<ICommonState, number | null, { contextIdentifier: string }> = () => {\n  const stateForContext = selectStateForContext();\n\n  return memoize((state, { contextIdentifier }) => {\n    const presentationState = stateForContext(state, contextIdentifier);\n    return presentationState?.presentationId ?? null;\n  });\n};\n\nconst selectPresentationContent = (): (\n  (\n    state: ICommonState,\n    contextIdentifier: string\n  ) => IPresentationContentWithPerformanceData | null) => {\n  const sPresentationId = presentationIdSelectorCreator();\n\n  return (state: ICommonState, contextIdentifier: string) => {\n    const presentationId = sPresentationId(state, { contextIdentifier });\n    return (presentationId != null\n      ? state.presentationContentById[presentationId] ?? null\n      : null);\n  };\n};\n\nexport const slideIdSelectorCreator: SelectorCreator<ICommonState, number | null, { contextIdentifier: string }> = () => {\n  const stateForContext = selectStateForContext();\n\n  return memoize((state, { contextIdentifier }) => {\n    const presentationState = stateForContext(state, contextIdentifier);\n    if(presentationState == null) return null;\n\n    return presentationState.slideId ?? null;\n  });\n};\n\ninterface ISelectIsSlideEmptyFlow {\n  state: ICommonState;\n  contextIdentifier: string;\n  contextState?: IPresentationContextState | null;\n  presentationId?: number | null;\n  slideId?: number | null;\n  presentationContent?: IPresentationContentDto | null;\n  slide?: IPresentationSlideDto | null;\n}\n\nconst selectSlideFlow = (): ((input: ISelectIsSlideEmptyFlow) => ISelectIsSlideEmptyFlow) => memoizedFlow<ISelectIsSlideEmptyFlow>([\n  ({ state, contextIdentifier }) => {\n    const context = state.contentContexts[contextIdentifier];\n    if(context == null || context.type === 'course') return { contextState: null };\n    return { contextState: context.state };\n  },\n  ({ contextState }) => ({ presentationId: contextState?.presentationId ?? null, slideId: contextState?.slideId ?? null }),\n  ({ state, presentationId }) => ({\n    presentationContent: presentationId != null\n      ? state.presentationContentById[presentationId] ?? null\n      : null,\n  }),\n  ({ presentationContent, slideId }) => ({ slide: presentationContent?.slides.find(slide => slide.id === slideId) ?? null }),\n]);\n\nexport const selectSlide = (): ((state: ICommonState, contextIdentifier: string) => IPresentationSlideDto | null) => {\n  const slideFlow = selectSlideFlow();\n  return (state: ICommonState, contextIdentifier: string): IPresentationSlideDto | null => {\n    const result = slideFlow({ state, contextIdentifier });\n    return result.slide ?? null;\n  };\n};\n\ninterface ISelectIsSlideEmptyFlow {\n  state: ICommonState;\n  contextIdentifier: string;\n  contextState?: IPresentationContextState | null;\n  presentationId?: number | null;\n  slideId?: number | null;\n  presentationContent?: IPresentationContentDto | null;\n  isSlideEmpty?: boolean;\n}\n\nconst selectIsSlideEmptyFlow = (): ((input: ISelectIsSlideEmptyFlow) => ISelectIsSlideEmptyFlow) => memoizedFlow<ISelectIsSlideEmptyFlow>([\n  ({ state, contextIdentifier }) => {\n    const context = state.contentContexts[contextIdentifier];\n    if(context == null || context.type === 'course') return { contextState: null };\n    return { contextState: context.state };\n  },\n  ({ contextState }) => ({ presentationId: contextState?.presentationId ?? null, slideId: contextState?.slideId ?? null }),\n  ({ state, presentationId }) => ({\n    presentationContent: presentationId != null\n      ? state.presentationContentById[presentationId] ?? null\n      : null,\n  }),\n  ({ presentationContent, slideId }) => ({\n    isSlideEmpty: slideId != null && presentationContent?.layoutContainers.find(container => container.slideId === slideId\n      && container.parentLayoutContainerId == null) == null,\n  }),\n]);\n\nexport const selectIsSlideEmpty = (): ((state: ICommonState, contextIdentifier: string) => boolean) => {\n  const isSlideEmptyFlow = selectIsSlideEmptyFlow();\n\n  return (state: ICommonState, contextIdentifier: string): boolean => {\n    const result = isSlideEmptyFlow({ state, contextIdentifier });\n    return result.isSlideEmpty === true;\n  };\n};\n\nexport const slideIdsOrderedSelectorCreator: SelectorCreator<ICommonState, Array<number>, { contextIdentifier: string }> = () => {\n  const sPresentationContent = selectPresentationContent();\n\n  return memoize((state: ICommonState, { contextIdentifier }) => {\n    const presentationContent = sPresentationContent(state, contextIdentifier);\n\n    return presentationContent?.orderedSlideIds ?? [];\n  });\n};\n\nexport const selectSlideById = (): ((state: ICommonState, contextIdentifier: string, slideId: number | null) => IPresentationSlideDto | undefined) => {\n  const sPresentationContent = selectPresentationContent();\n\n  return memoize((state: ICommonState, contextIdentifier: string, slideId: number | null) => {\n    if(!slideId) return undefined;\n    const presentationContent = sPresentationContent(state, contextIdentifier);\n\n    return presentationContent?.slides.find(slide => slide.id === slideId);\n  });\n};\n\ninterface ISelectRootLayoutContainerFlow {\n  state: ICommonState;\n  contextIdentifier: string;\n  slideId: number;\n  contextState?: IPresentationContextState | null;\n  presentationId?: number | null;\n  presentationContent?: IPresentationContentWithPerformanceData | null;\n  rootLayoutContainerId?: number | null;\n}\n\nexport const selectRootLayoutContainerFlow = (): (\n  (input: ISelectRootLayoutContainerFlow) => ISelectRootLayoutContainerFlow) => memoizedFlow<ISelectRootLayoutContainerFlow>([\n    ({ state, contextIdentifier }) => {\n      const context = state.contentContexts[contextIdentifier];\n      if(context == null || context.type === 'course') return { contextState: null };\n      return { contextState: context.state };\n    },\n    ({ contextState }) => ({ presentationId: contextState?.presentationId ?? null }),\n    ({ state, presentationId }) => ({\n      presentationContent: presentationId != null\n        ? state.presentationContentById[presentationId] ?? null\n        : null,\n    }),\n    ({ presentationContent, slideId }) => ({\n      rootLayoutContainerId: presentationContent?.rootLayoutContainerIdBySlideId[slideId],\n    }),\n  ]);\n\nexport const selectRootLayoutContainerId = (): ((state: ICommonState, contextIdentifier: string, slideId: number | null) => number | null) => {\n  const rootLayoutContainerFlow = selectRootLayoutContainerFlow();\n  return (state: ICommonState, contextIdentifier: string, slideId: number | null): number | null => {\n    if(!slideId) return null;\n    const result = rootLayoutContainerFlow({ state, contextIdentifier, slideId });\n    return result.rootLayoutContainerId ?? null;\n  };\n};\n\ninterface ILayoutContainerByIdFlow {\n  contextIdentifier: string;\n  layoutContainerId: number | null;\n  content?: IUniversalContentWithPerformanceData | null;\n  layoutContainer?: ILayoutContainerDto | null;\n}\n\nexport const layoutContainerByIdSelectorCreator = createSelectorCreator<ICommonState, ILayoutContainerByIdFlow, ILayoutContainerDto | null>([\n  ({ state, contextIdentifier }) => {\n    const context = state.contentContexts[contextIdentifier] ?? null;\n    if(context == null) return {};\n    return {\n      content: context.type === 'course'\n        ? state.courseContentById[context.state.courseId]\n        : state.presentationContentById[context.state.presentationId],\n    };\n  },\n  ({ content, layoutContainerId }) => ({\n    layoutContainer: StateSelectorUtils.selectLayoutContainer(content, layoutContainerId),\n  }),\n], result => result.layoutContainer ?? null);\n\nexport const layoutContainerChildIdsOrderedSelectorCreator: SelectorCreator<\n  ICommonState,\n  Array<number>,\n  { contextIdentifier: string; layoutContainerId: number }\n> = () => {\n  return memoize(\n    (state, { contextIdentifier, layoutContainerId }) => {\n      const context = state.contentContexts[contextIdentifier];\n      if(context == null) return [];\n      const content = context.type === 'course'\n        ? state.courseContentById[context.state.courseId] ?? null\n        : state.presentationContentById[context.state.presentationId] ?? null;\n\n      return content?.orderedLayoutContainerIdsByParentId[layoutContainerId] ?? [];\n    },\n  );\n};\n\ninterface ISelectLayoutContainerChildInfoOrderedFlow {\n  contextIdentifier: string;\n  layoutContainerId: number;\n  contextState?: ContentContextState;\n  content?: IUniversalContentWithPerformanceData;\n  tempSizes?: { sizeChangePercent: number; child1Id: number; child2Id: number } | null;\n  childInfos?: Array<ILayoutContainerChildInfo>;\n}\n\nexport const layoutContainerChildInfoOrderedSelectorCreator = createSelectorCreator<\n  ICommonState,\n  ISelectLayoutContainerChildInfoOrderedFlow,\n  Array<ILayoutContainerChildInfo>\n>([\n  ({ state, contextIdentifier }) => ({ contextState: state.contentContexts[contextIdentifier] }),\n  ({ state, contextState }) => {\n    if(contextState == null) return { content: undefined };\n    const content = contextState.type === 'course'\n      ? state.courseContentById[contextState.state.courseId]\n      : state.presentationContentById[contextState.state.presentationId];\n    return { content };\n  },\n  ({ state, layoutContainerId }) => ({ tempSizes: state.contentEditor?.resizing[layoutContainerId] }),\n  ({ content, layoutContainerId, tempSizes }) => {\n    return {\n      childInfos: content?.orderedLayoutContainerIdsByParentId[layoutContainerId]?.map<ILayoutContainerChildInfo>(id => {\n        const layoutContainer = StateSelectorUtils.selectLayoutContainer(content, id);\n        const block = StateSelectorUtils.selectBlock(content, layoutContainer?.blockId);\n        let itemSize = layoutContainer?.layout.itemSize ?? 0;\n\n        if(tempSizes != null && id === tempSizes.child1Id) {\n          itemSize += tempSizes.sizeChangePercent;\n        } else if(tempSizes != null && id === tempSizes.child2Id) {\n          itemSize -= tempSizes.sizeChangePercent;\n        }\n\n        return {\n          id,\n          itemSize: MathUtils.roundPercentForItemSize(itemSize),\n          blockType: block?.type ?? null,\n        };\n      }),\n    };\n  },\n], result => result.childInfos ?? []);\n\nexport const selectTitle: SelectorCreator<ICommonState, string | null, { contextIdentifier: string }> = () => {\n  const sPresentationContent = selectPresentationContent();\n\n  return memoize((state: ICommonState, { contextIdentifier }) => {\n    const presentationContent = sPresentationContent(state, contextIdentifier);\n    return presentationContent?.title ?? null;\n  });\n};\n\ninterface ISelectLayoutContainerBlockRatio {\n  state: ICommonState;\n  contextIdentifier: string;\n  layoutContainerId: number;\n  contextState?: ContentContextState;\n  content?: IUniversalContentWithPerformanceData;\n  blockId?: number | null;\n  blockRatio?: number | null;\n  blockType?: BlockType | null;\n  isTemplate?: boolean;\n  blockInfo?: IBlockInfo | null;\n}\n\nexport const selectLayoutContainerBlockInfoFlow = (): (\n  (input: ISelectLayoutContainerBlockRatio) => ISelectLayoutContainerBlockRatio) => memoizedFlow<ISelectLayoutContainerBlockRatio>([\n    ({ state, contextIdentifier }) => ({ contextState: state.contentContexts[contextIdentifier] }),\n    ({ state, contextState }) => {\n      if(contextState == null) return {};\n      const content = contextState.type === 'course'\n        ? state.courseContentById[contextState.state.courseId]\n        : state.presentationContentById[contextState.state.presentationId];\n      return { content };\n    },\n    ({ content, layoutContainerId }) => {\n      const layoutContainer = StateSelectorUtils.selectLayoutContainer(content, layoutContainerId);\n      const block = StateSelectorUtils.selectBlock(content, layoutContainer?.blockId);\n      const blockPerformanceData = StateSelectorUtils.selectBlockPerformanceData(content, layoutContainer?.blockId);\n\n      return {\n        blockRatio: blockPerformanceData?.ratio,\n        blockType: block?.type,\n        isTemplate: blockPerformanceData?.isTemplateBlock,\n      };\n    },\n    ({ blockRatio, blockType, isTemplate }) => ({\n      blockInfo: blockType != null ? {\n        ratio: blockRatio ?? null,\n        type: blockType,\n        isTemplate: !!isTemplate,\n      } : null,\n    }),\n  ]);\n\nexport const layoutContainerBlockInfoSelectorCreator: SelectorCreator<\n  ICommonState,\n  IBlockInfo | null,\n  { contextIdentifier: string; layoutContainerId: number }\n> = () => {\n  const layoutContainerBlockInfoFlow = selectLayoutContainerBlockInfoFlow();\n\n  return (state, { contextIdentifier, layoutContainerId }) => {\n    const result = layoutContainerBlockInfoFlow({ state, contextIdentifier, layoutContainerId });\n    return result.blockInfo ?? null;\n  };\n};\n\ninterface INavigationInfoFlow {\n  state: ICommonState;\n  contextIdentifier: string;\n  contextState?: IPresentationContextState | null;\n  presentationId?: number | null;\n  slideId?: number | null;\n  orderedSlideIds?: Array<number>;\n  navigationInfo?: IPresentationNavigationInfo | null;\n}\n\nexport const navigationInfoSelectorCreator = createSelectorCreator<ICommonState, INavigationInfoFlow, IPresentationNavigationInfo | null>([\n  ({ state, contextIdentifier }) => {\n    const context = state.contentContexts[contextIdentifier];\n    if(context == null || context.type === 'course') return { contextState: null };\n    return { contextState: context.state };\n  },\n  ({ contextState }) => ({\n    presentationId: contextState?.presentationId ?? null,\n    slideId: contextState?.slideId ?? null,\n  }),\n  ({ state, presentationId }) => ({\n    orderedSlideIds: (presentationId != null\n      ? state.presentationContentById[presentationId] ?? null\n      : null)?.orderedSlideIds,\n  }),\n  ({ orderedSlideIds, slideId }) => ({\n    navigationInfo: orderedSlideIds && {\n      hasNextSlide: orderedSlideIds.findIndex(id => id === slideId) < orderedSlideIds.length - 1,\n      hasPreviousSlide: orderedSlideIds.findIndex(id => id === slideId) !== 0,\n      totalSlides: orderedSlideIds.length ?? 1,\n      slideIndex: (orderedSlideIds.findIndex(id => id === slideId) ?? 0) + 1,\n    },\n  }),\n], ({ navigationInfo }) => navigationInfo ?? null);\n\nexport const isFullscreenSelectorCreator: SelectorCreator<ICommonState, boolean | undefined, { contextIdentifier: string }> = () => {\n  const stateForContext = selectStateForContext();\n\n  return memoize((state, { contextIdentifier }) => {\n    const presentationState = stateForContext(state, contextIdentifier);\n    return presentationState?.isFullscreen;\n  });\n};\n\nexport const hasControlsSelectorCreator: SelectorCreator<ICommonState, boolean | undefined, { contextIdentifier: string }> = () => {\n  const stateForContext = selectStateForContext();\n\n  return memoize((state, { contextIdentifier }) => {\n    const presentationState = stateForContext(state, contextIdentifier);\n    return presentationState?.hasControls;\n  });\n};","import { SelectorCreator } from 'common/aurelia-decorators/select-state/definitions';\nimport { Mapper, memoizedFlow } from 'memoize-state';\nimport { ICommonState } from '../store.interface';\n\nexport function createSelectorCreator<TState, TIO, TResult>(\n  input: Array<Mapper<TIO & { state: TState }>>,\n  retriever: (output: TIO & { state: TState }) => TResult,\n): SelectorCreator<TState, TResult, TIO> {\n  return () => {\n    const flow = memoizedFlow<TIO & { state: TState }>(input);\n    return (state, dependencyObject) => {\n      const result = flow({\n        ...dependencyObject,\n        state,\n      });\n      return retriever(result);\n    };\n  };\n}\n\nexport const selectHasUnreadChats = (state: ICommonState): boolean => {\n  return state.chat.unreadChatRooms.length > 0;\n};\n","import { ICommonState, ICommonUserCourseInteractionState } from '../store.interface';\nimport { createSelectorCreator } from './selectors';\n\ninterface IVisitedHotSpotIdsByBlockIdSelectorFlow {\n  contextIdentifier: string;\n  userCourseInteraction?: ICommonUserCourseInteractionState;\n  blockId: number;\n  visitedHotSpotIds?: number[];\n}\n\nexport const visitedHotSpotIdsByBlockIdSelectorCreator = createSelectorCreator<ICommonState, IVisitedHotSpotIdsByBlockIdSelectorFlow, number[]>([\n  ({ state }) => {\n    const userCourseInteraction = state.userCourseInteraction ?? null;\n    if(userCourseInteraction == null) return {};\n    return { userCourseInteraction };\n  },\n  ({ userCourseInteraction, blockId }) => {\n    if(userCourseInteraction == null) return { visitedHotSpotIds: [] };\n    const visitedHotSpotIds = userCourseInteraction.visitedHotSpotIdsByBlockId?.[blockId] ?? [];\n\n    return { visitedHotSpotIds };\n  },\n], result => result.visitedHotSpotIds ?? []);","import { Timer } from 'common/aurelia-classes/Timer';\n\nexport class AnimationUtils {\n\n  public static shake(element: HTMLElement, totalDuration = 1200): Promise<void> {\n    const duration = 400;\n    const appendStyle: Keyframe = {\n      borderColor: 'var(--shake-border-color, inherit)',\n      backgroundColor: 'var(--shake-background-color, inherit)',\n      color: 'var(--shake-text-color, inherit)',\n    };\n    return this.playAnimation(() => element.animate(\n      [\n        { transform: 'translate(1px, 1px) rotate(0deg)', ...appendStyle },\n        { transform: 'translate(-1px, -2px) rotate(-1deg)' },\n        { transform: 'translate(-3px, 0px) rotate(1deg)' },\n        { transform: 'translate(3px, 2px) rotate(0deg)' },\n        { transform: 'translate(1px, -1px) rotate(1deg)' },\n        { transform: 'translate(-1px, 2px) rotate(-1deg)' },\n        { transform: 'translate(-3px, 1px) rotate(0deg)' },\n        { transform: 'translate(3px, 1px) rotate(-1deg)' },\n        { transform: 'translate(-1px, -1px) rotate(1deg)' },\n        { transform: 'translate(1px, 2px) rotate(0deg)' },\n        { transform: 'translate(1px, -2px) rotate(-1deg)', ...appendStyle },\n      ], {\n        duration,\n        iterations: totalDuration / duration,\n      },\n    ));\n  }\n\n  public static stampIn(element: HTMLElement): Promise<void> {\n    const duration = 100;\n    return this.playAnimation(() => element.animate(\n      [\n        { transform: 'scale(var(--stamp-in-scale, 7))', opacity: 0 },\n        { transform: 'scale(1)', opacity: 1 },\n      ], {\n        duration,\n        iterations: 1,\n        easing: 'ease-out',\n      },\n    ));\n  }\n\n  public static flashFadeOutward(element: HTMLElement): Promise<void> {\n    return this.playAnimation(() => element.animate([\n      { transform: 'scale(1)', opacity: 1 },\n      { transform: 'scale(1.5)', opacity: 0 },\n    ], {\n      duration: 1000,\n      iterations: 1,\n      easing: 'ease-out',\n    }));\n  }\n\n  public static flashFadeInward(element: HTMLElement): Promise<void> {\n    return this.playAnimation(() => element.animate([\n      { transform: 'scale(1.5)', opacity: 0 },\n      { transform: 'scale(1)', opacity: 1 },\n    ], {\n      duration: 1000,\n      iterations: 1,\n      easing: 'ease-in',\n    }));\n  }\n\n  public static colorFlash(element: HTMLElement, flashConfig?: Partial<IColorFlashConfig>): Promise<void> {\n    const config: IColorFlashConfig = {\n      backgroundColor: 'var(--flash-color, red)',\n      color: 'white',\n      sustainTime: 140,\n      releaseTime: 80,\n      ...flashConfig,\n    };\n    const duration = config.sustainTime + config.releaseTime;\n    const sustainEndPercent = config.sustainTime / duration;\n    return this.playAnimation(() => element.animate(\n      [\n        { backgroundColor: config.backgroundColor, color: config.color },\n        { backgroundColor: config.backgroundColor, color: config.color, offset: sustainEndPercent },\n      ], {\n        duration,\n        iterations: 1,\n        easing: 'ease-out',\n      },\n    ));\n  }\n\n  public static setAnimationFrameInterval(\n    callback: (elapsedMs: number, delta: number) => void,\n    options?: Partial<{\n      invokeImmediately: boolean;\n      limitFps: number;\n    }>,\n  ): { cancel: () => void } {\n    const deltaMsRequired = options?.limitFps ? (1000 / options.limitFps) : 0;\n\n    const timer = new Timer().start();\n    let handle: number | undefined;\n    let running = true;\n    let accumulatedDeltaMs = 0;\n\n    const wrappedCallback = (): void => {\n      const { elapsed, delta } = timer.snapMs();\n      accumulatedDeltaMs += delta;\n\n      if(accumulatedDeltaMs >= deltaMsRequired) {\n        try {\n          callback(elapsed, accumulatedDeltaMs);\n        } catch(e) {\n          console.error(`Error in animation frame callback: ${e}`);\n        }\n        accumulatedDeltaMs = 0;\n      }\n\n      if(running) {\n        handle = window.requestAnimationFrame(wrappedCallback);\n      }\n    };\n\n    if(options?.invokeImmediately) {\n      wrappedCallback();\n    } else {\n      handle = window.requestAnimationFrame(wrappedCallback);\n    }\n\n    return {\n      cancel: () => {\n        running = false;\n        if(handle != null) {\n          window.cancelAnimationFrame(handle);\n        }\n      },\n    };\n  }\n\n  private static async playAnimation(createFn: () => Animation): Promise<void> {\n    return new Promise(resolve => {\n      try {\n        const animation = createFn();\n        animation.onfinish = () => resolve();\n      } catch(e) {\n        console.error(e);\n        resolve();\n      }\n    });\n  }\n}\n\nexport interface IColorFlashConfig {\n  backgroundColor: string;\n  color: string;\n  sustainTime: number;\n  releaseTime: number;\n}\n","import { IFilterDto } from 'common/dto/filter/filter.interface';\nimport { ITenantBillingDetailsExportQueryDto, ITenantBillingSapExportQueryDto } from 'common/dto/tenant/billing/tenant.billing.interface';\nimport { IAccessCodeExportQueryDto } from 'common/dto/tenant/management/tenant.management.interface';\nimport { CSVType } from 'common/enum/csv.type.enum';\n\nexport type ParamValue = string | number | symbol | boolean | null | undefined;\n\nexport interface IUploadQuery<T> {\n  queryKey: string;\n  value: keyof T;\n}\n\nexport class ApiQueryStringUtils {\n  public static toQueryString(filter: IFilterDto): string {\n    const parts: Array<string> = [];\n    if(filter.limit) parts.push(`limit=${filter.limit}`);\n    if(filter.offset) parts.push(`offset=${filter.offset}`);\n\n    const orderQuery = ApiQueryStringUtils.buildQueryString('order', filter.order ?? []);\n    const filterQuery = ApiQueryStringUtils.buildQueryString('filter', filter.filter ?? []);\n    if(orderQuery) parts.push(orderQuery);\n    if(filterQuery) parts.push(filterQuery);\n\n    return parts.join('&');\n  }\n\n  public static buildObjectQueryString(obj: Record<string, ParamValue>): string | null {\n    const parts: Array<string> = [];\n    for(const [key, val] of Object.entries(obj)) {\n      parts.push(`${encodeURIComponent(key)}=${encodeURIComponent(val != null ? val.toString() : '')}`);\n    }\n    return parts.length === 0 ? null : parts.join('&');\n  }\n\n  public static buildArrayQueryString(paramName: string, array: ParamValue[]): string | null {\n    const parts: Array<string> = [];\n    for(const value of array) {\n      parts.push(`${encodeURIComponent(paramName)}[]=${encodeURIComponent(value != null ? value.toString() : '')}`);\n    }\n    return parts.length === 0 ? null : parts.join('&');\n  }\n\n  public static buildCsvExportQuery(urlPart: string, query: IAccessCodeExportQueryDto, csvType: CSVType): string {\n    const url = new URL(`${urlPart}?`, window.location.origin);\n\n    ApiQueryStringUtils.setSearchParam(url, 'exportType', csvType);\n    ApiQueryStringUtils.setSearchParam<IAccessCodeExportQueryDto>(url, 'quarter', query.quarter);\n    if(query.year) {\n      ApiQueryStringUtils.setSearchParam<IAccessCodeExportQueryDto>(url, 'year', query.year);\n    }\n    if(query.tenantId) {\n      ApiQueryStringUtils.setSearchParam<IAccessCodeExportQueryDto>(url, 'tenantId', query.tenantId);\n    }\n    if(typeof query.includeFreeCodes !== 'undefined') {\n      ApiQueryStringUtils.setSearchParam<IAccessCodeExportQueryDto>(url, 'includeFreeCodes', query.includeFreeCodes);\n    } else {\n      ApiQueryStringUtils.setSearchParam<IAccessCodeExportQueryDto>(url, 'includeFreeCodes', false);\n    }\n    if(typeof query.includeTenantsWhichSoldNone !== 'undefined') {\n      ApiQueryStringUtils.setSearchParam<IAccessCodeExportQueryDto>(url, 'includeTenantsWhichSoldNone', query.includeTenantsWhichSoldNone);\n    } else {\n      ApiQueryStringUtils.setSearchParam<IAccessCodeExportQueryDto>(url, 'includeTenantsWhichSoldNone', false);\n    }\n\n    const clientTypeQuery = query.clientTypes.map((cT, i) => `${encodeURIComponent('clientTypes')}[${i}]=${cT}`).join('&');\n\n    return `${url.toString()}&${clientTypeQuery}`;\n  }\n\n  public static buildBillingSapExportQuery(urlPart: string, query: ITenantBillingSapExportQueryDto): string {\n    const url = new URL(`${urlPart}?`, window.location.origin);\n\n    ApiQueryStringUtils.setSearchParam<ITenantBillingSapExportQueryDto>(url, 'quarter', query.quarter);\n    ApiQueryStringUtils.setSearchParam<ITenantBillingSapExportQueryDto>(url, 'exportType', query.exportType);\n    ApiQueryStringUtils.setSearchParam<ITenantBillingSapExportQueryDto>(url, 'year', query.year);\n    ApiQueryStringUtils.setSearchParam<ITenantBillingSapExportQueryDto>(url, 'includeTenantsWithoutActivities', !!query.includeTenantsWithoutActivities);\n\n    const clientTypeQuery = query.clientTypes.map((cT, i) => `${encodeURIComponent('clientTypes')}[${i}]=${cT}`).join('&');\n\n    return `${url.toString()}&${clientTypeQuery}`;\n  }\n\n  public static buildBillingDetailsExportQuery(urlPart: string, query: ITenantBillingDetailsExportQueryDto): string {\n    const url = new URL(`${urlPart}?`, window.location.origin);\n\n    ApiQueryStringUtils.setSearchParam<ITenantBillingDetailsExportQueryDto>(url, 'quarter', query.quarter);\n    ApiQueryStringUtils.setSearchParam<ITenantBillingDetailsExportQueryDto>(url, 'exportType', query.exportType);\n    ApiQueryStringUtils.setSearchParam<ITenantBillingDetailsExportQueryDto>(url, 'year', query.year);\n    ApiQueryStringUtils.setSearchParam<ITenantBillingDetailsExportQueryDto>(url, 'includeNonBillableActivities', !!query.includeNonBillableActivities);\n\n    return url.toString();\n  }\n\n  private static buildQueryString(\n    paramName: string,\n    array: Array<Record<string, ParamValue | Array<ParamValue>>>,\n  ): string | null {\n    const parts: Array<string> = [];\n    for(let i = 0; i < array.length; i++) {\n      for(const [key, value] of Object.entries(array[i])) {\n        if(!['field', 'dir', 'type', 'value'].includes(key)) {\n          continue;\n        }\n        /**\n         * for FilterOperationType.In\n         *\n         * filter[x][field]=<fieldName>\n         * & filter[x][type]=in\n         */\n        if(Array.isArray(value)) {\n          if(value.length === 0) {\n            parts.push(this.encodeParamKeyValue(paramName, i, key));\n          }\n          for(let y = 0; y < value.length; y++) {\n            /**\n             * & filter[x][value][]=<value>\n            */\n            const valueY = value[y];\n            parts.push(this.encodeParamKeyValue(paramName, i, key, valueY, true));\n          }\n          parts.push();\n        } else {\n          parts.push(this.encodeParamKeyValue(paramName, i, key, value));\n        }\n      }\n    }\n\n    return parts.length === 0 ? null : parts.join('&');\n  }\n\n  private static encodeParamKeyValue(\n    paramName: string,\n    index: number,\n    key: string,\n    value?: ParamValue,\n    partOfArray = false,\n  ): string {\n    const arrayString = partOfArray ? '[]' : '';\n\n    return `${encodeURIComponent(`${paramName}[${index}][${key}]${arrayString}`)}=${encodeURIComponent(value != null ? value.toString() : '')}`;\n  }\n\n  private static setSearchParam<T>(url: URL, paramName: keyof T, value: ParamValue): void {\n    url.searchParams.set(paramName.toString(), value != null ? value.toString() : '');\n  }\n}\n\n","import { productIdentityConfigs } from 'common/constants/app.constants';\nimport { LoginProvider } from 'common/enum/auth.enum';\nimport { Config } from 'common/fe-config/config';\nimport { AppFlavour } from 'common/fe-config/config.interface';\n\nexport class AppUtils {\n  private constructor() { }\n\n  public static get isElectronRemoteFEApp(): boolean {\n    return navigator?.userAgent?.includes('Electron');\n  }\n\n  public static get isApp(): boolean {\n    return !!__IS_CORDOVA__ || this.isElectronRemoteFEApp;\n  }\n\n  public static get isIOSApp(): boolean {\n    return this.platform === Platform.IosApp;\n  }\n\n  public static get isAndroidApp(): boolean {\n    return this.platform === Platform.AndroidApp;\n  }\n\n  public static get isLera(): boolean {\n    return AppUtils.appFlavour === AppFlavour.Lera;\n  }\n\n  public static get isFrp(): boolean {\n    return AppUtils.appFlavour === AppFlavour.Frp;\n  }\n\n  public static get isYarrive(): boolean {\n    return AppUtils.appFlavour === AppFlavour.Yarrive;\n  }\n\n  public static get ufeLoginPage(): string {\n    return AppUtils.isFrp ? '/setup/start' : '/setup/login';\n  }\n\n  public static get platform(): Platform {\n    if(this.isElectronRemoteFEApp) return Platform.ElectronRemoteFEApp;\n    switch(AppUtils.isApp && window.cordova.platformId) {\n      case 'ios': return Platform.IosApp;\n      case 'android': return Platform.AndroidApp;\n      case 'electron': return Platform.ElectronLocalFEApp;\n      default: return Platform.Browser;\n    }\n  }\n\n  public static get isElectronLocalFEApp(): boolean {\n    return this.isApp && this.platform === Platform.ElectronLocalFEApp;\n  }\n\n  public static get isElectronApp(): boolean {\n    return this.isElectronRemoteFEApp || this.isElectronLocalFEApp;\n  }\n\n  public static get isMobileApp(): boolean {\n    return this.isApp && (this.platform === Platform.IosApp || this.platform === Platform.AndroidApp);\n  }\n\n  public static getAppStoreUrl(platform: Platform, config: IPlatformConfig): string | null {\n    switch(platform) {\n      case Platform.IosApp:\n        return `https://apps.apple.com/app/id${config.iosAppId}`;\n      case Platform.AndroidApp:\n        return `https://play.google.com/store/apps/details?id=${config.androidPackageName}`;\n      default:\n        return null;\n    }\n  }\n\n  public static openAppStoreOrReload(config: IPlatformConfig): void {\n    const appStoreUrl = AppUtils.getAppStoreUrl(this.platform, config);\n    if(appStoreUrl) {\n      AppUtils.openExternal(appStoreUrl);\n    } else {\n      window.location.reload();\n    }\n  }\n\n  public static get isIOSBrowser(): boolean {\n    return /\\(iP.+; CPU .*OS (\\d+)[_\\d]*.*\\) AppleWebKit\\//.test(navigator.userAgent);\n  }\n\n  /**\n   *\n   * @param url the URL to open\n   * @returns `false` if not in app, `true` otherwise\n   */\n  public static openExternal(url: string): boolean {\n    if(!AppUtils.isApp) return false;\n\n    window.open(url, '_system');\n\n    return true;\n  }\n\n  /**\n   * prevents the screen from going dark\n   * needs to be released\n   * needs to be requested again after visibilitychange\n   * @returns WakeLockSentinel if available\n   */\n  public static async getScreenLock(): Promise<IWakeLockSentinel | null> {\n    let screenLock: IWakeLockSentinel | null = null;\n    if('wakeLock' in navigator) {\n      try {\n        if('wakeLock' in navigator) {\n          const anyNav = navigator as any;\n          screenLock = await anyNav.wakeLock.request('screen');\n        }\n      } catch(err) {\n        console.error(err.name, err.message);\n      }\n    }\n    return screenLock;\n  }\n\n  /**\n   * enables font-sizes smaller than ~8px on android devices\n   * https://github.com/next-mad-hatter/cordova-plugin-webview-minimumfontsize#a9722b12bd25f1209418c0526e68195be2fc9f46\n   */\n  public static setMinFontSize(size: number): void {\n    if(!AppUtils.isAndroidApp) return;\n    window.plugin?.webviewSetting.setMinFontSize?.(size);\n  }\n\n  public static getDataPrivacyAgreementLink(): string {\n    return productIdentityConfigs[this.appFlavour].dataPrivacyAgreementLink;\n  }\n\n  public static getAppName(nameInLogo: boolean = false): string | null {\n    switch(AppUtils.appFlavour) {\n      case AppFlavour.Lera:\n        return nameInLogo ? null : productIdentityConfigs[this.appFlavour].appName;\n      case AppFlavour.Risi:\n        return nameInLogo ? null : productIdentityConfigs[this.appFlavour].appName;\n      case AppFlavour.Frp:\n        return nameInLogo ? null : productIdentityConfigs[this.appFlavour].appName;\n      default:\n        return productIdentityConfigs[this.appFlavour].appName;\n    }\n  }\n\n  public static getSupportPhone(): string | null {\n    return productIdentityConfigs[this.appFlavour].supportPhone;\n  }\n\n  public static getSupportEmail(): string {\n    return productIdentityConfigs[this.appFlavour].supportMail;\n  }\n\n  public static getBrandUrl(): string {\n    return productIdentityConfigs[this.appFlavour].brandUrl;\n  }\n\n  public static getAppBrandUrl(): string {\n    return productIdentityConfigs[this.appFlavour].appBrandUrl;\n  }\n\n  public static getLegalNoticeLink(language?: string | null): string {\n    if(!language || language?.toLowerCase() === 'de') {\n      return productIdentityConfigs[this.appFlavour].legalNoticeLink;\n    }\n\n    return productIdentityConfigs[this.appFlavour].legalNoticeNonGermanDefaultLink;\n  }\n\n  public static updateFavicon(): void {\n    const config = productIdentityConfigs[this.appFlavour];\n    let link: HTMLLinkElement | null = document.querySelector('link[rel~=\\'icon\\']');\n    if(!link) {\n      link = document.createElement('link');\n      link.rel = 'icon';\n      link.href = config.favicon;\n      document.getElementsByTagName('head')[0].appendChild(link);\n      return;\n    }\n    link.href = config.favicon;\n  }\n\n  public static get appFlavour(): AppFlavour {\n    return Config().appFlavour;\n  }\n\n  public static getProviderDisplayName(provider: LoginProvider): string {\n    switch(provider) {\n      case LoginProvider.Buergerkarte:\n        return 'Bürgerkarte';\n      case LoginProvider.SAML:\n      case LoginProvider.AAD:\n        return provider.toUpperCase();\n      default:\n        return provider.charAt(0).toUpperCase() + provider.slice(1);\n    }\n  }\n}\n\nexport enum Platform {\n  AndroidApp = 'android-app',\n  Browser = 'browser',\n  IosApp = 'ios-app',\n  ElectronLocalFEApp = 'electron-local-fe-app',\n  ElectronRemoteFEApp = 'electron-remote-fe-app'\n}\n\nexport interface IPlatformConfig {\n  iosAppId: string;\n  androidPackageName: string;\n}\n\nexport interface IPluginConfig {\n  webviewSetting: {\n    setMinFontSize?: (size: number) => void;\n  };\n}\n\nexport interface IWakeLockSentinel extends EventTarget {\n  readonly released: boolean;\n  readonly type: string;\n  release(): Promise<undefined>;\n  onrelease: (evt: Event) => void;\n}\n","import { ApplicationError } from 'common/aurelia-classes/Error';\nimport { ErrorType } from 'common/enum/errortype.enum';\n\nexport class ApplicationErrorUtils {\n  private constructor() { }\n\n  public static getServerErrorTk(errorCode: number): string {\n    switch(errorCode) {\n      case 502:\n        return 'common.serverErrors.deploymentInProgressError';\n      default:\n    }\n\n    return 'common.serverErrors.genericServerError';\n  }\n\n  public static async catchApplicationErrorsWrap<T>(\n    safeOperation: () => Promise<T> | T,\n    errorHandler: (errorType: ErrorType, error: Error) => Promise<T> | T,\n  ): Promise<T> {\n    try {\n      // we need to await to catch errors here\n      const result = await safeOperation();\n      return result;\n    } catch(e) {\n      const error = e as Error;\n      if(error instanceof ApplicationError) {\n        const result = await errorHandler(error.message as ErrorType, error);\n        return result;\n      }\n      // rethrow all other errors\n      throw e;\n    }\n  }\n}\n","import { assertNotNullOrUndefined } from './assert';\n\nexport class ArrayUtils {\n  public static remove<T>(arr: Array<T>, matcher: (item: T) => boolean): Array<T> {\n    const newArr: Array<T> = [];\n\n    for(let key = 0; key < arr.length; key++) {\n      if(!matcher(arr[key])) {\n        newArr.push(arr[key]);\n      }\n    }\n\n    return newArr;\n  }\n\n  /**\n   * Filters an array in place, use with caution\n   */\n  public static filterInPlace<T>(array: Array<T>, filterFn: (item: T) => unknown): Array<T> {\n    for(let i = array.length - 1; i >= 0; i--) {\n      if(!filterFn(array[i])) array.splice(i, 1);\n    }\n    return array;\n  }\n\n  public static flatten<T>(arr: Array<Array<T>>): Array<T> {\n    const resultArray: Array<T> = [];\n\n    for(const rootItem of arr) {\n      for(const innerItem of rootItem) {\n        resultArray.push(innerItem);\n      }\n    }\n\n    return resultArray;\n  }\n\n  // Durstenfeld shuffle: https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle#The_modern_algorithm\n  public static shuffle<T>(arr: Array<T>): Array<T> {\n    const resultArray = [...arr];\n    for(let i = resultArray.length - 1; i > 0; i--) {\n      const newIndex = Math.floor(Math.random() * (i + 1));\n      const tempItem = resultArray[newIndex];\n      resultArray[newIndex] = resultArray[i];\n      resultArray[i] = tempItem;\n    }\n\n    return resultArray;\n  }\n\n  public static pushIfSet<T>(target: Array<T>, ...values: Array<T | undefined | null>): void {\n    values.forEach(v => {\n      if(v) {\n        target.push(v);\n      }\n    });\n  }\n\n  public static getLast<T>(arr: Array<T>): T | undefined {\n    return arr[arr.length - 1];\n  }\n\n  public static getRandom<T>(arr: Array<T>): T | undefined {\n    return arr[Math.floor(Math.random() * arr.length)];\n  }\n\n  public static findLast<T>(array: T[], matcher: (item: T) => unknown): T | undefined {\n    for(let i = array.length - 1; i >= 0; i--) {\n      if(matcher(array[i])) return array[i];\n    }\n\n    return undefined;\n  }\n\n  public static toRecord<TKey extends string | number | symbol, TItem, TEntry = TItem>(\n    arr: Array<TItem>,\n    getKey: (item: TItem) => TKey,\n    getEntry: (item: TItem, existing: TEntry | undefined, index: number) => TEntry,\n  ): Record<TKey, TEntry> {\n    const record: Record<TKey, TEntry> = {} as Record<TKey, TEntry>;\n    for(let i = 0; i < arr.length; i++) {\n      const key = getKey(arr[i]);\n      record[key] = getEntry(arr[i], record[key], i);\n    }\n    return record;\n  }\n\n  public static toPartialRecord<TKey extends string | number | symbol, TItem, TEntry = TItem>(\n    arr: Array<TItem>,\n    getKey: (item: TItem) => TKey,\n    getEntry: (item: TItem, existing: TEntry | undefined, index: number) => TEntry,\n  ): Partial<Record<TKey, TEntry>> {\n    return this.toRecord(arr, getKey, getEntry);\n  }\n\n  /**\n   * Builds a lookup table from a flat list representation of a tree (e.g. pages with recursive sub pages),\n   * mapping each node's id to its root node's id.\n   * @param nodes A flat list representation of a tree, linked by parentId->id\n   */\n  public static getRootNodeIdsByNodeId(nodes: Array<{ id: number; parentId: number | null }>): Record<number, number> {\n    const rootNodeIdsByNodeId: Partial<Record<number, number>> = {};\n    const nodesById = ArrayUtils.toRecord(nodes, n => n.id, n => n);\n\n    // For each node, find its root chapter by recursively finding parent nodes.\n    // All nodes encountered will have the same parent node, so they can all be entered into the lookup table right away.\n    // As later nodes will traverse the same intermediate steps as previous nodes, they can leverage this and can take a shortcut.\n    for(const node of nodes) {\n      const traversedNodes = [node.id];\n      let tempRootNodeId = node.id;\n      let currentlyTraversedNode = node;\n\n      while(currentlyTraversedNode.parentId !== null) {\n        // If we encounter a node that points back to the node we started with, we have a loop and therefore not a tree\n        if(currentlyTraversedNode.parentId === node.id) {\n          throw new Error(`detected internal loop (node id ${node.id})`);\n        }\n\n        const currentlyTraversedNodeParentId = currentlyTraversedNode.parentId;\n        const existingNode = rootNodeIdsByNodeId[currentlyTraversedNodeParentId];\n        // explicit undefined check on purpose\n        if(existingNode === undefined) {\n          // if we don't know this node's root node, we have to find it by traversing upward\n          const currentlyTraversedNodeParent = nodesById[currentlyTraversedNodeParentId];\n          assertNotNullOrUndefined(currentlyTraversedNodeParent, `parent node ${currentlyTraversedNodeParentId} of node ${currentlyTraversedNode.id} does not exist`);\n\n          traversedNodes.push(currentlyTraversedNodeParentId);\n          tempRootNodeId = currentlyTraversedNodeParentId;\n          currentlyTraversedNode = currentlyTraversedNodeParent;\n        } else {\n          // if we do, we can take a shortcut\n          tempRootNodeId = existingNode;\n          break;\n        }\n      }\n\n      // add all traversed nodes to the lookup table right away, so the upcoming nodes can benefit from the information\n      for(const traversed of traversedNodes) {\n        rootNodeIdsByNodeId[traversed] = tempRootNodeId;\n      }\n    }\n\n    return rootNodeIdsByNodeId as Record<number, number>;\n  }\n\n  public static groupBy<TItem, TGroupNames extends string | number | symbol>(\n    array: Array<TItem>,\n    groupingFn: (item: TItem) => TGroupNames,\n  ): Partial<Record<TGroupNames, TItem[]>> {\n    return this.groupByMap(array, groupingFn, item => item);\n  }\n\n  public static groupByMap<TItem, TGroupNames extends string | number | symbol, TResult = TItem>(\n    array: Array<TItem>,\n    groupingFn: (item: TItem) => TGroupNames,\n    map: (item: TItem) => TResult,\n  ): Partial<Record<TGroupNames, TResult[]>> {\n    return this.toRecord(array, groupingFn, (item, existing) => {\n      if(!existing) return [map(item)];\n      existing.push(map(item));\n      return existing;\n    });\n  }\n\n  public static getFirstNonNullValue<T>(...args: Array<null | undefined | T>): T | null {\n    return args.find(i => i != null) ?? null;\n  }\n\n  public static toFixedLength<T>(array: T[], desiredLength: number, padValue: T): T[] {\n    const newArray = new Array(desiredLength).fill(padValue);\n    newArray.splice(0, array.length, ...array.slice(0, desiredLength));\n    return newArray;\n  }\n\n  public static mapUnique<T, R>(values: Array<T>, mapFn: (item: T) => R): Array<R> {\n    return this.uniquePrimitive(values.map(mapFn));\n  }\n\n  /**\n   * ensures the returning array includes only unique elements\n   */\n  public static uniquePrimitive<T>(collection: Array<T>): Array<T> {\n    return Array.from(new Set(collection));\n  }\n\n  /**\n   * Creates an array with all duplicates removed. An item is a duplicate of another, if getKey returns the same value\n   */\n  public static uniqueBy<T>(collection: Array<T>, getKey: (value: T) => unknown): Array<T> {\n    const uniqueKeys = new Set();\n\n    // filter function with a local side effect ;)\n    return collection.filter(c => {\n      const key = getKey(c);\n      if(uniqueKeys.has(key)) return false;\n\n      uniqueKeys.add(key);\n      return true;\n    });\n  }\n\n  public static findIndices<T>(values: Array<T>, evaluate: (item: T) => boolean): Array<number> {\n    return values.reduce<Array<number>>((acc, item, idx) => (evaluate(item)\n      ? [...acc, idx]\n      : acc), []);\n  }\n\n  public static immutableSort<T>(values: Array<T>, sorterFunction: (a: T, b: T) => number): Array<T> {\n    return [...values].sort(sorterFunction);\n  }\n\n  public static max<T>(values: Array<T>, selectorFunction: (property: T) => number): T | null {\n    if(values.length === 1) return values[0];\n    return values.find(value => values.every(innerValue => selectorFunction(innerValue) < selectorFunction(value))) ?? null;\n  }\n\n  /**\n   * Maps all array values of the given array using the async callbackfn.\n   * All callbackfns are called concurrently!\n   *\n   * @param array\n   * @param callbackfn\n   */\n  public static async mapAsync<T, U>(array: T[], callbackfn: (value: T, index: number, array: T[]) => Promise<U>): Promise<U[]> {\n    return Promise.all(array.map(callbackfn));\n  }\n\n  public static async filterAsync<T>(array: T[], callbackfn: (value: T, index: number, array: T[]) => Promise<boolean>): Promise<T[]> {\n    const filterMap = await ArrayUtils.mapAsync(array, callbackfn);\n    return array.filter((value, index) => filterMap[index]);\n  }\n\n  public static async findAsync<T>(array: T[], callbackfn: (value: T, index: number, array: T[]) => Promise<boolean>): Promise<T | null> {\n    for(let i = 0; i < array.length; i++) {\n      if((await callbackfn(array[i], i, array))) return array[i];\n    }\n    return null;\n  }\n\n  /**\n   * pushes non null items to an array\n   * @param collection\n   * @param newItems\n   * @returns the new array length\n   */\n  public static pushNonNullEntries<T>(collection: Array<T>, newItems: Array<T | null>): number {\n    return collection.push(...newItems.filter((item): item is T => item != null));\n  }\n\n  /**\n   * returns all elements from arrA that also occur in arrB\n   */\n  public static intersect<T>(arrA: T[], arrB: T[], getKey: (item: T) => unknown = item => item): T[] {\n    const bSet = new Set(arrB.map(getKey));\n    return arrA.filter(el => bSet.has(getKey(el)));\n  }\n\n  /**\n   * returns all elements from arr that do not occur in toExclude\n   */\n  public static exclude<T>(arr: T[], toExclude: T[], getKey: (item: T) => unknown = item => item): T[] {\n    const bSet = new Set(toExclude.map(getKey));\n    return arr.filter(el => !bSet.has(getKey(el)));\n  }\n\n  public static simpleDiff<T>(oldArray: T[], newArray: T[], getKey: (item: T) => unknown = item => item): { removed: T[]; added: T[] } {\n    return {\n      removed: this.exclude(oldArray, newArray, getKey),\n      added: this.exclude(newArray, oldArray, getKey),\n    };\n  }\n\n  public static areKeysIdentical<T>(oldArray: T[], newArray: T[], getKey: (item: T) => unknown = item => item): boolean {\n    if(oldArray.length !== newArray.length) {\n      return false;\n    }\n    const oldArrayKeys = oldArray.map(getKey).sort();\n    const newArrayKeys = newArray.map(getKey).sort();\n    for(let i = 0; i < newArrayKeys.length; i++) {\n      if(oldArrayKeys[i] !== newArrayKeys[i]) {\n        return false;\n      }\n    }\n    return true;\n  }\n\n  /*\n   * Detects a circle in a set of edges.\n   * Each node can have multiple incoming and outgoing edges.\n   * The properties that denote the outgoing and target node Ids must be of the same type\n   */\n  public static hasCircularReferences<\n    TType extends string | number | symbol,\n    T extends { [P in TId | TRefId]: TType },\n    TId extends keyof T,\n    TRefId extends keyof T,\n  >(\n    edges: T[],\n    outgoingNodeIdProperty: TId,\n    targetNodeIdProperty: TRefId,\n  ): boolean {\n    const nodesById = ArrayUtils.groupBy<T, TType>(edges, e => e[outgoingNodeIdProperty]);\n\n    // When all paths of a node end without a circle, it gets added to this set. Nodes that are in here do not need to be re-checked if they are encountered again.\n    const alreadyCheckedNodeIds = new Set<TType>();\n\n    // This set holds the currently traversed nodes, basically \"breadcrumbs\". If we are along a path and find a node that is already in here, we have a circle.\n    const currentlyTraversingNodeIds = new Set<TType>();\n\n    const checkRecursively = (nodeId: TType): boolean => {\n      if(alreadyCheckedNodeIds.has(nodeId)) return false;\n      if(currentlyTraversingNodeIds.has(nodeId)) return true;\n\n      currentlyTraversingNodeIds.add(nodeId);\n\n      for(const node of nodesById[nodeId] ?? []) {\n        if(checkRecursively(node[targetNodeIdProperty])) return true;\n      }\n\n      alreadyCheckedNodeIds.add(nodeId);\n      currentlyTraversingNodeIds.delete(nodeId);\n\n      return false;\n    };\n\n    for(const edge of edges) {\n      if(checkRecursively(edge[outgoingNodeIdProperty])) return true;\n    }\n\n    return false;\n  }\n\n  /**\n   * Checks if all items are equal.\n   */\n  public static allEqual<T>(items: T[], selectorFn: (item: T) => any = item => item): boolean {\n    if(items.length <= 1) return true;\n\n    const first = selectorFn(items[0]);\n\n    return items.slice(1).every(item => selectorFn(item) === first);\n  }\n}\n","import { UniversalBlock } from 'common/dto/content-editor/content.editor.interface';\nimport { ContentElementType, NonBlockElementType } from 'common/dto/content-editor/content.editor.types';\nimport { IPictureCropOptionsDto } from 'common/dto/content/pictures/picture.block.render.options.interface';\nimport { IPictureDto } from 'common/dto/course/content/course.interface';\nimport { BlockType } from 'common/dto/course/content/course.types';\n\nexport class BlockUtils {\n  public static getPictureBlockRatio(picture: IPictureDto, pictureBlockRenderOptions: IPictureCropOptionsDto | null): number {\n    const height = pictureBlockRenderOptions ? pictureBlockRenderOptions.bottom - pictureBlockRenderOptions.top : picture?.height ?? 0;\n    const width = pictureBlockRenderOptions ? pictureBlockRenderOptions.right - pictureBlockRenderOptions.left : picture?.width ?? 0;\n    return width / height;\n  }\n\n  public static isSingleBlockPage(blockType: BlockType): boolean {\n    return [\n      BlockType.MP_FLIPPING_CARD,\n      BlockType.MP_HOT_SPOT,\n      BlockType.MP_INTERACTIVE_TEXT,\n      BlockType.MP_RFG,\n      BlockType.MP_RISI,\n      BlockType.MP_SCORM,\n      BlockType.MP_SEQUENCE_START,\n    ].includes(blockType);\n  }\n\n  public static getIconName(type: ContentElementType): string {\n    switch(type) {\n      case BlockType.AUDIO: return 'Uce/Volume';\n      case BlockType.EMPTY_CARD: return 'Uce/Card';\n      case BlockType.FILE: return 'Uce/File';\n      case BlockType.FORMULA: return 'Uce/SuperScript';\n      case BlockType.MP_FLIPPING_CARD: return 'Uce/FlippingCard';\n      case BlockType.MP_HOT_SPOT: return 'Uce/HotSpot';\n      case BlockType.MP_INTERACTIVE_TEXT: return 'Uce/InteractiveText';\n      case BlockType.MP_QUESTION: return 'Uce/Question';\n      case BlockType.MP_RFG: return 'Uce/RapidFire';\n      case BlockType.MP_RISI: return 'Uce/Risi';\n      case BlockType.MP_SCORM: return 'Uce/Scorm';\n      case BlockType.MP_SEQUENCE_START: return 'Uce/Sequence';\n      case BlockType.MP_TILES: return 'Uce/Tiles';\n      case BlockType.PICTURE: return 'Uce/Photo';\n      case BlockType.QUESTION: return 'Uce/Question';\n      case BlockType.SPACER: return 'Uce/Spacer';\n      case BlockType.TEXT: return 'Uce/Text';\n      case BlockType.VIDEO: return 'Uce/Film';\n      case BlockType.VIMEO: return 'Uce/Vimeo';\n      case BlockType.YOUTUBE: return 'Uce/Youtube';\n      case NonBlockElementType.AiQuiz: return 'Uce/Question';\n      case NonBlockElementType.AiText: return 'Uce/Text';\n      case NonBlockElementType.Card: return 'Uce/Card';\n      default: return 'System/Tabler/Alert-Triangle';\n    }\n  }\n\n  public static getModifierIconName(type: ContentElementType): string | null {\n    switch(type) {\n      case NonBlockElementType.AiQuiz:\n      case NonBlockElementType.AiText:\n        return 'System/Ai';\n      default:\n        return null;\n    }\n  }\n\n  public static getTemplateTextBlockText(\n    blockType: BlockType,\n  ): string | null {\n    switch(blockType) {\n      case BlockType.TEXT:\n        return JSON.stringify({\n          type: 'doc',\n          content: [\n            {\n              type: 'heading',\n              attrs: {\n                textAlign: 'left',\n                level: 3,\n              },\n              content: [{ type: 'text', text: 'Headline lorem ipsum dolor sit amet' }],\n            },\n            {\n              type: 'paragraph',\n              attrs: { textAlign: 'left' },\n              content: [\n                {\n                  type: 'text',\n                  text: 'lorem ipsum dolor sit amet mea tassilo lorem ipsum dolor sit amet mea tassilo.',\n                },\n              ],\n            },\n          ],\n        });\n\n    }\n    return null;\n  }\n\n  public static isTemplateBlock(block: UniversalBlock): boolean {\n    switch(block.type) {\n      case BlockType.PICTURE:\n        return !block.picture;\n      case BlockType.VIDEO:\n      case BlockType.VIMEO:\n      case BlockType.YOUTUBE:\n        return !block.videoUrl;\n      case BlockType.MP_TILES:\n        return !block.text;\n      case BlockType.FILE:\n        return !block.fileUrl;\n      case BlockType.AUDIO:\n        return !block.audioUrl;\n      case BlockType.EMPTY_CARD:\n        return true;\n    }\n    return false;\n  }\n\n  public static isDualVideoBlock(block: UniversalBlock): boolean {\n    return block.pipVideoUrl != null || !!block?.pipVideoSources?.length;\n  }\n\n  public static isVideoBlock(block: UniversalBlock): boolean {\n    return block.type === BlockType.VIDEO\n      || block.type === BlockType.VIMEO\n      || block.type === BlockType.YOUTUBE;\n  }\n\n  public static blockAllowsSizing(block: UniversalBlock): boolean {\n    return this.isVideoBlock(block)\n      || block.type === BlockType.AUDIO\n      || block.type === BlockType.FILE\n      || block.type === BlockType.PICTURE\n      || block.type === BlockType.MP_TILES\n      || block.type === BlockType.TEXT;\n  }\n}","import { UAParser } from 'ua-parser-js';\n\nexport class BrowserUtils {\n  public static getBrowserSessionString(uaString?: string): string {\n    const parser = new UAParser(uaString);\n    // eslint-disable-next-line max-len\n    return `${parser.getDevice().vendor || ''} ${parser.getOS().name} ${parser.getOS().version} ${parser.getBrowser().name} ${parser.getBrowser().version}`.trim();\n  }\n\n  public static get isSafari(): boolean {\n    const ua = navigator.userAgent.toLowerCase();\n    if(ua.indexOf('safari') !== -1) {\n      return !(ua.indexOf('chrome') > -1);\n    }\n\n    return false;\n  }\n}\n","export class BunnyUtils {\n  public static thumbnailFromUrl(url: string | undefined): string | null {\n    if(url?.includes('/play_')) {\n      return url.replace(/\\/play_.*/, '/thumbnail.jpg');\n    }\n    return null;\n  }\n}"],"names":[],"sourceRoot":""}