{"version":3,"file":"chunk-dgiqfqyq.js","sources":["packages/sports/libs/favourites/core/feature/src/favourites.model.ts","packages/sports/libs/favourites/core/feature/src/helpers.ts","packages/sports/libs/favourites/core/feature/src/favourites-fixtures.service.ts","packages/sports/libs/favourites/core/feature/src/favourites-model.factory.ts","packages/sports/libs/favourites/core/feature/src/favourites.service.ts","packages/sports/web/app/src/event-list-shared/sport/virtual-competition.service.ts","packages/sports/web/app/src/event-list-shared/sport/standings.service.ts","packages/sports/web/app/src/event-list-shared/sport/competitions/prune-helper.service.ts","packages/sports/web/app/src/widget/core/modular-config-accessor.service.ts","packages/vanilla/lib/features/icons/src/icons.client-config.ts","packages/vanilla/lib/features/icons/src/icon-fast.service.ts","packages/vanilla/lib/features/icons/src/icon-fast.component.ts","packages/sports/web/app/src/crm-promotion/promotion-status-button.component.html","packages/sports/web/app/src/crm-promotion/promotion-status-button.component.ts","packages/sports/web/app/src/crm-promotion/offer-status.service.ts","packages/sports/web/app/src/crm-promotion/tracking.service.ts","packages/sports/web/app/src/crm-promotion/banner-more-info.html","packages/sports/web/app/src/crm-promotion/banner-more-info.component.ts","packages/sports/web/app/src/crm-promotion/banner.service.ts","packages/sports/web/app/src/crm-promotion/promotion-status-button-wrapper.component.html","packages/sports/web/app/src/crm-promotion/promotion-status-button-wrapper.component.ts","packages/sports/libs/tracking/feature/src/track-referring-action.directive.ts","packages/design-system/ui/list-item/src/list-item.component.ts","packages/sports/libs/common/ui/sports-list-items/src/lib/list-item.html","packages/sports/libs/common/ui/sports-list-items/src/lib/list-item.component.ts","packages/sports/web/app/src/common/item/item.html","packages/sports/web/app/src/common/item/item.component.ts","packages/sports/web/app/src/crm-promotion/badge.component.ts","packages/sports/web/app/src/crm-promotion/badge.html","packages/sports/web/app/src/perf-cdk/lcp-candidate.directive.ts","packages/design-system/ui/card/src/card.component.ts","packages/sports/web/app/src/crm-promotion/banner.html","packages/sports/web/app/src/crm-promotion/banner.component.ts","packages/sports/web/app/src/crm-promotion/promo-item.html","packages/sports/web/app/src/crm-promotion/promo-item.component.ts","packages/sports/web/app/src/crm-promotion/promo-item-list.html","packages/sports/web/app/src/crm-promotion/promo-item-list.component.ts","packages/sports/web/app/src/crm-promotion/crm-promotion.module.ts","packages/sports/web/app/src/directives/scrolled-to-bottom.directive.ts","packages/sports/web/app/src/directives/class-auto-remove.directive.ts","packages/sports/web/app/src/directives/column-width.directive.ts","packages/sports/web/app/src/directives/height-to-bottom.directive.ts","packages/sports/web/app/src/directives/info-tooltip.directive.ts","packages/sports/web/app/src/directives/outside-interaction.directive.ts","packages/sports/web/app/src/directives/scroll-ios-hack/scroll-ios-hack.directive.ts","packages/sports/web/app/src/directives/scroll-to.directive.ts","packages/sports/web/app/src/directives/stop-event-propagation.directive.ts","packages/sports/web/app/src/directives/toolbar-buttons-height.directive.ts","packages/sports/web/app/src/directives/directives.module.ts","packages/sports/web/app/src/directives/scroll-selected-in-view/scroll-selected-in-view.directive.ts","node_modules/@rx-angular/cdk/fesm2022/cdk-internals-scheduler.mjs","node_modules/@rx-angular/cdk/fesm2022/cdk-coercing.mjs","node_modules/@rx-angular/cdk/fesm2022/cdk-render-strategies.mjs","packages/sports/web/app/src/tab-bar/tab-bar.html","packages/sports/web/app/src/tab-bar/tab-bar.component.ts","packages/sports/web/app/src/tab-bar/tab-bar.module.ts","packages/design-system/ui/shared/src/safari-browser-token.interface.ts","packages/design-system/ui/shared/src/touch-click.directive.ts","packages/sports/libs/common/core/feature/font-resizer/src/lib/font-resizer-base.service.ts","packages/sports/libs/common/core/feature/font-resizer/src/lib/font-resizer.html","packages/sports/libs/common/core/feature/font-resizer/src/lib/font-resizer.component.ts","packages/sports/web/app/src/shared-priceboost/Components/priceboost-count-signpost.component.html","packages/sports/web/app/src/shared-priceboost/Components/priceboost-count-signpost.component.ts","packages/sports/web/app/src/shared-priceboost/shared-priceboost.module.ts","packages/sports/libs/common/core/utils/dom/src/lib/trust.pipe.ts","packages/sports/web/app/src/widget/common/widget-tab-bar.component.html","packages/sports/web/app/src/widget/common/widget-tab-bar.component.ts","packages/sports/web/app/src/widget/common/widget-header.component.html","packages/sports/web/app/src/widget/common/widget-header.component.ts","packages/sports/web/app/src/widget/common/widget-common.module.ts","packages/sports/libs/betting-offer/feature/animation/src/animation-provider.service.ts","packages/sports/libs/betting-offer/feature/model/src/event-details-column.type.ts","packages/sports/libs/grid/media/feature/model/src/model.ts","packages/sports/web/app/src/event-details-common/event-details-helper.service.ts","packages/sports/libs/betting-offer/feature/media/src/event-media-helper.service.ts","packages/sports/libs/tracking/feature/src/tracking-video-source.ts","packages/sports/libs/grid/media/feature/state/src/actions.ts","packages/sports/libs/grid/media/feature/state/src/media.state.ts","packages/sports/libs/grid/core/feature/store/src/grid.actions.ts","packages/sports/libs/grid/core/feature/store/src/grid.state.ts","packages/sports/libs/grid/core/feature/model/src/grid.model.ts","packages/sports/libs/grid/core/feature/store/src/grid.reducer.ts","packages/sports/libs/grid/core/feature/store/src/grid-store.service.ts","packages/sports/libs/betting-offer/feature/types/src/cds/angstrom-tag-values.ts","packages/sports/libs/betting-offer/feature/fixture-factories/src/detailed-grouping.factory.ts","packages/sports/libs/betting-offer/feature/fixture-factories/src/mappers/sports-market-grouping-fallback.service.ts","packages/sports/libs/betting-offer/feature/fixture-factories/src/mappers/event-sitemap-grouping.factory.ts","packages/sports/libs/betting-offer/feature/fixture-factories/src/mappers/detailed-fixture-market.factory.ts","packages/sports/libs/betting-offer/feature/fixture-factories/src/mappers/linked-fixture.service.ts","packages/sports/libs/betting-offer/feature/fixture-factories/src/mappers/detailed-create.factory.ts","packages/sports/libs/betting-offer/feature/fixture-factories/src/mappers/detailed-delete.factory.ts","packages/sports/libs/betting-offer/feature/fixture-factories/src/mappers/detailed-update.factory.ts","packages/sports/libs/betting-offer/feature/fixture-factories/src/mappers/detailed-fixture.factory.ts","packages/sports/libs/grid/core/feature/favourites/src/grid-favourite.service.ts","packages/sports/libs/grid/core/feature/factories/src/grid-base.factory.ts","packages/sports/libs/grid/core/feature/factories/src/grid-fixture.factory.ts","packages/sports/libs/grid/core/feature/factories/src/event.factory.ts","packages/sports/libs/common/core/utils/rxjs/src/lib/ignore-elements-typed.ts","node_modules/@ngrx/operators/fesm2022/ngrx-operators.mjs","packages/sports/libs/grid/core/feature/observables/src/observable-grid.ts","packages/sports/libs/grid/core/feature/observables/src/observable-grid.provider.ts","packages/sports/libs/grid/core/feature/observables/src/observable-grid.factory.ts","packages/sports/web/app/src/competition-list/services/competition-list.service.ts","packages/sports/web/app/src/cds/marquee-api.service.ts","packages/sports/web/app/src/highlights-marquee/marquee-request-builder.service.ts","packages/sports/web/app/src/highlights-marquee/marquee.service.ts","packages/sports/web/app/src/cds/statistics-api.service.ts","packages/sports/web/app/src/statistics/statistics-api.service.ts","packages/sports/libs/user/feature/src/model/user.models.ts","packages/sports/libs/user/feature/src/services/sports-client-config-refresh.service.ts","packages/sports/web/app/src/data-refresh/fresh-data-provider.service.ts","packages/sports/web/app/src/data-refresh/ellapsed-timer.ts","packages/sports/web/app/src/data-refresh/online-detection.service.ts","packages/sports/web/app/src/data-refresh/fresh-data.service.ts","packages/sports/web/app/src/statistics/statistics-config.service.ts","packages/sports/libs/event-subscription/feature/base-subscription/src/base-subscription.service.ts","packages/sports/libs/event-subscription/feature/precreated-group-commands/src/utils.ts","packages/sports/libs/event-subscription/feature/event-subscription/src/event-subscription.service.ts","packages/sports/libs/common/core/utils/rxjs/src/lib/buffer-with-size.ts","packages/sports/web/app/src/event-details-common/event-details-common.utils.ts","packages/sports/web/app/src/event-details-common/event-details-common.models.ts","packages/sports/web/app/src/favourites/live-events-subscription.service.ts","packages/sports/web/app/src/favourites/live-favourites-fixtures.service.ts","packages/sports/web/app/src/navigation/navigation-tracking.model.ts","packages/sports/libs/common/core/utils/route-params/src/lib/route-params.ts","packages/sports/web/app/src/navigation/navigation-router.service.ts","packages/sports/libs/common/core/utils/collection/src/lib/collection.ts","packages/sports/web/app/src/event-list-shared/sport/competitions/competition.models.ts","packages/sports/web/app/src/teampages-core/team-pages.model.ts","packages/sports/web/app/src/teampages-core/team-pages-api.service.ts","packages/sports/web/app/src/teampages-core/team-pages-core-marquee.service.ts","packages/sports/web/app/src/teampages-core/team-pages-standing.service.ts","packages/sports/web/app/src/teampages-core/team-pages-core-grid.service.ts","packages/sports/web/app/src/teampages-core/team-pages-core.service.ts","packages/sports/web/app/src/deferred/component-loader.component.ts","packages/sports/web/app/src/deferred/component-proxy.directive.ts","packages/sports/web/app/src/favourites/toggle/favourite-toggle-tracking.service.ts","packages/sports/web/app/src/favourites/toggle/favourite-toggle.component.ts","packages/sports/web/app/src/favourites/toggle/favourite-toggle.html","packages/sports/web/app/src/favourites/toggle/favourite-league-toggle.component.ts","packages/sports/web/app/src/favourites/toggle/favourite-event-toggle.component.ts","packages/sports/web/app/src/favourites/toggle/favourite-participant-toggle.component.ts","packages/sports/web/app/src/favourites/favourites.module.ts","packages/sports/web/app/src/widget/core/widget-skeleton.model.ts","packages/sports/web/app/src/widget/core/widget-registry.service.ts","packages/sports/web/app/src/event-list-shared/sport/tournament-groups-participants.html","packages/sports/web/app/src/event-list-shared/sport/tournament-groups-participants.component.ts","packages/sports/web/app/src/event-list-shared/sport/tournament-groups.html","packages/sports/web/app/src/event-list-shared/sport/tournament-groups.component.ts","packages/sports/web/app/src/event-list-shared/sport/reward-token-eligible-tag.html","packages/sports/web/app/src/event-list-shared/sport/reward-token-eligible-tag.component.ts","packages/sports/web/app/src/event-list-shared/sport/fixture-list-header.html","packages/sports/web/app/src/event-list-shared/sport/fixture-list-header.component.ts","packages/sports/web/app/src/event-list-shared/sport/competition-header-widget.html","packages/sports/web/app/src/event-list-shared/sport/competition-header-widget.component.ts","packages/sports/web/app/src/deferred/team-pages-component-loader.service.ts","packages/sports/web/app/src/event-list-shared/sport/competition-teams-widget.html","packages/sports/web/app/src/event-list-shared/sport/competition-teams-widget.component.ts","packages/sports/web/app/src/event-list-shared/sport/services/event-list.service.ts","packages/sports/web/app/src/breadcrumbs/breadcrumbs.resolver.ts","packages/sports/web/app/src/router/regex.matcher.ts","packages/sports/web/app/src/router/name-identifier.matcher.ts","packages/sports/web/app/src/navigation-core/navigation-core.models.ts","node_modules/@angular/router/esm2022/src/utils/collection.mjs","node_modules/@angular/router/esm2022/src/url_tree.mjs","node_modules/@angular/router/esm2022/src/utils/config.mjs","node_modules/@angular/router/esm2022/src/utils/preactivation.mjs","packages/sports/web/app/src/router/router.exports.ts","packages/sports/web/app/src/sitemap/sitemap-save-state.service.ts","packages/sports/web/app/src/sitemap/sitemap-state.service.ts","packages/sports/web/app/src/sitemap/sitemap.models.ts","packages/sports/web/app/src/sitemap/sitemap-state-mapper.ts","packages/sports/web/app/src/sitemap/sitemap.service.ts","packages/sports/web/app/src/sitemap/sitemap.bootstrap.service.ts","packages/sports/web/app/src/breadcrumbs/breadcrumbs-slot.service.ts","packages/sports/web/app/src/live-now-item/live-now-link-register.service.ts","packages/sports/web/app/src/common/local-storage.service.ts","packages/sports/web/app/src/competition-list/competition-list-seo.service.ts","packages/sports/web/app/src/seo/seo-content.service.ts","packages/sports/web/app/src/option-pick/pick-source.provider.ts","packages/sports/web/app/src/widget/core/modular-pick-source.service.ts","packages/sports/common/betslip/modules/quick-bet/quick-bet.state.ts","packages/sports/common/betslip/modules/combo-bet/selectors.ts","packages/sports/common/betslip/modules/validation/errors/notify-user-error.ts","packages/sports/common/betslip/modules/validation/errors/result/pick-invisible.ts","packages/sports/common/betslip/modules/validation/errors/pre-check/pick-locked-pre-check-error.ts","packages/sports/common/betslip/modules/betslip-bar/selectors.ts","packages/sports/common/betslip/modules/settings/selectors.ts","packages/sports/common/betslip/modules/summary/selectors.ts","packages/sports/common/betslip/modules/tracking/models.ts","packages/sports/common/betslip/modules/quick-bet/quick-bet.selectors.ts","packages/sports/web/app/src/common/hide-header.service.ts","node_modules/resize-observer-polyfill/dist/ResizeObserver.es.js","packages/sports/libs/common/core/utils/dom/src/lib/resize-observer.service.ts","packages/sports/web/app/src/grid/grid-breakpoint.service.ts","packages/sports/libs/common/core/utils/hooks/src/lib/hooks-wireup.ts","packages/sports/libs/common/core/utils/hooks/src/lib/on-route-resolve.ts","packages/sports/web/app/src/widget/core/widgets-to-widget-types.util.ts","packages/sports/web/app/src/widget/core/widget-right-column.service.ts","packages/sports/web/app/src/widget/core/modular-tracking.service.ts","packages/sports/web/app/src/widget/common/widget-context-refresh-processor.ts","packages/sports/web/app/src/widget/core/widget-refresh.service.ts","packages/sports/web/app/src/widget/core/widget-skeleton-renderer.service.ts","packages/sports/web/app/src/widget/core/widget-slot.component.ts","packages/sports/web/app/src/widget/core/widget-tracking.service.ts","packages/sports/web/app/src/widget/core/widget-layout-hook.ts","packages/sports/web/app/src/widget/core/widget-layout.component.html","packages/sports/web/app/src/widget/core/widget-layout.component.ts","packages/sports/libs/common/core/utils/element-provider/src/lib/element-provider.ts","packages/sports/libs/modal/feature/src/dialog/modal-dialog-container.directive.ts","packages/sports/web/app/src/widget/core/widget-column.component.html","packages/sports/web/app/src/widget/core/widget-column.component.ts","packages/sports/web/app/src/widget/core/widget-core.module.ts","packages/sports/web/app/src/betslip/betslip-digital-component-loader.service.ts","packages/sports/libs/betting-offer/feature/offer-service/src/betting-offer.service.ts","packages/sports/web/app/src/betbuilder/services/betbuilder-mapper.service.ts","packages/sports/web/app/src/event-details-common/event-details.service.ts","packages/sports/web/app/src/betslip-base/models/pick-models.ts","packages/design-system/ui/rx-host-listener/src/rx-host-listener.ts","node_modules/@angular/forms/fesm2022/forms.mjs","packages/design-system/ui/radio-button/src/radio-button.component.ts","packages/design-system/ui/radio-button/src/index.ts","packages/vanilla/lib/shared/browser/src/trust-as-html.pipe.ts","packages/vanilla/lib/shared/browser/src/format.pipe.ts","packages/vanilla/lib/shared/browser/src/html-attrs.directive.ts","packages/vanilla/lib/shared/browser/src/trust-as-resource-url.pipe.ts","packages/vanilla/lib/shared/browser/src/iframe.component.ts","packages/vanilla/lib/shared/image/image.html","packages/vanilla/lib/shared/image/image.component.ts","packages/vanilla/lib/shared/account-menu/account-menu-onboarding.service.ts","packages/vanilla/lib/shared/account-menu/account-menu.client-config.ts","packages/vanilla/lib/shared/account-menu/account-menu-data.service.ts","packages/vanilla/lib/shared/account-menu/account-menu.models.ts","packages/vanilla/lib/shared/account-menu/account-menu-tasks.service.ts","packages/vanilla/lib/shared/account-menu/account-menu-router.ts","packages/vanilla/lib/shared/account-menu/account-menu.html","packages/vanilla/lib/shared/account-menu/account-menu.component.ts","packages/vanilla/lib/shared/overlay-factory/src/overlay-factory.models.ts","packages/vanilla/lib/shared/overlay-factory/src/overlay.factory.ts","packages/vanilla/lib/features/header-bar/src/header-bar.client-config.ts","packages/vanilla/lib/features/header-bar/src/header-bar.service.ts","packages/vanilla/lib/features/header-bar/src/header-bar-bootstrap.service.ts","packages/vanilla/lib/features/header-bar/src/header-bar.feature.ts","packages/vanilla/lib/shared/confirm-popup/src/confirm-popup.client-config.ts","packages/vanilla/lib/shared/confirm-popup/src/confirm-popup.html","packages/vanilla/lib/shared/confirm-popup/src/confirm-popup.component.ts","packages/vanilla/lib/shared/confirm-popup/src/confirm-popup.service.ts","packages/vanilla/lib/features/header-bar/src/header-bar.models.ts","packages/vanilla/lib/features/header-bar/src/header-bar.html","packages/vanilla/lib/features/header-bar/src/header-bar.component.ts","packages/vanilla/lib/features/header-bar/src/lh-header-bar.component.ts","packages/vanilla/lib/features/header-bar/src/header-bar.component.html","packages/vanilla/lib/features/language-switcher/src/language-switcher.client-config.ts","packages/vanilla/lib/features/language-switcher/src/language-item.html","packages/vanilla/lib/features/language-switcher/src/language-item.component.ts","packages/vanilla/lib/features/language-switcher/src/language-switcher-tracking.service.ts","packages/vanilla/lib/features/flags/src/flags.service.ts","packages/vanilla/lib/features/language-switcher/src/default-language-switcher-urls-provider.ts","packages/vanilla/lib/features/language-switcher/src/language-switcher-urls-provider.ts","packages/vanilla/lib/features/language-switcher/src/language-switcher.service.ts","packages/vanilla/lib/features/language-switcher/src/language-switcher.tokens.ts","packages/vanilla/lib/features/language-switcher/src/language-switcher-menu.html","packages/vanilla/lib/features/language-switcher/src/language-switcher-menu.component.ts","packages/vanilla/lib/features/language-switcher/src/language-switcher-overlay.service.ts","packages/vanilla/lib/features/language-switcher/src/language-switcher-bootstrap.service.ts","packages/vanilla/lib/features/language-switcher/src/language-switcher.feature.ts","packages/vanilla/lib/features/language-switcher/src/language-switcher-radio-menu.html","packages/vanilla/lib/features/language-switcher/src/language-switcher-radio-menu.component.ts","packages/vanilla/lib/features/language-switcher/src/seo-language-links.html","packages/vanilla/lib/features/language-switcher/src/seo-language-links.component.ts","packages/vanilla/lib/features/language-switcher/src/responsive-language-switcher.html","packages/vanilla/lib/features/language-switcher/src/responsive-language-switcher.component.ts","packages/sports/web/app/src/world-cup-hub/world-cup-hub.service.ts","packages/sports/web/app/src/calendar/calendar-cache.service.ts","packages/sports/web/app/src/event-list-shared/sport/sport-sub-navigation.service.ts","packages/sports/web/app/src/layout/slot-layout.service.ts","packages/sports/web/app/src/event-subscription/player-stats-subscription-service.ts","packages/sports/web/app/src/sub-navigation/sub-navigation-resolve.service.ts","node_modules/fast-json-patch/lib/helpers.js","node_modules/fast-json-patch/node_modules/fast-deep-equal/index.js","node_modules/fast-json-patch/lib/core.js","node_modules/fast-json-patch/lib/duplex.js","packages/sports/web/app/src/event-subscription/scoreboard-subscription.service.ts","packages/sports/libs/loading-indicator-feature/src/custom-loading-indicator-handler.ts","packages/sports/libs/loading-indicator-feature/src/custom-loading-indicator-switch.service.ts","packages/sports/libs/loading-indicator-feature/src/custom-loading-indicator.service.ts","packages/sports/libs/loading-indicator-feature/src/loading-indicator.html","packages/sports/libs/loading-indicator-feature/src/loading-indicator.component.ts","packages/sports/libs/loading-indicator-feature/src/loading-indicator.module.ts","node_modules/@babel/runtime/helpers/esm/typeof.js","node_modules/fflate/esm/browser.js","node_modules/jspdf/dist/jspdf.es.min.js","packages/sports/web/app/src/print/print-error-dialog.service.ts","packages/sports/web/app/src/print/print-loading-indicator.component.ts","packages/sports/web/app/src/print/print-loading-indicator.html","packages/sports/web/app/src/print/print-loading-indicator-dialog.service.ts","packages/sports/web/app/src/print/print.models.ts","packages/sports/web/app/src/print/print.service.ts"],"sourcesContent":["import { ImageProfile } from '@cds/betting-offer';\nimport { EventParticipantImage } from '@frontend/sports/betting-offer/feature/model';\nimport { Omit } from '@frontend/sports/common/core/utils/extended-types';\n\nexport enum FavouriteType {\n League = 'L',\n Participant = 'P',\n ParticipantV2 = 'P2',\n Fixture = 'F',\n FixtureV2 = 'F2',\n}\n\nexport interface FavouritesDto {\n sport: FavouritesSport;\n name: string;\n lang: string;\n type: FavouriteType;\n itemId?: string; // itemId might not be there if we're not adding favourites by id\n region?: FavouritesRegion;\n imageProfile?: ImageProfile;\n order?: number;\n upcomingOrder?: number;\n participantImage?: EventParticipantImage;\n}\n\nexport interface FavouritesSport {\n id: number;\n name: string;\n}\n\nexport interface FavouritesRegion {\n id: number;\n name: string;\n}\n\nexport interface FavouritesViewModel extends Omit {\n key: string;\n id: string;\n itemId: string;\n count: number;\n}\n\nexport class FavouritesResponse {\n favourites: FavouritesViewModel[];\n isDefault: boolean;\n}\n\nexport interface FavouritesError {\n message: string;\n severity: string;\n}\n\nexport interface FavouritesSettings {\n isResponsive: boolean;\n source: string;\n hasRemoveBtn: boolean;\n showRight: boolean;\n showRemove?: boolean;\n swipeCountRemove: boolean;\n}\n","import { FavouriteType, FavouritesViewModel } from './favourites.model';\n\nexport function isFixtureFavourite(favourite: FavouritesViewModel): boolean {\n return favourite.type === FavouriteType.Fixture || favourite.type === FavouriteType.FixtureV2;\n}\n","import { Injectable } from '@angular/core';\n\nimport { OfferSource } from '@cds';\nimport { FixtureType } from '@cds/betting-offer';\nimport { TagCount, TagType } from '@cds/betting-offer/tags';\nimport { BatchRequest, FixtureCountsRequest, FixtureState } from '@cds/query-objects';\nimport { BettingOfferApi } from '@frontend/sports/betting-offer/feature/offer-service';\nimport { toString } from 'lodash-es';\nimport { Observable, of } from 'rxjs';\nimport { map } from 'rxjs/operators';\n\nimport { FavouriteType, FavouritesViewModel } from './favourites.model';\nimport { isFixtureFavourite } from './helpers';\n\n@Injectable({ providedIn: 'root' })\nexport class FavouritesFixturesService {\n constructor(private bettingOffer: BettingOfferApi) {}\n\n getCounts(favourites: FavouritesViewModel[]): Observable<{ [key: string]: TagCount[] }> {\n if (!favourites.length) {\n return of({});\n }\n\n const participantFavourite = (fav: FavouritesViewModel): boolean =>\n fav.type === FavouriteType.Participant || fav.type === FavouriteType.ParticipantV2;\n\n const request: BatchRequest[] = favourites.map((favourite) => {\n const mapped: BatchRequest = {\n batchId: favourite.id,\n request: {\n fixtureTypes: FixtureType.Standard,\n offerSource: this.getOfferSource(favourite),\n state: FixtureState.Latest,\n tagTypes: toString([TagType.Sport, TagType.Region, TagType.Tournament, TagType.Competition]),\n sportIds: toString(favourite.sport.id),\n extendedTags: '',\n fixtureParticipantIds: participantFavourite(favourite) ? toString(favourite.itemId) : '',\n competitionIds: favourite.type === FavouriteType.League ? toString(favourite.itemId) : '',\n fixtureIds: isFixtureFavourite(favourite) ? favourite.itemId : '',\n },\n };\n\n return mapped;\n });\n\n return this.bettingOffer.getBatchCounts(request).pipe(map((result) => result || {}));\n }\n\n private getOfferSource(favourite: FavouritesViewModel): OfferSource | undefined {\n if (favourite.type === FavouriteType.League) {\n return undefined;\n }\n\n if (favourite.type === FavouriteType.FixtureV2 || favourite.type === FavouriteType.ParticipantV2) {\n return OfferSource.V2;\n }\n\n return OfferSource.V1;\n }\n}\n","/* eslint-disable no-param-reassign */\nimport { Injectable } from '@angular/core';\n\nimport { PrettyUrlsConfig } from '@frontend/sports/common/client-config-data-access';\nimport { SegmentNormalizer } from '@frontend/sports/navigation/core/feature/url-builder';\nimport { produce } from 'immer';\nimport { clone, trim } from 'lodash-es';\n\nimport { FavouriteType, FavouritesDto, FavouritesViewModel } from './favourites.model';\n\n@Injectable({ providedIn: 'root' })\nexport class FavouritesModelFactory {\n private normalizer: SegmentNormalizer;\n\n constructor(urlConfig: PrettyUrlsConfig) {\n this.normalizer = new SegmentNormalizer(urlConfig);\n }\n\n toModel(dto: FavouritesDto | FavouritesViewModel): Readonly {\n return produce(dto as FavouritesViewModel, (draft) => {\n draft.id = this.getToken(draft.type, draft.name || '', draft.sport.id, draft.itemId || 0);\n draft.key = this.getKey(draft);\n draft.count = 0;\n draft.region = clone(dto.region);\n draft.sport.name = dto.sport.name;\n draft.imageProfile = dto.imageProfile;\n draft.participantImage = dto.participantImage;\n });\n }\n\n toDto(val: FavouritesViewModel): Readonly {\n return val as FavouritesDto;\n }\n\n private getKey(value: FavouritesDto | FavouritesViewModel): string {\n return trim([value.type, 0, value.sport.id, value.itemId, value.name].join(','), ',');\n }\n\n private getToken(type: FavouriteType, name: string, sport: number, item: number | string): string {\n return this.normalizer.normalize(`${type} ${name} ${sport || 0} ${item || ''}`);\n }\n}\n","/* eslint-disable no-param-reassign */\nimport { Injectable } from '@angular/core';\n\nimport { Competition as CompetitionTag, TagCount, TagType } from '@cds/betting-offer/tags';\nimport { EventParticipantImage } from '@frontend/sports/betting-offer/feature/model';\nimport { ApiService } from '@frontend/sports/common/api-utils';\nimport { FavouritesConfig, Sitecore } from '@frontend/sports/common/client-config-data-access';\nimport { filterSportsEmitLast } from '@frontend/sports/host-app/sports-product/feature/utils';\nimport { ToasterService } from '@frontend/sports/toaster/feature';\nimport { UserService } from '@frontend/sports/user/feature';\nimport { Page } from '@frontend/vanilla/core';\nimport produce from 'immer';\nimport { differenceBy, maxBy } from 'lodash-es';\nimport { BehaviorSubject, Observable, of, throwError } from 'rxjs';\nimport { catchError, finalize, map, shareReplay, switchMap } from 'rxjs/operators';\n\nimport { FavouritesFixturesService } from './favourites-fixtures.service';\nimport { FavouritesModelFactory } from './favourites-model.factory';\nimport { FavouriteType, FavouritesDto, FavouritesResponse, FavouritesViewModel } from './favourites.model';\n\nexport enum FavouriteChangeType {\n Add = 'Add',\n Remove = 'Remove',\n Get = 'Get',\n Save = 'Save',\n}\n\nexport interface FavouriteChange {\n favourites: FavouritesViewModel[];\n changeType: FavouriteChangeType;\n}\n\n@Injectable({ providedIn: 'root' })\nexport class FavouritesService {\n private favourites: FavouritesViewModel[] = [];\n private cached: Observable;\n\n private updating = false;\n private default: boolean;\n\n private eventSubject = new BehaviorSubject({ favourites: [], changeType: FavouriteChangeType.Get });\n\n constructor(\n private apiService: ApiService,\n private userService: UserService,\n private page: Page,\n private favouritesConfig: FavouritesConfig,\n private sitecore: Sitecore,\n private favouritesData: FavouritesFixturesService,\n private favouritesFactory: FavouritesModelFactory,\n private toaster: ToasterService,\n ) {\n const get = () => this.getList(true).subscribe();\n this.userService.onUserLogin$.pipe(filterSportsEmitLast()).subscribe(get);\n\n get();\n }\n\n get change(): Observable {\n return this.eventSubject;\n }\n\n get favouritesList(): FavouritesViewModel[] {\n return this.favourites;\n }\n\n get isDefault(): boolean {\n return this.default;\n }\n\n get isFull(): boolean {\n return this.favourites.length >= this.favouritesConfig.maxFavourites;\n }\n\n notify(message: string, iconClass?: string): void {\n this.toaster.open(message, { duration: this.favouritesConfig.toastMessageTimeOut, icon: iconClass });\n }\n\n add(\n itemId: string,\n sportId: number,\n type: FavouriteType,\n name: string,\n participantImage?: EventParticipantImage,\n ): Observable {\n if (this.updating) {\n return throwError(() => new Error('UPDATING'));\n }\n\n if (!this.userService.isAuthenticated) {\n return throwError(() => new Error(this.sitecore.favouritesMessages.NotAuthenticated));\n }\n\n if (this.isFull) {\n return throwError(\n () =>\n new Error(this.sitecore.favouritesMessages.TooManyFavourites.replace('{{maxCount}}', `(${this.favouritesConfig.maxFavourites})`)),\n );\n }\n\n const favourite = this.get(itemId, sportId, type);\n\n if (favourite) {\n return of(favourite);\n }\n\n const favForSport = this.favourites.find((f) => f.sport.id === sportId && f.upcomingOrder !== 0);\n const maxUpcomingOrder = maxBy(this.favourites.map((f) => f.upcomingOrder || 0));\n const maxOrder = maxBy(this.favourites.map((f) => f.order || 0));\n const newFavourite = this.favouritesFactory.toModel({\n sport: { id: sportId, name: '' },\n itemId,\n name,\n lang: this.page.culture,\n type,\n order: maxOrder ? maxOrder + 1 : 0,\n upcomingOrder: favForSport?.upcomingOrder || (maxUpcomingOrder ? maxUpcomingOrder + 1 : 0),\n participantImage,\n });\n\n const newFavourites = produce(this.favourites, (favs) => {\n favs.push(newFavourite);\n });\n\n const request = newFavourites.map((current) => this.favouritesFactory.toDto(current));\n\n return this.update(request).pipe(\n switchMap(() => this.loadCounts(newFavourites, true)),\n map((favourites) => {\n this.set(favourites);\n this.eventSubject.next({ favourites: this.favourites, changeType: FavouriteChangeType.Add });\n\n return this.get(itemId, sportId, type);\n }),\n catchError(() => throwError(() => new Error(this.sitecore.favouritesMessages.AddError))),\n );\n }\n\n save(newFavourites: FavouritesViewModel[]): Observable {\n if (this.updating) {\n return throwError(() => new Error('UPDATING'));\n }\n\n if (!this.userService.isAuthenticated) {\n return throwError(() => new Error(this.sitecore.favouritesMessages.NotAuthenticated));\n }\n\n const request = newFavourites.map((current) => this.favouritesFactory.toDto(current));\n\n return this.update(request).pipe(\n switchMap(() => this.loadCounts(newFavourites, true)),\n map((favourites) => {\n this.set(favourites);\n this.eventSubject.next({ favourites: this.favourites, changeType: FavouriteChangeType.Save });\n\n return favourites;\n }),\n catchError(() => throwError(() => new Error(this.sitecore.favouritesDashboard.Save))),\n );\n }\n\n remove(item: FavouritesViewModel | FavouritesViewModel[]): Observable {\n const itemArray = Array.isArray(item) ? item : [item];\n if (this.updating) {\n return throwError(() => new Error('UPDATING'));\n }\n\n if (!this.userService.isAuthenticated) {\n return throwError(() => new Error(this.sitecore.favouritesMessages.NotAuthenticated));\n }\n\n if (itemArray.length === 0) {\n return of();\n }\n\n const diff = differenceBy(this.favourites, itemArray, (current) => `${current.itemId}-${current.type}`);\n const request = diff.map((current) => this.favouritesFactory.toDto(current));\n\n return this.update(request).pipe(\n map(() => {\n this.set(diff);\n this.eventSubject.next({ favourites: this.favourites, changeType: FavouriteChangeType.Remove });\n }),\n );\n }\n\n get(itemId: string, sportId: number, type: FavouriteType): FavouritesViewModel | undefined {\n return this.favourites.find((favourite) => favourite.sport.id === sportId && favourite.itemId === itemId && favourite.type === type);\n }\n\n getList(refresh?: boolean): Observable {\n if (!this.cached || refresh) {\n this.default = !this.userService.isAuthenticated;\n\n this.cached = this.apiService.get('favourites').pipe(\n switchMap((result) => {\n this.default = result.isDefault;\n this.favourites = result.favourites.map((current) => this.favouritesFactory.toModel(current));\n\n return this.loadCounts(this.favourites);\n }),\n map((favourites) => {\n this.favourites = favourites;\n this.eventSubject.next({ favourites: this.favourites, changeType: FavouriteChangeType.Get });\n\n return this.favourites;\n }),\n shareReplay({\n bufferSize: 1,\n refCount: true,\n }),\n );\n }\n\n return this.cached;\n }\n\n private loadCounts(favourites: FavouritesViewModel[], loadAdditionalData = false): Observable {\n return this.favouritesData.getCounts(favourites).pipe(\n map((result) => {\n if (!result) {\n return favourites;\n }\n\n return produce(favourites, (favDraft) => {\n // eslint-disable-next-line sonarjs/cognitive-complexity\n favDraft.forEach((f) => {\n const tags = result[f.id];\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n if (!tags?.length) {\n return;\n }\n\n const tag = this.findTag(tags, TagType.Tournament) || this.findTag(tags, TagType.Region) || tags[0];\n const sportTag = this.findTag(tags, TagType.Sport);\n const competitionTag = this.findTag(tags, TagType.Competition);\n // if it's a participant, the correct count is on the Sport tag otherwise any tag should have the correct count\n const countTag =\n f.type === FavouriteType.Participant || f.type === FavouriteType.ParticipantV2 ? sportTag || tags[0] : tags[0];\n\n if (loadAdditionalData) {\n if (f.type === FavouriteType.League && !f.region) {\n f.region = {\n id: tag.tag.id,\n name: tag.tag.name.value,\n };\n if (competitionTag) {\n f.imageProfile = (competitionTag.tag as CompetitionTag).imageProfile;\n }\n }\n if (sportTag && !f.sport.name) {\n f.sport.name = sportTag.tag.name.value;\n }\n }\n f.count = countTag.live + countTag.preMatch;\n });\n });\n }),\n );\n }\n\n private update(favourites: FavouritesDto[]): Observable {\n this.updating = true;\n\n // below seems to be related to finalize type\n // eslint-disable-next-line @typescript-eslint/no-invalid-void-type\n return this.apiService.post('favourites', { Favourites: favourites }).pipe(finalize(() => (this.updating = false)));\n }\n\n private set(favourites: FavouritesViewModel[]): void {\n this.favourites = favourites;\n this.cached = of(this.favourites).pipe(\n shareReplay({\n bufferSize: 1,\n refCount: true,\n }),\n );\n }\n\n private findTag(tags: TagCount[], type: TagType): TagCount | undefined {\n return tags.find((t) => t.tag.type === type);\n }\n}\n","import { Injectable } from '@angular/core';\n\nimport { SitecoreEventDetailsConfig } from '@frontend/sports/common/client-config-data-access';\nimport {\n CountItem,\n ItemType,\n LeagueItem,\n VirtualCompetitionDisplayMode,\n VirtualCompetitionGroupItem,\n VirtualCompetitionImageType,\n children,\n} from '@frontend/sports/common/core/data-access/sport-model';\nimport { isDefined } from '@frontend/sports/common/core/utils/extended-types';\nimport { FavouriteChangeType, FavouriteType, FavouritesService } from '@frontend/sports/favourites/core/feature';\nimport { Observable, merge, of } from 'rxjs';\nimport { filter, map, switchMap } from 'rxjs/operators';\n\nimport { getItemTypeForSport } from '../../competition-list/competition-list.models';\nimport { CompetitionRouteService } from '../../competition-list/competition-route.service';\nimport { SportUrlParam, UrlHelperService } from '../../navigation-core/url-helper.service';\nimport { CompetitionRoute } from '../../navigation/navigation.models';\nimport { TournamentGroup } from './tournament-groups.component';\n\nexport interface VirtualCompetitionModel {\n groups: TournamentGroup[];\n sportId: number;\n displayMode: VirtualCompetitionDisplayMode;\n imageType: VirtualCompetitionImageType;\n id: number;\n realCompetitionId?: number;\n}\n\n@Injectable({ providedIn: 'root' })\nexport class VirtualCompetitionService {\n constructor(\n private urlHelperService: UrlHelperService,\n private favouritesService: FavouritesService,\n private sitecoreEventDetails: SitecoreEventDetailsConfig,\n private routeService: CompetitionRouteService,\n ) {}\n\n subscribe$(\n sport: CountItem,\n virtualCompetition: LeagueItem,\n urlFactory?: (group?: VirtualCompetitionGroupItem) => string,\n ): Observable {\n return this.sitecoreEventDetails.whenReady.pipe(\n switchMap(() =>\n merge(\n of(undefined),\n this.favouritesService.change.pipe(\n filter((change) => change.changeType === FavouriteChangeType.Add || change.changeType === FavouriteChangeType.Remove),\n ),\n ).pipe(map(() => this.buildVirtualCompetition(sport, virtualCompetition, urlFactory))),\n ),\n );\n }\n\n getCompetition(sport: CountItem | undefined, route: CompetitionRoute, findVirtual = true): LeagueItem | undefined {\n if (!sport) {\n return;\n }\n\n const region = sport.children.find(\n (item) => (item.type === ItemType.Region || item.type === ItemType.Tournament) && item.id === route.region,\n );\n if (region) {\n const leagueList = region.children as LeagueItem[];\n\n return leagueList.find(\n (item: LeagueItem) => item.type === ItemType.Competition && item.id === route.league && item.isVirtual === findVirtual,\n );\n }\n\n return;\n }\n\n private buildVirtualCompetition(\n sport: CountItem,\n virtualCompetition: LeagueItem,\n urlFactory?: (group?: VirtualCompetitionGroupItem) => string,\n ): VirtualCompetitionModel | null {\n const virtualCompetitionGroups = this.buildTournamentGroups(sport, virtualCompetition, urlFactory);\n if (virtualCompetitionGroups.length === 0) {\n return null;\n }\n\n return {\n groups: virtualCompetitionGroups,\n sportId: sport.id,\n displayMode: virtualCompetition.displayMode!,\n imageType: virtualCompetition.imageType!,\n id: virtualCompetition.id,\n realCompetitionId: virtualCompetition.realCompetitionId,\n };\n }\n\n private buildTournamentGroups(\n sport: CountItem,\n virtualCompetition: LeagueItem,\n urlFactory?: (group?: VirtualCompetitionGroupItem) => string,\n ): TournamentGroup[] {\n const route = this.routeService.params();\n if (!(route.isVirtual && virtualCompetition.children.length > 0)) {\n return [];\n }\n\n const regionType = getItemTypeForSport(sport.id);\n const region = children(sport, regionType).find((item) => item.id === route.region);\n\n if (!region) {\n return [];\n }\n\n const league = children(sport, ItemType.Competition).pop() as LeagueItem;\n\n const allGroups = [\n {\n id: 0,\n title: this.sitecoreEventDetails.eventDetails.AllMarketsTemplate,\n active: route.virtualCompetitionGroup === undefined,\n url: urlFactory?.() ?? this.urlHelperService.getSportUrl(sport, SportUrlParam.Betting, region, league, undefined, route.marketOffer),\n canBeFavourited: false,\n },\n ];\n const virtualGroups = virtualCompetition.children as VirtualCompetitionGroupItem[];\n let tournamentGroups = virtualGroups.map((group) => {\n const isFavourite = group.siblings.length\n ? isDefined(this.favouritesService.get(group.siblings[0].toString(), sport.id, FavouriteType.League))\n : false;\n\n return {\n id: group.id,\n favouriteId: group.siblings[0],\n title: group.name,\n active: group.id === route.virtualCompetitionGroup,\n isFavourite,\n favouriteName: `${league.name} - ${group.name}`,\n url: urlFactory?.(group) ?? this.urlHelperService.getSportUrl(sport, SportUrlParam.Betting, region, league, group, route.marketOffer),\n participants: group.participants,\n canBeFavourited: !(group.stageIds.length || group.groupIds.length),\n };\n });\n\n const tournamentGroupsFavourite = this.favouritesService.favouritesList\n .filter((f) => f.type === FavouriteType.League)\n .map((f) => tournamentGroups.find((g) => g.canBeFavourited && g.favouriteId?.toString() === f.itemId))\n .filter((f) => isDefined(f));\n tournamentGroups = tournamentGroupsFavourite.concat(tournamentGroups.filter((f) => !f.isFavourite));\n\n return allGroups.concat(tournamentGroups);\n }\n}\n","import { Injectable } from '@angular/core';\n\nimport { StatisticsConfig } from '@frontend/sports/common/client-config-data-access';\nimport { SportConstant } from '@frontend/sports/common/core/data-access/constants';\nimport { CountItem, LeagueItem, RegionItem, isVirtualCompetitionGroupItem } from '@frontend/sports/common/core/data-access/sport-model';\nimport { Omit, isDefined } from '@frontend/sports/common/core/utils/extended-types';\nimport { LeagueGroup } from '@frontend/sports/grid/core/feature/model';\nimport { flatten } from 'lodash-es';\n\nexport interface GetStandingsParams {\n sport: CountItem;\n selectedVirtualGroupId?: number;\n expandedGroups?: number;\n}\n\n@Injectable({ providedIn: 'root' })\nexport class StandingsService {\n constructor(private statisticsConfig: StatisticsConfig) {}\n\n getStandingsGroups(params: GetStandingsParams): LeagueGroup[] {\n const { sport, selectedVirtualGroupId, expandedGroups = this.statisticsConfig.expandedLeagueTablesCount } = params;\n\n if (sport.id !== SportConstant.Soccer) {\n return [];\n }\n\n const leagueGroups = !selectedVirtualGroupId\n ? this.getStandingsForCompetitions(sport)\n : this.getStandingsForVirtualGroups(sport, selectedVirtualGroupId);\n\n leagueGroups.forEach((leagueGroup, index) => (leagueGroup.collapsed = index >= expandedGroups));\n\n return leagueGroups;\n }\n\n private getStandingsForCompetitions(sport: CountItem): LeagueGroup[] {\n const leagueGroups = flatten(\n (sport.children).map((region) =>\n flatten(\n (region.children).map((leagueItem) =>\n leagueItem.isVirtual ? this.mapVirtualGroups(leagueItem, region) : this.mapCompetition(leagueItem, region),\n ),\n ),\n ),\n );\n\n return leagueGroups;\n }\n\n private getStandingsForVirtualGroups(sport: CountItem, selectedVirtualGroupId: number): LeagueGroup[] {\n const leagueGroups = flatten(\n (sport.children).map((region) =>\n flatten((region.children).map((leagueItem) => this.mapVirtualGroups(leagueItem, region, selectedVirtualGroupId))),\n ),\n );\n\n return leagueGroups;\n }\n\n private mapCompetition(competition: LeagueItem, region: RegionItem): LeagueGroup[] {\n if (!competition.standings) {\n return [];\n }\n\n return [{ ...this.toLeagueGroup(competition, region), id: competition.id, name: competition.name, canBeFavourited: true }];\n }\n\n private mapVirtualGroups(competition: LeagueItem, region: RegionItem, selectedVirtualGroup?: number): LeagueGroup[] {\n if (!competition.isVirtual || !competition.children) {\n return [];\n }\n\n const leagueGroups = competition.children\n .filter(isVirtualCompetitionGroupItem)\n .filter((virtualGroup) => virtualGroup.standings && (!isDefined(selectedVirtualGroup) || virtualGroup.id === selectedVirtualGroup))\n .map((virtualGroup) => ({\n ...this.toLeagueGroup(virtualGroup, region),\n id: virtualGroup.siblings[0] || virtualGroup.groupIds[0] || virtualGroup.stageIds[0],\n name: `${competition.name} - ${virtualGroup.name}`,\n virtualCompetitionId: virtualGroup.parentId,\n virtualCompetitionGroupId: virtualGroup.id,\n canBeFavourited: !(virtualGroup.stageIds.length || virtualGroup.groupIds.length),\n }));\n\n return leagueGroups;\n }\n\n private toLeagueGroup(\n leagueItem: LeagueItem,\n region: RegionItem,\n ): Omit {\n return {\n count: leagueItem.counts.preMatch + leagueItem.counts.live,\n events: [],\n collapsed: false,\n collapsible: true,\n deferred: false,\n siblings: [leagueItem.id],\n region,\n };\n }\n}\n","import { Injectable } from '@angular/core';\n\nimport { CountItem, ItemCount, ItemFilter, ItemMeta, ItemType } from '@frontend/sports/common/core/data-access/sport-model';\nimport { cloneDeep, isArray, keys, some, sumBy } from 'lodash-es';\n\nimport { getItemTypeForSport } from '../../../competition-list/competition-list.models';\nimport { CompetitionRoute } from '../../../navigation/navigation.models';\n\n@Injectable({ providedIn: 'root' })\nexport class PruneHelperService {\n /**\n * Returns a tree of items traversed recursively and applied filters on each level. All items\n * and its children with 0 counts are removed from the tree\n *\n * @param items list of source items\n * @param filters list of filters (item type and item id) to be applied on the collection\n */\n prune(sport: CountItem | undefined, route: CompetitionRoute): CountItem | undefined {\n if (!sport) {\n return;\n }\n\n const filters = [{ type: ItemType.Sport, id: sport.id }];\n\n if (route.league) {\n const leagues = isArray(route.league) ? route.league : [route.league];\n leagues.forEach((league) => filters.push({ type: ItemType.Competition, id: league }));\n }\n\n if (route.virtualCompetitionGroup) {\n filters.push({\n type: ItemType.VirtualCompetitionGroup,\n id: route.virtualCompetitionGroup,\n });\n }\n\n if (route.conference) {\n filters.push({\n type: ItemType.Conference,\n id: route.conference,\n });\n }\n\n if (route.region) {\n filters.push({\n type: getItemTypeForSport(sport.id),\n id: route.region,\n });\n }\n\n const clonedSport = [cloneDeep(sport)];\n\n return this.pruneRecursively(clonedSport, filters).pop();\n }\n\n private pruneRecursively(items: CountItem[], filters: ItemFilter[]): CountItem[] {\n if (!items.length) {\n return items;\n }\n\n const typeFilters = filters.filter((current) => current.type === items[0].type);\n\n if (typeFilters.length) {\n items = items.filter((item) => typeFilters.some((filter) => item.id === filter.id));\n }\n\n return items\n .map((currentItem) => {\n if (!currentItem.children.length) {\n return currentItem;\n }\n\n currentItem.children = this.pruneRecursively(currentItem.children, filters);\n // if there are no VirtualCompetitionGroup filters, then for VirtualCompetition items there is no need\n // to sum up the count of VirtualCompetitionGroups because we get the count already calculated from backend\n if (filters.some((filter) => filter.type === ItemType.VirtualCompetitionGroup) || !this.isVirtualCompetition(currentItem)) {\n keys(currentItem.counts)\n .map((key) => key as keyof ItemCount)\n .forEach((key: keyof ItemCount) => {\n (currentItem.counts[key] as number) = sumBy(currentItem.children, (child) => (child.counts[key] as number) || 0);\n });\n keys(currentItem.meta)\n .map((key) => key as keyof ItemMeta)\n .forEach((key) => {\n (currentItem.meta[key] as boolean) = !!currentItem.meta[key] || some(currentItem.children, (child) => child.meta[key]);\n });\n }\n\n return currentItem;\n })\n .filter((item) => item.counts.preMatch + item.counts.live);\n }\n\n private isVirtualCompetition(countItem: CountItem): boolean {\n return countItem.children.length > 0 && countItem.children.every((child) => child.type === ItemType.VirtualCompetitionGroup);\n }\n}\n","import { Injectable } from '@angular/core';\n\nimport { Widget, WidgetLayoutTemplate, WidgetPage } from '@frontend/sports/types/components/widget';\n\n@Injectable()\nexport class ModularConfigAccessorService {\n private widget?: Widget;\n private parent?: Widget;\n private page?: WidgetPage;\n private layoutTemplate?: WidgetLayoutTemplate;\n\n setPage(page: WidgetPage | undefined): void {\n this.page = page;\n }\n\n getPage(): WidgetPage | undefined {\n return this.page;\n }\n\n setWidget(widget: Widget): void {\n this.widget = widget;\n }\n\n getWidget(): Readonly> | undefined {\n return this.widget;\n }\n\n setParentWidget(widget: Widget | undefined): void {\n this.parent = widget;\n }\n\n getParentWidget(): Readonly> | undefined {\n return this.parent;\n }\n\n setLayoutTemplate(layoutTemplate: WidgetLayoutTemplate | undefined): void {\n this.layoutTemplate = layoutTemplate;\n }\n\n getLayoutTemplate(): WidgetLayoutTemplate | undefined {\n return this.layoutTemplate;\n }\n}\n","import { Injectable } from '@angular/core';\n\nimport { ClientConfigProductName, LazyClientConfig, LazyClientConfigBase, LazyClientConfigService } from '@frontend/vanilla/core';\n\nimport { IconsModel } from './icons-model';\n\n@LazyClientConfig({ key: 'vnIconset', product: ClientConfigProductName.SF })\n@Injectable({\n providedIn: 'root',\n deps: [LazyClientConfigService],\n useFactory: iconsFactory,\n})\nexport class IconsetConfig extends LazyClientConfigBase {\n iconItems: IconsModel[];\n}\n\nexport function iconsFactory(service: LazyClientConfigService) {\n return service.get(IconsetConfig);\n}\n","import { Injectable, inject } from '@angular/core';\n\nimport { IconsModel } from './icons-model';\nimport { IconsetConfig } from './icons.client-config';\n\n@Injectable({ providedIn: 'root' })\nexport class IconFastService {\n config = inject(IconsetConfig);\n\n getIconParameter(uniqueIconName: string, parameter: string) {\n if (this.config.iconItems) {\n const matchingIcon: IconsModel = this.config.iconItems.find(\n (data: IconsModel) => data.name === uniqueIconName || data.iconName === uniqueIconName,\n ) || {\n name: 'notfound',\n iconName: '',\n parameters: { urlId: '' },\n image: { src: '' },\n };\n\n if (matchingIcon.name != 'notfound') {\n return this.getAvailableValues(parameter, matchingIcon);\n }\n }\n return '';\n }\n\n getAvailableValues(parameter: string, icon: IconsModel) {\n switch (parameter) {\n case 'urlId':\n return icon.parameters?.urlId ?? icon.image?.src ?? icon.imageUrl;\n case 'size':\n return icon.parameters?.size ?? icon.size;\n case 'extraClass':\n return icon.parameters?.extraClass ?? icon.extraClass;\n case 'fillColor':\n return icon.parameters?.fillColor ?? icon.fillColor;\n case 'title':\n return icon.parameters?.title ?? icon.title;\n default:\n return '';\n }\n }\n\n static isValueHere(obj: any, searchValue: string): boolean {\n for (let key in obj) {\n if (obj[key] === searchValue) {\n return true;\n }\n }\n return false;\n }\n}\n","import { AfterViewInit, Component, ElementRef, Input, OnInit, Renderer2, ViewEncapsulation } from '@angular/core';\n\nimport { DeviceService, IconFastCoreService } from '@frontend/vanilla/core';\nimport { FastSvgComponent } from '@push-based/ngx-fast-svg';\nimport { firstValueFrom } from 'rxjs';\n\nimport { IconFastService } from './icon-fast.service';\nimport { IconsetConfig } from './icons.client-config';\n\n/**\n * \n * @input `name` is required and should be the name of the icon in site core.\n * @input `size` if provided then width and height not required.\n * @input `width` if provided then height is required.\n * @input `height` if provided then width is required.\n * @input `extraClass` if provided as input then it will apply to svg otherwise extraClass witll be taken from sitecore item parameters if available.\n *\n */\n\n@Component({\n standalone: true,\n imports: [FastSvgComponent],\n selector: 'vn-icon',\n template: ` @if (!deviceService.isRobot) {\n @if (width && height) {\n \n } @else {\n \n }\n }`,\n styles: ['vn-icon {display: contents;} .fast-svg {margin: 0!important; height: auto; width: auto;}'],\n encapsulation: ViewEncapsulation.None,\n})\nexport class IconCustomComponent implements OnInit, AfterViewInit {\n @Input({ required: true }) name: string;\n @Input() size: string;\n @Input() width: string;\n @Input() height: string;\n @Input() extraClass: string;\n constructor(\n private el: ElementRef,\n private renderer: Renderer2,\n private iconFastCoreService: IconFastCoreService,\n private iconFastService: IconFastService,\n private iconsConfig: IconsetConfig,\n public deviceService: DeviceService,\n ) {}\n\n async ngOnInit(): Promise {\n await firstValueFrom(this.iconsConfig.whenReady);\n this.iconFastCoreService.set(this.iconFastService);\n\n const hostElement = this.el.nativeElement as HTMLElement;\n if (this.name === undefined) {\n this.name = hostElement.getAttribute('name') || hostElement.getAttribute('text') || 'not defined';\n }\n\n if (this.size === undefined) {\n const parentSize = hostElement.getAttribute('size') || '21';\n this.size = this.iconFastService.getIconParameter(this.name, 'size') || parentSize;\n }\n\n if (!this.extraClass) {\n this.extraClass = this.iconFastService.getIconParameter(this.name, 'extraClass') || '';\n }\n }\n\n ngAfterViewInit(): void {\n const svgElement = this.el.nativeElement.querySelector('svg') as HTMLElement;\n\n if (svgElement) {\n this.renderer.setAttribute(svgElement, 'role', 'img');\n\n this.iconsConfig.whenReady.subscribe(() => {\n const fillColor = this.iconFastService.getIconParameter(this.name, 'fillColor');\n if (fillColor) this.renderer.setAttribute(svgElement, 'fill', fillColor);\n\n const iconTitle = this.iconFastService.getIconParameter(this.name, 'title');\n if (iconTitle) this.renderer.setAttribute(svgElement, 'title', iconTitle);\n });\n }\n }\n}\n","@switch (offerStatus) {\n @case (offersStatusEnum.OptIn) {\n
\n {{ promotion.banner?.callToAction || translations.OptInButton }}\n
\n }\n @case (offersStatusEnum.OptedIn) {\n
\n \n {{ translations.OptedInButton }}\n
\n }\n @case (offersStatusEnum.Expired) {\n
\n \n {{ translations.ExpiredButton }}\n
\n }\n @case (offersStatusEnum.Error) {\n
\n \n {{ translations.ErrorButton }}\n
\n }\n}\n","import { Component, EventEmitter, Input, Output } from '@angular/core';\n\nimport { Sitecore } from '@frontend/sports/common/client-config-data-access';\nimport { CrmPromotionsBanners } from '@frontend/sports/types/components/sitecore';\nimport { DsIconButton } from '@frontend/ui/icon-button';\nimport { IconCustomComponent } from '@frontend/vanilla/features/icons';\n\nimport { OfferStatus, Promotion } from '../crm-offer-data/crm-offer.model';\n\n@Component({\n selector: 'ms-crm-promotion-status-button',\n templateUrl: './promotion-status-button.component.html',\n standalone: true,\n imports: [DsIconButton, IconCustomComponent],\n})\nexport class PromotionStatusButtonComponent {\n translations: CrmPromotionsBanners;\n offersStatusEnum = OfferStatus;\n\n @Input() promotion: Promotion;\n @Output() buttonClick = new EventEmitter();\n\n constructor(sitecore: Sitecore) {\n this.translations = sitecore.crmPromotionsBanners;\n }\n\n get offerStatus(): OfferStatus {\n return this.promotion.status;\n }\n\n onButtonClick(event: Event): void {\n this.buttonClick.emit(event);\n }\n}\n","import { Injectable } from '@angular/core';\n\nimport { ApiService } from '@frontend/sports/common/api-utils';\nimport { LoggerFactory, SportsRemoteLogger } from '@frontend/sports/common/core/feature/logging';\nimport { Observable, of } from 'rxjs';\nimport { catchError, map } from 'rxjs/operators';\n\nimport { OfferStatus, OfferStatusRequest, OfferStatusResponse, OfferStatusType, Promotion } from '../crm-offer-data/crm-offer.model';\n\n@Injectable({ providedIn: 'root' })\nexport class OfferStatusService {\n private optinResponseToStatus = {\n OFFERED: OfferStatus.OptIn,\n NOT_OFFERED: OfferStatus.NoOptIn,\n EXPIRED: OfferStatus.Expired,\n OPTED_IN: OfferStatus.OptedIn,\n };\n\n private readonly logger: SportsRemoteLogger;\n\n constructor(\n private api: ApiService,\n loggerFactory: LoggerFactory,\n ) {\n this.logger = loggerFactory.getLogger('OfferStatusService');\n }\n\n setOfferStatus(promotion: Promotion): Observable {\n const request: OfferStatusRequest = {\n id: promotion.id,\n type: OfferStatusType[promotion.type as keyof typeof OfferStatusType],\n source: 'SPORTS',\n };\n\n return this.api.post('crmPromotions/offerStatus', request).pipe(\n map((response) => this.optinResponseToStatus[response.status as keyof typeof this.optinResponseToStatus] || OfferStatus.Error),\n catchError((error) => {\n this.logger.error(error, 'OfferStatusService');\n\n return of(OfferStatus.Error);\n }),\n );\n }\n}\n","import { Injectable, Optional } from '@angular/core';\n\nimport { NOT_APPLICABLE, TrackData, TrackingService, trackingConstants } from '@frontend/sports/tracking/feature';\nimport { WidgetPage } from '@frontend/sports/types/components/widget';\n\nimport { OfferStatus, Promotion } from '../crm-offer-data/crm-offer.model';\nimport { ModularConfigAccessorService } from '../widget/core/modular-config-accessor.service';\n\nexport enum CTALocation {\n OptIn = 'OptIn',\n MoreDetails = 'MoreDetails',\n Banner = 'Banner',\n}\n\nexport interface CtaTrackingData {\n promotion: Promotion;\n trackingSource?: string;\n ctaLocation: CTALocation;\n position?: number;\n}\n\n@Injectable({ providedIn: 'root' })\nexport class CrmPromotionTracking {\n private ctaTrackingLocations = {\n [CTALocation.OptIn]: 'OptIn',\n [CTALocation.MoreDetails]: 'More Details',\n [CTALocation.Banner]: 'Banner',\n };\n\n private failedOptinText: Record = {\n [OfferStatus.NoOptIn]: ':???:',\n [OfferStatus.Expired]: 'Promotion Expired',\n [OfferStatus.Error]: 'OptIn Call Failed',\n [OfferStatus.OptedIn]: 'Opted In',\n [OfferStatus.OptIn]: 'Unknown Error',\n };\n\n constructor(\n private trackingService: TrackingService,\n @Optional() private configAccessor: ModularConfigAccessorService,\n ) {}\n\n trackViewed(promotion: Promotion, trackingSource?: string): void {\n this.trackingService.track(trackingConstants.EVENT_CONTENT_VIEW, {\n ...this.baseTracking(promotion, trackingSource),\n [trackingConstants.COMPONENT_ACTION_EVENT]: 'Load',\n });\n }\n\n trackCTA(data: CtaTrackingData): void {\n let url = NOT_APPLICABLE;\n\n if (data.ctaLocation === CTALocation.MoreDetails) {\n url = (data.promotion.banner && data.promotion.banner.moreInfo) || NOT_APPLICABLE;\n }\n\n this.trackingService.track(trackingConstants.EVENT_CLICKS, {\n ...this.baseTracking(data.promotion, data.trackingSource),\n [trackingConstants.COMPONENT_ACTION_EVENT]: 'Click',\n [trackingConstants.COMPONENT_EVENT_DETAILS]: this.getEventDetails(data.promotion),\n [trackingConstants.COMPONENT_POSITION_EVENT]: this.ctaTrackingLocations[data.ctaLocation] ?? NOT_APPLICABLE,\n [trackingConstants.COMPONENT_URL_CLICKED]: url,\n [trackingConstants.COMPONENT_CONTENT_POSITION]: data.position?.toString() ?? NOT_APPLICABLE,\n });\n }\n\n trackOptinResult(data: { promotion: Promotion; trackingSource?: string; status: OfferStatus }): void {\n if (data.status === OfferStatus.OptedIn) {\n return;\n }\n\n this.trackingService.track(trackingConstants.EVENT_CLICKS, {\n ...this.baseTracking(data.promotion, data.trackingSource),\n [trackingConstants.COMPONENT_ACTION_EVENT]: 'Error - Load',\n [trackingConstants.COMPONENT_EVENT_DETAILS]: this.failedOptinText[data.status],\n });\n }\n\n private baseTracking(promotion: Promotion, tracingSource?: string): Partial {\n return {\n [trackingConstants.COMPONENT_CATEGORY_EVENT]: 'Sports Teaser Banners',\n [trackingConstants.COMPONENT_LABEL_EVENT]: this.getLabelEvent(promotion),\n [trackingConstants.COMPONENT_POSITION_EVENT]: tracingSource ?? NOT_APPLICABLE,\n [trackingConstants.COMPONENT_LOCATION_EVENT]: this.getLocationEvent(),\n [trackingConstants.COMPONENT_URL_CLICKED]: NOT_APPLICABLE,\n [trackingConstants.COMPONENT_EVENT_DETAILS]: NOT_APPLICABLE,\n };\n }\n\n private getLabelEvent(promotion: Promotion): string {\n return ['SPORTS', promotion.id, promotion.banner && promotion.banner.title].filter((x) => x).join('|');\n }\n\n private getEventDetails(promotion: Promotion): string {\n return promotion.banner?.title ?? 'CTA';\n }\n\n private getLocationEvent(): string {\n const page = this.configAccessor?.getPage();\n switch (page) {\n case WidgetPage.EventDetailsPage:\n return 'event details page';\n default:\n return NOT_APPLICABLE;\n }\n }\n}\n","@if (promotion.details) {\n @if (promotion.details.backgroundImageUrl) {\n
\n \n @if (promotion.details.heroImageUrl) {\n \n }\n
\n }\n
\n\n \n\n
\n
\n \n \n
\n @if (tncVisible) {\n
\n }\n
\n}\n","import { Component, Input } from '@angular/core';\n\nimport { Sitecore } from '@frontend/sports/common/client-config-data-access';\nimport { TrackingSource } from '@frontend/sports/tracking/feature';\nimport { CrmPromotionsBanners } from '@frontend/sports/types/components/sitecore';\n\nimport { OfferStatus, Promotion } from '../crm-offer-data/crm-offer.model';\nimport { OfferStatusService } from './offer-status.service';\nimport { CTALocation, CrmPromotionTracking } from './tracking.service';\n\n@Component({\n selector: 'ms-crm-promotion-banner-more-info',\n templateUrl: './banner-more-info.html',\n styleUrl: './banner-more-info.scss',\n})\nexport class BannerMoreInfoComponent {\n tncVisible = true;\n offersStatusEnum = OfferStatus;\n translation: CrmPromotionsBanners;\n\n @Input() promotion: Promotion;\n @Input() tracking?: TrackingSource;\n\n constructor(\n public sitecore: Sitecore,\n private offerStatusService: OfferStatusService,\n private trackingService: CrmPromotionTracking,\n ) {\n this.translation = sitecore.crmPromotionsBanners;\n }\n\n toggleTnc(): void {\n this.tncVisible = !this.tncVisible;\n }\n\n onButtonClick(event: Event): void {\n event.stopPropagation();\n\n this.trackingService.trackCTA({\n promotion: this.promotion,\n trackingSource: this.tracking?.source,\n ctaLocation: CTALocation.MoreDetails,\n position: this.tracking?.position,\n });\n\n this.offerStatusService.setOfferStatus(this.promotion).subscribe((response) => {\n this.promotion.status = response;\n });\n }\n}\n","import { Injectable } from '@angular/core';\n\nimport { ApiService } from '@frontend/sports/common/api-utils';\nimport { ModalDialogService, ModalRef } from '@frontend/sports/modal/feature';\nimport { TrackingSource } from '@frontend/sports/tracking/feature';\nimport { LocalStoreService } from '@frontend/vanilla/core';\nimport { firstValueFrom } from 'rxjs';\n\nimport { Promotion } from '../crm-offer-data/crm-offer.model';\nimport { CampaignDetails } from '../crm-offer-data/crm-offer.server.model';\nimport { BannerMoreInfoComponent } from './banner-more-info.component';\nimport { CrmPromotionFactory } from './crm-promotion.factory';\n\n@Injectable({ providedIn: 'root' })\nexport class BannerService {\n private dialog: ModalRef;\n storageKey = 'promoBanner';\n\n constructor(\n private crmPromotionFactory: CrmPromotionFactory,\n private apiService: ApiService,\n private localStorage: LocalStoreService,\n private modalDialogService: ModalDialogService,\n ) {}\n\n async loadBannerDetails(promotion: Promotion): Promise {\n if (promotion.details) {\n return;\n }\n\n await firstValueFrom(this.apiService.get('crmPromotions/getBannerDetails', { cmsItemId: promotion.cmsItemId })).then(\n (result: any) => {\n result && this.crmPromotionFactory.setBannerDetails(promotion, result);\n },\n );\n }\n\n async openDetailsPage(promotion: Promotion, tracking: TrackingSource | undefined, callback: () => void): Promise {\n await this.loadBannerDetails(promotion);\n if (!promotion.details) {\n return;\n }\n\n this.localStorage.set(this.storageKey, promotion.id);\n this.dialog = this.modalDialogService.openDialog(BannerMoreInfoComponent, {\n data: {\n promotion,\n tracking,\n },\n settings: {\n title: promotion.details.title,\n cssClass: 'crm-promotion-more-info',\n isModalDialog: false,\n fit: false,\n showDialogHeader: true,\n },\n });\n this.dialog.result.finally(() => {\n this.localStorage.set(this.storageKey, undefined);\n callback();\n });\n }\n\n get detailsPopupRef(): ModalRef | undefined {\n return this.dialog;\n }\n}\n","@if (!optInInvisible) {\n \n}\n
\n {{ translations.MoreInfo }}\n
\n","import { ChangeDetectionStrategy, ChangeDetectorRef, Component, EventEmitter, HostBinding, Input, Output, inject } from '@angular/core';\n\nimport { CrmConfig, Sitecore } from '@frontend/sports/common/client-config-data-access';\nimport { TrackingSource } from '@frontend/sports/tracking/feature';\nimport { CrmPromotionsBanners } from '@frontend/sports/types/components/sitecore';\nimport { UserService } from '@frontend/sports/user/feature';\nimport { WINDOW } from '@frontend/vanilla/core';\n\nimport { Promotion } from '../crm-offer-data/crm-offer.model';\nimport { BannerService } from './banner.service';\nimport { OfferStatusService } from './offer-status.service';\nimport { PromotionStatusButtonComponent } from './promotion-status-button.component';\nimport { CTALocation, CrmPromotionTracking } from './tracking.service';\n\n@Component({\n selector: 'ms-promotion-status-button-wrapper',\n templateUrl: 'promotion-status-button-wrapper.component.html',\n changeDetection: ChangeDetectionStrategy.OnPush,\n standalone: true,\n imports: [PromotionStatusButtonComponent],\n})\nexport class PromotionStatusButtonWrapperComponent {\n translations: CrmPromotionsBanners;\n\n @Input() promotion: Promotion;\n @Input() tracking?: TrackingSource;\n @Output() detailsPopupChange = new EventEmitter();\n @Output() optInButtonClicked = new EventEmitter();\n\n @HostBinding('class') className = 'info-panel';\n\n private optInRedirect = false;\n optInInvisible = false;\n\n constructor(\n private trackingService: CrmPromotionTracking,\n private crmConfig: CrmConfig,\n private m2User: UserService,\n public sitecore: Sitecore,\n private offerStatusService: OfferStatusService,\n private changeDetectorRef: ChangeDetectorRef,\n private bannerService: BannerService,\n ) {\n this.translations = sitecore.crmPromotionsBanners;\n\n const { optInRedirectBlackListedCountries, optInInvisibleBlackListedCountries } = this.crmConfig.promotions;\n this.optInRedirect = optInRedirectBlackListedCountries.includes(this.m2User.country);\n this.optInInvisible = optInInvisibleBlackListedCountries.includes(this.m2User.country);\n }\n\n async onButtonClick(event: Event): Promise {\n event.stopPropagation();\n\n if (this.optInRedirect) {\n this.detailsPopupChange.emit(true);\n await this.bannerService.openDetailsPage(this.promotion, this.tracking, () => {\n this.detailsPopupChange.emit(false);\n this.changeDetectorRef.detectChanges();\n });\n\n return;\n }\n\n this.setOfferStatus();\n this.optInButtonClicked.emit(true);\n }\n\n private setOfferStatus(): void {\n this.trackClick(CTALocation.OptIn);\n\n this.offerStatusService.setOfferStatus(this.promotion).subscribe((response) => {\n this.promotion.status = response;\n this.trackingService.trackOptinResult({ promotion: this.promotion, trackingSource: this.tracking?.source, status: response });\n this.changeDetectorRef.detectChanges();\n });\n }\n\n async moreInfoClick(event?: Event): Promise {\n event && event.stopPropagation();\n\n this.trackClick(CTALocation.MoreDetails);\n\n const moreInfoLink = this.promotion.banner && this.promotion.banner.moreInfo;\n if (moreInfoLink) {\n this.#window.open(moreInfoLink, '_blank');\n\n return;\n }\n this.detailsPopupChange.emit(true);\n await this.bannerService.openDetailsPage(this.promotion, this.tracking, () => {\n this.detailsPopupChange.emit(false);\n this.changeDetectorRef.detectChanges();\n });\n }\n\n closeDialog(): void {\n this.bannerService.detailsPopupRef?.close();\n }\n\n private trackClick(ctaLocation: CTALocation): void {\n this.trackingService.trackCTA({\n promotion: this.promotion,\n trackingSource: this.tracking?.source,\n ctaLocation,\n position: this.tracking?.position,\n });\n }\n\n readonly #window = inject(WINDOW);\n}\n","import { Directive, HostListener, Input } from '@angular/core';\n\nimport { trackingConstants } from './tracking.models';\nimport { TrackingService } from './tracking.service';\n\n@Directive({\n selector: '[msTrackReferringActionDirective]',\n standalone: true,\n})\nexport class TrackReferringACtionDirective {\n @Input() trackingValue?: string;\n @Input() cleanAfterTrack = false;\n\n constructor(private trackerService: TrackingService) {}\n\n @HostListener('click') onClick() {\n this.trackerService.update({ [trackingConstants.PAGE_REFERRING_ACTION]: this.trackingValue });\n\n if (this.cleanAfterTrack) {\n this.trackerService.triggerEventWithCleaning();\n }\n }\n}\n","import { CommonModule } from '@angular/common';\nimport { ChangeDetectionStrategy, Component, ViewEncapsulation, booleanAttribute, input } from '@angular/core';\n\nexport const DS_LISTITEM_SIZE_ARRAY = ['medium', 'medium-condensed', 'large', 'large-condensed'] as const;\nexport type DsListItemSize = (typeof DS_LISTITEM_SIZE_ARRAY)[number];\n\n@Component({\n selector: 'ds-list-item',\n standalone: true,\n imports: [CommonModule],\n template: `\n \n @if (showCenterSlot()) {\n
\n \n {{ title() }}\n \n @if (subtitle()) {\n {{ subtitle() }}\n }\n @if (subtext()) {\n {{ subtext() }}\n }\n
\n }\n
\n \n
\n `,\n styleUrl: './list-item.component.scss',\n host: {\n 'class': 'ds-list-item',\n '[class.ds-list-item-selected]': 'selected()',\n '[class.ds-list-item-inverse]': 'inverse()',\n '[class]': '\"ds-list-item-\"+size()',\n },\n encapsulation: ViewEncapsulation.None,\n changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class DsListItem {\n title = input.required();\n subtitle = input('');\n subtext = input('');\n selected = input(false, { transform: booleanAttribute });\n inverse = input(false, { transform: booleanAttribute });\n boldTitle = input(true, { transform: booleanAttribute });\n showCenterSlot = input(true, { transform: booleanAttribute });\n size = input('large');\n}\n","@if (!href) {\n \n \n \n} @else {\n \n \n \n}\n\n\n \n @if (showStartSlot) {\n \n @if (icon) {\n \n }\n \n \n }\n\n
\n \n @if (badge) {\n \n }\n @if (!title) {\n \n }\n @if (count) {\n \n {{ count }}\n \n }\n @if (arrow) {\n \n }\n
\n \n \n
\n@if (isShowBorder) {\n \n}\n","import { NgTemplateOutlet } from '@angular/common';\nimport { ChangeDetectionStrategy, Component, EventEmitter, HostBinding, Input, Output } from '@angular/core';\n\nimport { DsDivider } from '@frontend/ui/divider';\nimport { DsIconButton } from '@frontend/ui/icon-button';\nimport { DsListItem } from '@frontend/ui/list-item';\nimport { DsNotificationBubble } from '@frontend/ui/notification-bubble';\nimport { PlainLinkComponent } from '@frontend/vanilla/core';\nimport { IconCustomComponent } from '@frontend/vanilla/features/icons';\nimport { SpeculativeLinkDirective } from '@frontend/vanilla/features/speculative-link';\n\n@Component({\n selector: 'ms-list-item',\n templateUrl: './list-item.html',\n standalone: true,\n styleUrls: ['./list-item.scss'],\n imports: [\n IconCustomComponent,\n DsListItem,\n DsDivider,\n DsNotificationBubble,\n DsIconButton,\n NgTemplateOutlet,\n PlainLinkComponent,\n SpeculativeLinkDirective,\n ],\n changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class ListItemComponent {\n @HostBinding('class') className = 'list-item-ds';\n\n @Input() title?: string;\n @Input() boldTitle = false;\n @Input() subTitle?: string;\n @Input() count?: number;\n @Input() icon?: string;\n @Input() iconSize?: string;\n @Input() href?: string;\n @Input() arrow = true;\n @Input() isShowBorder = true;\n @Input() badge = false;\n @Input() isActive = false;\n @Input() showStartSlot = true;\n @Output() itemClicked = new EventEmitter();\n\n clicked() {\n this.itemClicked.emit();\n }\n}\n","\n \n\n\n\n \n \n \n\n\n\n
\n \n \n
\n
\n {{ title }}\n \n \n
\n
{{ count }}
\n
\n \n
\n","import { NgIf, NgTemplateOutlet } from '@angular/common';\nimport { ChangeDetectionStrategy, Component, EventEmitter, HostBinding, Input, Output } from '@angular/core';\n\nimport { AutomationModule } from '@frontend/sports/automation/core-feature';\nimport { TrackingService, trackingConstants } from '@frontend/sports/tracking/feature';\nimport { PlainLinkComponent } from '@frontend/vanilla/core';\nimport { SpeculativeLinkDirective } from '@frontend/vanilla/features/speculative-link';\n\n@Component({\n selector: 'ms-item',\n templateUrl: 'item.html',\n changeDetection: ChangeDetectionStrategy.OnPush,\n standalone: true,\n imports: [NgIf, AutomationModule, NgTemplateOutlet, PlainLinkComponent, SpeculativeLinkDirective],\n})\nexport class ItemComponent {\n @Input() title?: string;\n @Input() count?: number;\n @Input() icon?: string;\n @Input() href?: string;\n @Input() regionIds?: number[];\n @Input() tracking = 'M2_quicklinks';\n @Input() arrow = true;\n @Input() attribute?: string;\n @HostBinding('class') className = 'list-item';\n @Output() itemClicked = new EventEmitter();\n constructor(private trackerService: TrackingService) {}\n\n clicked(): void {\n if (this.tracking) {\n this.trackerService.update({ [trackingConstants.PAGE_REFERRING_ACTION]: this.tracking });\n\n if (this.href?.indexOf('http') === 0) {\n this.trackerService.triggerEventWithCleaning();\n }\n }\n this.itemClicked.emit();\n }\n}\n","import { Component } from '@angular/core';\n\nimport { Sitecore } from '@frontend/sports/common/client-config-data-access';\nimport { IconCustomComponent } from '@frontend/vanilla/features/icons';\n\n@Component({\n selector: 'ms-promotion-badge',\n templateUrl: './badge.html',\n standalone: true,\n imports: [IconCustomComponent],\n})\nexport class BadgeComponent {\n promotionTitle: string;\n\n constructor(public sitecore: Sitecore) {\n this.promotionTitle = sitecore.crmPromotions.Promotion;\n }\n}\n","\n {{ promotionTitle }}\n\n","import { Directive, inject } from '@angular/core';\n\nimport { LcpCandidateConfig } from '@frontend/sports/common/client-config-data-access';\n\n/**\n *\n * @experimental\n *\n * @whatItDoes Makes the element eligible to be considered the largest contentful paint by adding an invisible svg as a\n * background image. For more information on what makes a candidate eligible to be largest contentful paint read the\n * official specs. {@link https://w3c.github.io/largest-contentful-paint/#sec-terminology|W3C Largest Contentful Paint}\n *\n */\n\n@Directive({\n standalone: true,\n host: {\n '[class]': 'candidateClass',\n },\n})\nexport class LcpCandidateDirective {\n #config = inject(LcpCandidateConfig);\n candidateClass = this.#config.isEnabled ? 'sports-sports-lcp-candidate' : null;\n}\n","import { ChangeDetectionStrategy, Component, ViewEncapsulation, booleanAttribute, computed, input } from '@angular/core';\n\nexport const DS_CARD_VARIANT_ARRAY = ['surface-lowest', 'surface-low', 'surface-high', 'surface-highest'] as const;\nexport type DsCardVariant = (typeof DS_CARD_VARIANT_ARRAY)[number];\n\n@Component({\n selector: 'ds-card',\n template: ``,\n host: {\n 'class': 'ds-card',\n '[class]': `[ hostClass()]`,\n '[class.ds-card-hover-effect]': 'withHover()',\n '[class.ds-card-no-elevation]': '!elevated()',\n '[class.ds-card-overflow-hidden]': 'noOverflow()',\n '[class.ds-card-no-border]': 'noBorder()',\n '[class.ds-card-no-border-radius]': 'noBorderRadius()',\n },\n styleUrl: 'card.component.scss',\n standalone: true,\n changeDetection: ChangeDetectionStrategy.OnPush,\n encapsulation: ViewEncapsulation.None,\n})\nexport class DsCard {\n variant = input('surface-lowest');\n elevated = input(true, { transform: booleanAttribute });\n withHover = input(false, { transform: booleanAttribute });\n /* applying a css class with overflow:hidden style */\n noOverflow = input(false, { transform: booleanAttribute });\n noBorderRadius = input(false, { transform: booleanAttribute });\n noBorder = input(false, { transform: booleanAttribute });\n hostClass = computed(() => `ds-card-${this.variant()}`);\n}\n","
\n
\n \n\n \n\n
\n
\n
{{ promotion.banner?.title }}
\n
\n
\n \n
\n
\n\n
\n","import {\n AfterContentInit,\n ChangeDetectionStrategy,\n ChangeDetectorRef,\n Component,\n DestroyRef,\n ElementRef,\n HostBinding,\n HostListener,\n Input,\n OnChanges,\n OnInit,\n inject,\n} from '@angular/core';\nimport { takeUntilDestroyed } from '@angular/core/rxjs-interop';\nimport { SafeStyle } from '@angular/platform-browser';\n\nimport { CrmConfig, Sitecore } from '@frontend/sports/common/client-config-data-access';\nimport { WindowEventService } from '@frontend/sports/common/core/utils/window-event';\nimport { TrackingSource as TrackingSourceData } from '@frontend/sports/tracking/feature';\nimport { UserService } from '@frontend/sports/user/feature';\nimport { LocalStoreService } from '@frontend/vanilla/core';\n\nimport { OfferStatus, Promotion, TrackingSource } from '../crm-offer-data/crm-offer.model';\nimport { LcpCandidateDirective } from '../perf-cdk/lcp-candidate.directive';\nimport { BannerService } from './banner.service';\nimport { OfferStatusService } from './offer-status.service';\nimport { CTALocation, CrmPromotionTracking } from './tracking.service';\n\n@Component({\n selector: 'ms-crm-promotion-banner',\n templateUrl: 'banner.html',\n changeDetection: ChangeDetectionStrategy.OnPush,\n providers: [CrmPromotionTracking], // additionally registering dedicated crm promo tracking service, to make sure modular-tracking.service overrides tracking.service\n hostDirectives: [LcpCandidateDirective],\n})\nexport class BannerComponent implements OnInit, OnChanges, AfterContentInit {\n overlayActiveBreakpointPx = 358;\n translations: { [key: string]: string } = {};\n optInInvisible = false;\n private optInRedirect = false;\n\n @Input() promotion: Promotion;\n @Input() listIndex = 0;\n @Input() isDynamicSize = false;\n @Input() trackingSource = TrackingSource.Teaser;\n\n @HostBinding('style.background-image') backgroundImage: SafeStyle;\n @HostBinding('class.overlay-active') overlayActive: boolean;\n @HostBinding('class.large-dynamic-banner') largeDynamicBannerClass: boolean;\n\n private largeBannerWidthPx = 608;\n trackingSourceText: string;\n private readonly destroyRef = inject(DestroyRef);\n\n constructor(\n private elementRef: ElementRef,\n public sitecore: Sitecore,\n private tracking: CrmPromotionTracking,\n private changeDetectorRef: ChangeDetectorRef,\n private bannerService: BannerService,\n private windowEvent: WindowEventService,\n private localStorage: LocalStoreService,\n private crmConfig: CrmConfig,\n private m2User: UserService,\n private offerStatusService: OfferStatusService,\n ) {\n this.optInRedirect = this.crmConfig.promotions.optInRedirectBlackListedCountries.indexOf(this.m2User.country) > -1;\n this.optInInvisible = this.crmConfig.promotions.optInInvisibleBlackListedCountries.indexOf(this.m2User.country) > -1;\n }\n\n get trackingSourceData(): TrackingSourceData {\n return {\n source: this.trackingSourceText,\n position: this.listIndex,\n };\n }\n\n async ngOnInit(): Promise {\n this.trackingSourceText = `${this.trackingSource}_${this.listIndex + 1}`;\n this.tracking.trackViewed(this.promotion, this.trackingSourceText);\n\n const storedId = this.localStorage.get(this.bannerService.storageKey);\n\n if (storedId !== this.promotion.id) {\n return;\n }\n\n if (this.optInRedirect || this.optInInvisible) {\n await this.bannerService.openDetailsPage(this.promotion, this.trackingSourceData, () => {\n this.changeDetectorRef.detectChanges();\n });\n }\n\n this.localStorage.set(this.bannerService.storageKey, undefined);\n if (this.promotion?.status !== OfferStatus.NoOptIn) {\n this.setOfferStatus();\n }\n }\n\n @HostListener('click')\n async onclick(): Promise {\n this.trackClick(CTALocation.Banner);\n await this.bannerService.openDetailsPage(this.promotion, this.trackingSourceData, () => {\n this.changeDetectorRef.detectChanges();\n });\n }\n\n ngOnChanges(): void {\n if (this.promotion && this.promotion.banner && this.promotion.banner.backgroundImageUrl) {\n this.backgroundImage = `url(${this.promotion.banner.backgroundImageUrl})`;\n }\n }\n\n ngAfterContentInit(): void {\n // TODO Roxin - this will reflow content .. a few times\n this.overlayActive = this.elementRef.nativeElement.offsetWidth < this.overlayActiveBreakpointPx;\n\n this.isDynamicSize && this.setDynamicSizeBanner();\n }\n\n private setDynamicSizeBanner(): void {\n this.windowEvent.resize\n .asObservable()\n .pipe(takeUntilDestroyed(this.destroyRef))\n .subscribe(() => this.setClasses());\n\n this.setClasses();\n }\n\n private setOfferStatus(): void {\n this.trackClick(CTALocation.OptIn);\n\n this.offerStatusService.setOfferStatus(this.promotion).subscribe((response) => {\n this.promotion.status = response;\n this.tracking.trackOptinResult({ promotion: this.promotion, trackingSource: this.trackingSourceText, status: response });\n this.changeDetectorRef.detectChanges();\n });\n }\n\n private setClasses(): void {\n this.largeDynamicBannerClass = this.elementRef.nativeElement.offsetWidth > this.largeBannerWidthPx;\n }\n\n private trackClick(ctaLocation: CTALocation): void {\n this.tracking.trackCTA({\n promotion: this.promotion,\n trackingSource: this.trackingSourceText,\n ctaLocation,\n position: this.listIndex,\n });\n }\n}\n","@if (!href || popup) {\n \n} @else {\n \n \n \n}\n\n\n
\n @if (sportsIcon) {\n \n } @else if (icon) {\n \n }\n \n \n
\n @if (isMiniCarouselLayout) {\n \n } @else {\n \n }\n
\n\n\n
\n @if (title) {\n {{ title }}\n }\n \n @if (badgesEnabled) {\n \n }\n @if (showLiveBadge) {\n
{{ sitecore.event.Live }}
\n }\n
\n @if (count) {\n
{{ count }}
\n }\n @if (arrow) {\n
\n }\n
\n","import { NgClass, NgOptimizedImage, NgTemplateOutlet } from '@angular/common';\nimport { Component, Input, OnChanges } from '@angular/core';\n\nimport { AutomationModule } from '@frontend/sports/automation/core-feature';\nimport { Sitecore } from '@frontend/sports/common/client-config-data-access';\nimport { TrackingService } from '@frontend/sports/tracking/feature';\nimport { LinkListLayout } from '@frontend/sports/types/components/widget/types';\nimport { PlainLinkComponent } from '@frontend/vanilla/core';\nimport { IconCustomComponent } from '@frontend/vanilla/features/icons';\nimport { SpeculativeLinkDirective } from '@frontend/vanilla/features/speculative-link';\n\nimport { EpcotConfigService } from '../common/epcot-config.service';\nimport { ItemComponent } from '../common/item/item.component';\nimport { BadgeComponent } from './badge.component';\n\n@Component({\n selector: 'ms-promo-item',\n templateUrl: './promo-item.html',\n standalone: true,\n imports: [\n AutomationModule,\n NgTemplateOutlet,\n BadgeComponent,\n NgClass,\n PlainLinkComponent,\n SpeculativeLinkDirective,\n NgOptimizedImage,\n IconCustomComponent,\n ],\n})\nexport class PromoItemComponent extends ItemComponent implements OnChanges {\n @Input() badgesEnabled = false;\n @Input() showLiveBadge = false;\n @Input() popup: string;\n @Input() leagueTitle?: string;\n @Input() leagueIcon?: string;\n @Input() sportsIcon?: string;\n @Input() layout?: LinkListLayout;\n\n isMiniCarouselLayout: boolean;\n readonly isEpcotEnabled = this.epcotConfigService.isEnabled();\n\n constructor(\n public sitecore: Sitecore,\n trackerService: TrackingService,\n private epcotConfigService: EpcotConfigService,\n ) {\n super(trackerService);\n }\n\n ngOnChanges(): void {\n this.isMiniCarouselLayout = this.layout === LinkListLayout.MiniCarousel && !!this.leagueTitle && this.isEpcotEnabled;\n }\n}\n","@if (isStackedLayout && items?.length) {\n \n @for (item of items; track $index; let last = $last) {\n \n }\n \n} @else {\n @if (items?.length) {\n @if (isCarouselLayout) {\n \n @if (isMiniCarouselLayout) {\n
    \n \n
\n } @else {\n \n }\n
\n } @else {\n \n }\n }\n\n \n @for (item of items; track $index) {\n \n @if (hasCompetitionLogo(item.sportId, item.leagueId)) {\n \n }\n\n @if (item.colouredIcon) {\n \n }\n \n }\n \n}\n","import { ChangeDetectionStrategy, Component, EventEmitter, HostBinding, Input, OnInit, Output, ViewChild, signal } from '@angular/core';\n\nimport { EpcotConfig } from '@frontend/sports/common/client-config-data-access';\nimport { ScrollAdapterComponent } from '@frontend/sports/common/core/feature/scroll-adapter';\nimport { TimerService } from '@frontend/sports/common/core/utils/timer';\nimport { CompetitionLogosService } from '@frontend/sports/competition-logos/feature';\nimport { LinkListLayout } from '@frontend/sports/types/components/widget/types';\nimport { keys } from 'lodash-es';\n\nimport { ListItem } from '../common/item/item.model';\nimport { PopupManager, PopupTypes } from '../popup/popup-manager.service';\nimport { QuickLinkItem } from '../quick-links/quick-links.models';\n\n@Component({\n selector: 'ms-promo-item-list',\n templateUrl: 'promo-item-list.html',\n styleUrl: './promo-item-list.component.scss',\n changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class PromoItemListComponent implements OnInit {\n @Input() tracking?: string;\n @Input() arrow = true;\n @Input() items: ListItem[] | QuickLinkItem[];\n @Input() itemClass = '';\n @Input() promotedCompetitionIdsMap = {};\n @Input() promotedSportIdsMap = {};\n @Input() layout?: LinkListLayout;\n @Output() itemClick = new EventEmitter();\n @ViewChild(ScrollAdapterComponent) scrollAdapter?: ScrollAdapterComponent;\n cleanedAfterTrack = signal(false);\n private isEpcotEnabled: boolean;\n @HostBinding('class.mini-carousel-with-subheader') get isMiniCarouselLayout(): boolean {\n return this.layout === LinkListLayout.MiniCarousel && this.isEpcotEnabled;\n }\n\n @HostBinding('class.stacked-item-list') get isStackedList(): boolean {\n return this.layout === LinkListLayout.Stacked;\n }\n\n isStackedLayout = false;\n\n constructor(\n private competitionLogoService: CompetitionLogosService,\n private popupManager: PopupManager,\n private epcotConfig: EpcotConfig,\n private timer: TimerService,\n ) {\n this.isEpcotEnabled = this.epcotConfig.isEnabled;\n }\n\n ngOnInit(): void {\n this.timer.setTimeout(() => this.scrollAdapter?.setArrows());\n this.isStackedLayout = this.layout === LinkListLayout.Stacked;\n }\n\n clicked(item: ListItem | QuickLinkItem): void {\n this.cleanedAfterTrack.update(() => item.href?.indexOf('http') === 0 || false);\n this.itemClick.emit(item);\n\n if ('popup' in item && !!item.popup) {\n this.popupManager.toggle(item.popup);\n }\n }\n\n hasCompetitionLogo(sportId: number, leagueId: number): boolean {\n return this.competitionLogoService.hasLogo(sportId, leagueId);\n }\n\n hasBgcolorForTilesOrGridView(item: ListItem): boolean {\n return !!(item.bgColor && this.layout && [LinkListLayout.TilesCarousel, LinkListLayout.Grid].includes(this.layout));\n }\n\n areBadgesEnabled(item: ListItem): boolean {\n if (!item.attributes) {\n return false;\n }\n\n const sportIds = item.attributes['promotedSportId']?.toString().split(',') || [];\n const leagueIds = item.attributes['promotedLeagueId']?.toString().split(',') || [];\n\n return (\n keys(this.promotedSportIdsMap).some((key) => sportIds.includes(key)) ||\n keys(this.promotedCompetitionIdsMap).some((key) => leagueIds.includes(key))\n );\n }\n\n itemIndexTrackBy(index: any, item: any): string {\n return index;\n }\n\n get isCarouselLayout(): boolean {\n return !!this.layout && [LinkListLayout.MiniCarousel, LinkListLayout.TilesCarousel].includes(this.layout);\n }\n}\n","import { CommonModule, NgOptimizedImage } from '@angular/common';\nimport { NgModule } from '@angular/core';\n\nimport { AutomationModule } from '@frontend/sports/automation/core-feature';\nimport { ScrollAdapterComponent } from '@frontend/sports/common/core/feature/scroll-adapter';\nimport { ListItemComponent } from '@frontend/sports/common/ui/sports-list-items';\nimport { CompetitionLogoComponent } from '@frontend/sports/competition-logos/feature';\nimport { TrackReferringACtionDirective } from '@frontend/sports/tracking/feature';\nimport { DsButton } from '@frontend/ui/button';\nimport { DsCard } from '@frontend/ui/card';\nimport { PlainLinkComponent } from '@frontend/vanilla/core';\nimport { IconCustomComponent } from '@frontend/vanilla/features/icons';\nimport { SpeculativeLinkDirective } from '@frontend/vanilla/features/speculative-link';\n\nimport { BadgeComponent } from './badge.component';\nimport { BannerMoreInfoComponent } from './banner-more-info.component';\nimport { BannerComponent } from './banner.component';\nimport { PromoItemListComponent } from './promo-item-list.component';\nimport { PromoItemComponent } from './promo-item.component';\nimport { PromotionStatusButtonWrapperComponent } from './promotion-status-button-wrapper.component';\nimport { PromotionStatusButtonComponent } from './promotion-status-button.component';\nimport { SimpleBadgeComponent } from './simple-badge.component';\n\n@NgModule({\n imports: [\n AutomationModule,\n CommonModule,\n CompetitionLogoComponent,\n NgOptimizedImage,\n PlainLinkComponent,\n NgOptimizedImage,\n ScrollAdapterComponent,\n BadgeComponent,\n PromotionStatusButtonComponent,\n PromotionStatusButtonWrapperComponent,\n PromoItemComponent,\n SpeculativeLinkDirective,\n IconCustomComponent,\n DsButton,\n TrackReferringACtionDirective,\n ListItemComponent,\n DsCard,\n ],\n declarations: [BannerComponent, BannerMoreInfoComponent, PromoItemListComponent, SimpleBadgeComponent],\n exports: [\n BannerComponent,\n SimpleBadgeComponent,\n BannerMoreInfoComponent,\n PromoItemComponent,\n PromoItemListComponent,\n PromotionStatusButtonComponent,\n PromotionStatusButtonWrapperComponent,\n ],\n})\nexport class CrmPromotionModule {}\n","import { Directive, ElementRef, EventEmitter, Input, OnDestroy, OnInit, Output, Renderer2 } from '@angular/core';\n\nimport { ScrollContainerService } from '@frontend/sports/common/core/utils/scroll-container';\nimport { Subscription } from 'rxjs';\n\nimport { ColumnLayoutService } from '../layout/column-layout.service';\n\n@Directive({\n selector: '[msScrolledToBottom]',\n})\nexport class ScrolledToBottomDirective implements OnInit, OnDestroy {\n private readonly DEFAULT_SCROLL_TOLERANCE = 170; // number of px before reaching the footer\n\n @Input() scrollContext: 'self' | 'document' = 'self'; // self = when scroll current element, document when document scrolling\n @Input() toleranceValue: number = this.DEFAULT_SCROLL_TOLERANCE;\n @Input() toleranceType: 'exact' | 'relative' = 'exact';\n @Output() scrollAtBottom = new EventEmitter();\n @Output() scrollAtMid = new EventEmitter();\n\n private subscription: Subscription;\n private previousTop = 0;\n private scrollTolerance?: number;\n\n constructor(\n private elementRef: ElementRef,\n private renderer: Renderer2,\n private layout: ColumnLayoutService,\n private scrollContainer: ScrollContainerService,\n ) {}\n\n ngOnInit(): void {\n this.subscription = this.scrollContext === 'self' ? this.subscribeElement() : this.subscribeContainer();\n }\n\n ngOnDestroy(): void {\n this.subscription.unsubscribe();\n }\n\n private subscribeElement(): Subscription {\n const handler = (event: Event) => this.onScroll(event.target as HTMLElement | Window);\n const listener = this.renderer.listen(this.elementRef.nativeElement, 'scroll', handler);\n\n return new Subscription(listener);\n }\n\n private subscribeContainer(): Subscription {\n return this.scrollContainer.scroll.subscribe((container) => this.onScroll(container as HTMLElement | Window));\n }\n\n private getHeight(element: HTMLElement | Window): { clientHeight: number; scrollHeight: number; scrollTop: number } {\n let target: HTMLElement;\n let scrollTop: number;\n\n if ('pageYOffset' in element) {\n const windowRef = element;\n target = windowRef.document.documentElement;\n scrollTop = windowRef.pageYOffset;\n } else {\n target = element;\n scrollTop = target.scrollTop;\n }\n\n return {\n clientHeight: target.clientHeight,\n scrollHeight: target.scrollHeight,\n scrollTop,\n };\n }\n\n private setTolerance(clientHeight: number): void {\n this.scrollTolerance = this.toleranceType === 'relative' ? this.toleranceValue * clientHeight : this.toleranceValue;\n }\n\n private onScroll = (element: HTMLElement | Window) => {\n const { clientHeight, scrollHeight, scrollTop } = this.getHeight(element);\n const offsetTop = this.previousTop < scrollTop;\n\n if (!this.scrollTolerance) {\n this.setTolerance(clientHeight);\n }\n\n if (offsetTop && scrollTop + clientHeight + this.layout.currentFooterSize + this.scrollTolerance! >= scrollHeight) {\n this.scrollAtBottom.emit();\n }\n\n const height = clientHeight / 2;\n const isMidLevel = scrollTop - height >= 0;\n this.scrollAtMid.emit(isMidLevel);\n this.previousTop = scrollTop;\n };\n}\n","import { Directive, ElementRef, EventEmitter, Input, OnDestroy, Output, Renderer2 } from '@angular/core';\n\nimport { Nullable } from '@frontend/sports/common/core/utils/extended-types';\nimport { TimerService } from '@frontend/sports/common/core/utils/timer';\n\n@Directive({\n selector: '[msClassAutoRemove]',\n})\nexport class ClassAutoRemoveDirective implements OnDestroy {\n @Input('msClassAutoRemove') className = '';\n @Input() msClassAutoRemoveTime = 1000; // Default value: 1 sec.\n @Output() msClassAutoRemoveIsClassVisibleChange = new EventEmitter();\n\n private removeTimer: Nullable<() => void> = null;\n private _isClassVisible: boolean;\n\n @Input('msClassAutoRemoveIsClassVisible')\n get isClassVisible(): boolean {\n return this._isClassVisible;\n }\n\n set isClassVisible(value: boolean) {\n this.applyChanges(value);\n }\n\n constructor(\n private renderer: Renderer2,\n private elementRef: ElementRef,\n private timerService: TimerService,\n ) {}\n\n ngOnDestroy(): void {\n this.clearTimer();\n }\n\n private applyChanges(value: boolean): void {\n if (value && !this.elementHasClass()) {\n this.addClass();\n this.startTimer();\n } else if (!value && this.elementHasClass()) {\n this.removeClass();\n this.clearTimer();\n }\n }\n\n private addClass(): void {\n this.renderer.addClass(this.elementRef.nativeElement, this.className);\n this._isClassVisible = true;\n this.msClassAutoRemoveIsClassVisibleChange.emit(true);\n }\n\n private removeClass(): void {\n this.renderer.removeClass(this.elementRef.nativeElement, this.className);\n this._isClassVisible = false;\n this.msClassAutoRemoveIsClassVisibleChange.emit(false);\n }\n\n private startTimer(): void {\n this.removeTimer = this.timerService.setTimeout(() => {\n this.removeClass();\n this.removeTimer = null;\n }, this.msClassAutoRemoveTime);\n }\n\n private clearTimer(): void {\n if (this.removeTimer) {\n this.removeTimer = null;\n }\n }\n\n private elementHasClass(): boolean {\n return this.elementRef.nativeElement.classList.contains(this.className);\n }\n}\n","import { DOCUMENT } from '@angular/common';\nimport { Directive, ElementRef, Input, OnInit, inject } from '@angular/core';\n\n@Directive({\n selector: '[msColumnWidth]',\n})\nexport class ColumnWidthDirective implements OnInit {\n private readonly _doc = inject(DOCUMENT);\n\n constructor(private element: ElementRef) {}\n\n @Input('msColumnWidth') columnSelector = '';\n\n ngOnInit(): void {\n this.setElementWidth();\n }\n\n setElementWidth(): void {\n const column = this.getColumnElement();\n if (!column) {\n throw new Error('could not find .column parent element');\n }\n\n this.element.nativeElement.style.width = column.getBoundingClientRect().width + 'px';\n }\n\n private getColumnElement(): HTMLElement | null {\n return this._doc.querySelector(`.${this.columnSelector}`);\n }\n}\n","import { DOCUMENT } from '@angular/common';\nimport { Directive, ElementRef, HostListener, OnInit, inject } from '@angular/core';\n\nimport { Nullable } from '@frontend/sports/common/core/utils/extended-types';\nimport { WINDOW } from '@frontend/vanilla/core';\n\n@Directive({\n selector: '[msHeightToBottom]',\n})\nexport class HeightToBottomDirective implements OnInit {\n readonly #window = inject(WINDOW);\n private readonly _doc = inject(DOCUMENT);\n\n constructor(private element: ElementRef) {}\n\n ngOnInit(): void {\n this.setElementHeight();\n }\n\n @HostListener('window:resize')\n setElementHeight(): void {\n const pageHeight = this.#window.innerHeight;\n const bottomNav = this._doc.querySelector('nav.bottom-nav') as Nullable;\n const bottomNavOffsetHeight = bottomNav?.offsetHeight || 50;\n\n const elementOffsetTop = this.element.nativeElement.offsetTop;\n const mainHeader = this._doc.querySelector('header.header') as Nullable;\n const mainHeaderHeight = mainHeader?.offsetHeight || 0;\n\n const elementTop = elementOffsetTop > 0 ? elementOffsetTop : mainHeaderHeight;\n this.element.nativeElement.style.height = pageHeight - bottomNavOffsetHeight - elementTop + 'px';\n }\n}\n","import { Directive, ElementRef, OnInit, Renderer2 } from '@angular/core';\n\n@Directive({\n selector: '[msInfoyTooltip]',\n})\nexport class InfoTooltipDirective implements OnInit {\n constructor(\n private element: ElementRef,\n private renderer: Renderer2,\n ) {}\n\n ngOnInit(): void {\n this.setElementPosition();\n }\n\n private setElementPosition(): void {\n const parent = this.renderer.parentNode(this.element.nativeElement);\n\n if (parent) {\n const leftAlign = parent.offsetLeft;\n this.renderer.setStyle(this.element.nativeElement, 'left', leftAlign + 'px');\n }\n }\n}\n","import { AfterViewInit, DestroyRef, Directive, ElementRef, EventEmitter, Input, NgZone, Output, inject } from '@angular/core';\nimport { takeUntilDestroyed } from '@angular/core/rxjs-interop';\n\nimport { WINDOW } from '@frontend/vanilla/core';\nimport { fromEvent, merge } from 'rxjs';\nimport { filter } from 'rxjs/operators';\n\n@Directive({\n selector: '[msOutsideInteraction]',\n})\nexport class OutsideInteractionDirective implements AfterViewInit {\n @Output() msOutsideInteraction = new EventEmitter();\n\n @Input() set msOutsideInteractionExclude(value: string) {\n this.excludeSelectors = (value || '').split(',');\n }\n\n private excludeSelectors: string[] | undefined = undefined;\n private readonly destroyRef = inject(DestroyRef);\n\n constructor(\n private elementRef: ElementRef,\n private ngZone: NgZone,\n ) {}\n\n ngAfterViewInit(): void {\n const events = ['click'];\n\n this.ngZone.runOutsideAngular(() => {\n merge(...events.map((event) => fromEvent(this.#window, event)))\n .pipe(\n filter((e) => !this.isOwnClick(e)),\n takeUntilDestroyed(this.destroyRef),\n )\n .subscribe(() => this.msOutsideInteraction.next());\n });\n }\n\n private isOwnClick(event: Event): boolean {\n const target = event.target as HTMLElement;\n let isOwnEvent = !!target && this.element.contains(target);\n\n if (!isOwnEvent && this.excludeSelectors) {\n this.excludeSelectors.forEach((selector) => (isOwnEvent = isOwnEvent || target.matches(selector)));\n }\n\n return isOwnEvent;\n }\n\n private get element(): HTMLElement {\n return this.elementRef.nativeElement;\n }\n\n readonly #window = inject(WINDOW);\n}\n","import { Directive, ElementRef, OnDestroy, OnInit, Renderer2 } from '@angular/core';\n\nimport { SimpleCallback } from '@frontend/sports/common/core/utils/extended-types';\nimport { TimerService } from '@frontend/sports/common/core/utils/timer';\nimport { DeviceService } from '@frontend/vanilla/core';\nimport { Subscription } from 'rxjs';\n\n@Directive({\n selector: '[msScrollIosHack]',\n})\nexport class ScrollIosHackDirective implements OnInit, OnDestroy {\n private orientationSubscription?: Subscription;\n private timeout?: SimpleCallback;\n\n constructor(\n private timerService: TimerService,\n private deviceService: DeviceService,\n private renderer: Renderer2,\n private element: ElementRef,\n ) {}\n\n ngOnInit(): void {\n if (!this.deviceService.isiOS) {\n return;\n }\n\n this.orientationSubscription = this.deviceService.orientation.subscribe(this.onOrientationChange);\n }\n\n ngOnDestroy(): void {\n this.orientationSubscription?.unsubscribe();\n\n if (this.timeout) {\n this.timeout();\n }\n }\n\n private onOrientationChange = () => {\n this.changeOverflowScrolling('auto');\n\n this.timeout = this.timerService.setTimeoutOutsideAngular(() => {\n this.changeOverflowScrolling('touch');\n }, 1000);\n };\n\n private changeOverflowScrolling = (value: string) => {\n this.renderer.setStyle(this.element.nativeElement, 'webkitOverflowScrolling', value);\n };\n}\n","import { AfterContentInit, ContentChildren, DestroyRef, Directive, ElementRef, QueryList, inject } from '@angular/core';\nimport { takeUntilDestroyed } from '@angular/core/rxjs-interop';\n\nimport { TimerService } from '@frontend/sports/common/core/utils/timer';\nimport { DeviceService } from '@frontend/vanilla/core';\nimport { auditTime } from 'rxjs/operators';\n\n@Directive({\n selector: '[msScrollTo]',\n})\nexport class ScrollToDirective implements AfterContentInit {\n @ContentChildren('msScrollToItem') items: QueryList;\n\n element: HTMLElement;\n\n private readonly destroyRef = inject(DestroyRef);\n\n constructor(\n private elm: ElementRef,\n private deviceService: DeviceService,\n private timer: TimerService,\n ) {\n this.element = this.elm.nativeElement;\n this.deviceService.orientation.pipe(takeUntilDestroyed()).subscribe(this.changeScroll);\n }\n\n ngAfterContentInit(): void {\n if (this.items.length) {\n this.changeScroll();\n }\n\n this.items.changes.pipe(auditTime(150), takeUntilDestroyed(this.destroyRef)).subscribe(this.changeScroll);\n }\n\n private changeScroll = (): void => {\n this.timer.setAnimationFrame(() => {\n if (this.element.scrollWidth > this.element.offsetWidth) {\n this.element.scrollLeft = this.element.scrollWidth;\n }\n });\n };\n}\n","import { Directive, ElementRef, Input, OnDestroy, OnInit, Renderer2 } from '@angular/core';\n\n@Directive({\n selector: '[msStopEventPropagation]',\n})\nexport class StopEventPropagationDirective implements OnInit, OnDestroy {\n @Input('msStopEventPropagation') eventsList: string;\n private eventHandlerRemovers: Array<() => void> = [];\n\n constructor(\n public element: ElementRef,\n public renderer: Renderer2,\n ) {}\n\n ngOnInit(): void {\n if (this.eventsList) {\n const events = this.eventsList.split(' ');\n events.forEach((event) => {\n const remover = this.renderer.listen(this.element.nativeElement, event, this.eventHandler);\n this.eventHandlerRemovers.push(remover);\n });\n }\n }\n\n ngOnDestroy(): void {\n this.eventHandlerRemovers.forEach((remover) => remover());\n }\n\n private eventHandler = (event: any) => {\n if (event.type === 'touchstart') {\n // HACK\n // stopping the propagation of a touchstart event causes problems with FastClick\n // because the property `targetElement` is not reset inside the FastClick library.\n // When that happens the first click on the interface is missed\n event.touchStartEventPropagationStopped = true;\n } else {\n event.stopPropagation();\n }\n };\n}\n","import { Directive, ElementRef, Input, OnChanges, Renderer2 } from '@angular/core';\n\nimport { ISimpleChanges } from '@frontend/sports/common/core/utils/simple-change';\n\n@Directive({\n selector: '[msToolbarButtonsHeight]',\n})\nexport class ToolbarButtonsHeightDirective implements OnChanges {\n @Input() isExpanded: boolean;\n\n private readonly MAX_BUTTONS_CONTAINER_WIDTH = 0.85;\n\n constructor(\n private renderer: Renderer2,\n private element: ElementRef,\n ) {}\n\n ngOnChanges(changes: ISimpleChanges): void {\n if (changes.isExpanded?.currentValue && !changes.isExpanded?.previousValue) {\n this.trySetButtonsFixedHeight();\n }\n }\n\n private trySetButtonsFixedHeight(): void {\n const buttons = this.element.nativeElement.querySelectorAll('.toolbar-button') as HTMLElement[];\n const toolbarComponent = this.element.nativeElement.closest('.my-bets-toolbar');\n let allButtonsWidth = 0;\n\n for (const button of buttons) {\n allButtonsWidth += button.offsetWidth;\n }\n\n if (allButtonsWidth > toolbarComponent.offsetWidth * this.MAX_BUTTONS_CONTAINER_WIDTH) {\n buttons.forEach((b) => this.renderer.addClass(b, 'fixed-height'));\n }\n }\n}\n","import { NgModule } from '@angular/core';\n\nimport { ClassAutoRemoveDirective } from './class-auto-remove.directive';\nimport { ColumnWidthDirective } from './column-width.directive';\nimport { HeightToBottomDirective } from './height-to-bottom.directive';\nimport { InfoTooltipDirective } from './info-tooltip.directive';\nimport { OutsideInteractionDirective } from './outside-interaction.directive';\nimport { ScrollIosHackDirective } from './scroll-ios-hack/scroll-ios-hack.directive';\nimport { ScrollSelectedInViewDirective } from './scroll-selected-in-view/scroll-selected-in-view.directive';\nimport { ScrollToDirective } from './scroll-to.directive';\nimport { ScrolledToBottomDirective } from './scrolled-to-bottom.directive';\nimport { StopEventPropagationDirective } from './stop-event-propagation.directive';\nimport { ToolbarButtonsHeightDirective } from './toolbar-buttons-height.directive';\n\n@NgModule({\n imports: [ScrollSelectedInViewDirective],\n declarations: [\n ClassAutoRemoveDirective,\n ColumnWidthDirective,\n HeightToBottomDirective,\n InfoTooltipDirective,\n OutsideInteractionDirective,\n ScrolledToBottomDirective,\n ScrollIosHackDirective,\n ScrollToDirective,\n StopEventPropagationDirective,\n ToolbarButtonsHeightDirective,\n ],\n exports: [\n ClassAutoRemoveDirective,\n ColumnWidthDirective,\n HeightToBottomDirective,\n InfoTooltipDirective,\n OutsideInteractionDirective,\n ScrolledToBottomDirective,\n ScrollIosHackDirective,\n ScrollToDirective,\n StopEventPropagationDirective,\n ToolbarButtonsHeightDirective,\n ScrollSelectedInViewDirective,\n ],\n // needs to be fixed after module reordering\n})\nexport class DirectivesModule {}\n","import { Attribute, DestroyRef, Directive, ElementRef, Input, NgZone, OnInit, inject } from '@angular/core';\nimport { takeUntilDestroyed } from '@angular/core/rxjs-interop';\n\nimport { SmoothScroll } from '@frontend/sports/common/core/feature/smooth-scroll';\nimport { TimerService } from '@frontend/sports/common/core/utils/timer';\nimport { DeviceService } from '@frontend/vanilla/core';\n\ninterface ViewportPosition {\n scrollLeft: number;\n width: number;\n}\n\ntype Mode = 'center' | 'next-item';\n\n@Directive({\n selector: '[msScrollSelectedInView]',\n standalone: true,\n})\nexport class ScrollSelectedInViewDirective implements OnInit {\n private lastSelectedItem?: string;\n private readonly destroyRef = inject(DestroyRef);\n\n constructor(\n private element: ElementRef,\n private smoothScroll: SmoothScroll,\n private timerService: TimerService,\n private deviceService: DeviceService,\n private ngZone: NgZone,\n @Attribute('mode') private mode: Mode,\n ) {\n this.mode = this.mode || 'center';\n }\n\n @Input('msScrollSelectedInView')\n set target(value: string) {\n this.ngZone.runOutsideAngular(() => {\n this.timerService.setTimeout(() => this.selectedItemChangedHandler(value), 250);\n });\n }\n\n ngOnInit(): void {\n this.ngZone.runOutsideAngular(() => {\n this.deviceService.orientation.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(this.onOrientationChange);\n });\n }\n\n private selectedItemChangedHandler(value: string): void {\n if ((this.deviceService.isMobile && this.lastSelectedItem === value) || (!this.deviceService.isMobile && !value)) {\n return;\n }\n\n this.lastSelectedItem = value;\n\n const scrollableContainer = this.element.nativeElement.children[0];\n if (!scrollableContainer) {\n return;\n }\n\n const child = scrollableContainer.querySelector(`[data-menu-item-id=\"${value}\"]`);\n if (!child) {\n return;\n }\n\n const viewportPosition: ViewportPosition = {\n scrollLeft: scrollableContainer.scrollLeft,\n width: scrollableContainer.offsetWidth,\n };\n\n let scrollLeft = 0;\n if (this.mode === 'center') {\n scrollLeft = this.centerScroller(child, viewportPosition);\n } else {\n scrollLeft = this.nextItemScroller(child, viewportPosition);\n }\n\n this.smoothScroll.scrollLeftTo(scrollableContainer, scrollLeft, 350);\n }\n\n private centerScroller(child: HTMLElement, viewportPosition: ViewportPosition): number {\n const childPosition = this.elementViewportPosition(child, viewportPosition);\n const viewportMiddle = viewportPosition.width / 2;\n\n const childPositionMiddle = childPosition.scrollLeft + childPosition.width / 2;\n const scrollLeft = viewportPosition.scrollLeft + (childPositionMiddle - viewportMiddle);\n\n return Math.max(0, scrollLeft);\n }\n\n private nextItemScroller(child: HTMLElement, viewportPosition: ViewportPosition): number {\n const dontScroll = viewportPosition.scrollLeft;\n\n const nextElement = child.nextElementSibling;\n if (nextElement) {\n const nextPosition = this.elementViewportPosition(nextElement, viewportPosition);\n const nextScrollLeft = nextPosition.scrollLeft + nextPosition.width - viewportPosition.width;\n\n if (nextScrollLeft > 0) {\n return nextScrollLeft + viewportPosition.scrollLeft;\n }\n } else {\n return child.offsetLeft;\n }\n\n const prevElement = child.previousElementSibling;\n if (prevElement) {\n const prevPosition = this.elementViewportPosition(prevElement, viewportPosition);\n const prevScrollLeft = prevPosition.scrollLeft;\n\n if (prevScrollLeft < 0) {\n return prevScrollLeft + viewportPosition.scrollLeft;\n }\n } else {\n return 0;\n }\n\n return dontScroll;\n }\n\n private elementViewportPosition(element: HTMLElement, viewportPosition: ViewportPosition): ViewportPosition {\n return {\n scrollLeft: element.offsetLeft - viewportPosition.scrollLeft,\n width: element.offsetWidth,\n };\n }\n\n private onOrientationChange = () => {\n if (this.lastSelectedItem) {\n this.selectedItemChangedHandler(this.lastSelectedItem);\n }\n };\n}\n","import { ɵglobal as _global } from '@angular/core';\nfunction push(heap, node) {\n const index = heap.length;\n heap.push(node);\n siftUp(heap, node, index);\n}\nfunction peek(heap) {\n const first = heap[0];\n return first === undefined ? null : first;\n}\nfunction pop(heap) {\n const first = heap[0];\n if (first !== undefined) {\n const last = heap.pop();\n if (last !== first) {\n heap[0] = last;\n siftDown(heap, last, 0);\n }\n return first;\n } else {\n return null;\n }\n}\nfunction siftUp(heap, node, i) {\n let index = i;\n // eslint-disable-next-line no-constant-condition\n while (true) {\n const parentIndex = index - 1 >>> 1;\n const parent = heap[parentIndex];\n if (parent !== undefined && compare(parent, node) > 0) {\n // The parent is larger. Swap positions.\n heap[parentIndex] = node;\n heap[index] = parent;\n index = parentIndex;\n } else {\n // The parent is smaller. Exit.\n return;\n }\n }\n}\nfunction siftDown(heap, node, i) {\n let index = i;\n const length = heap.length;\n while (index < length) {\n const leftIndex = (index + 1) * 2 - 1;\n const left = heap[leftIndex];\n const rightIndex = leftIndex + 1;\n const right = heap[rightIndex];\n // If the left or right node is smaller, swap with the smaller of those.\n if (left !== undefined && compare(left, node) < 0) {\n if (right !== undefined && compare(right, left) < 0) {\n heap[index] = right;\n heap[rightIndex] = node;\n index = rightIndex;\n } else {\n heap[index] = left;\n heap[leftIndex] = node;\n index = leftIndex;\n }\n } else if (right !== undefined && compare(right, node) < 0) {\n heap[index] = right;\n heap[rightIndex] = node;\n index = rightIndex;\n } else {\n // Neither child is smaller. Exit.\n return;\n }\n }\n}\nfunction compare(a, b) {\n // Compare sort index first, then task id.\n const diff = a.sortIndex - b.sortIndex;\n return diff !== 0 ? diff : a.id - b.id;\n}\n\n// see https://github.com/facebook/react/blob/main/packages/scheduler/src/forks/Scheduler.js\nlet getCurrentTime;\nconst hasPerformanceNow = typeof _global.performance === 'object' && typeof _global.performance.now === 'function';\nif (hasPerformanceNow) {\n const localPerformance = _global.performance;\n getCurrentTime = () => localPerformance.now();\n} else {\n const localDate = Date;\n const initialTime = localDate.now();\n getCurrentTime = () => localDate.now() - initialTime;\n}\n// Max 31 bit integer. The max integer size in V8 for 32-bit systems.\n// Math.pow(2, 30) - 1\n// 0b111111111111111111111111111111\nconst maxSigned31BitInt = 1073741823;\n// Times out immediately\nconst IMMEDIATE_PRIORITY_TIMEOUT = -1;\n// Eventually times out\nconst USER_BLOCKING_PRIORITY_TIMEOUT = 250;\nconst NORMAL_PRIORITY_TIMEOUT = 5000;\nconst LOW_PRIORITY_TIMEOUT = 10000;\n// Never times out\nconst IDLE_PRIORITY_TIMEOUT = maxSigned31BitInt;\n// Tasks are stored on a min heap\nconst taskQueue = [];\nconst timerQueue = [];\n// Incrementing id counter. Used to maintain insertion order.\nlet taskIdCounter = 1;\nlet currentTask = null;\nlet currentPriorityLevel = 3 /* PriorityLevel.NormalPriority */;\n// This is set while performing work, to prevent re-entrancy.\nlet isPerformingWork = false;\nlet isHostCallbackScheduled = false;\nlet isHostTimeoutScheduled = false;\n// Capture local references to native APIs, in case a polyfill overrides them.\nconst setTimeout = _global.setTimeout;\nconst clearTimeout = _global.clearTimeout;\nconst setImmediate = _global.setImmediate; // IE and Node.js + jsdom\nconst messageChannel = _global.MessageChannel;\nconst defaultZone = {\n run: fn => fn()\n};\nfunction advanceTimers(currentTime) {\n // Check for tasks that are no longer delayed and add them to the queue.\n let timer = peek(timerQueue);\n while (timer !== null) {\n if (timer.callback === null) {\n // Timer was cancelled.\n pop(timerQueue);\n } else if (timer.startTime <= currentTime) {\n // Timer fired. Transfer to the task queue.\n pop(timerQueue);\n timer.sortIndex = timer.expirationTime;\n push(taskQueue, timer);\n } else {\n // Remaining timers are pending.\n return;\n }\n timer = peek(timerQueue);\n }\n}\nfunction handleTimeout(currentTime) {\n isHostTimeoutScheduled = false;\n advanceTimers(currentTime);\n if (!isHostCallbackScheduled) {\n if (peek(taskQueue) !== null) {\n isHostCallbackScheduled = true;\n requestHostCallback(flushWork);\n } else {\n const firstTimer = peek(timerQueue);\n if (firstTimer !== null) {\n requestHostTimeout(handleTimeout, firstTimer.startTime - currentTime);\n }\n }\n }\n}\nfunction flushWork(hasTimeRemaining, initialTime) {\n // We'll need a host callback the next time work is scheduled.\n isHostCallbackScheduled = false;\n if (isHostTimeoutScheduled) {\n // We scheduled a timeout but it's no longer needed. Cancel it.\n isHostTimeoutScheduled = false;\n cancelHostTimeout();\n }\n isPerformingWork = true;\n const previousPriorityLevel = currentPriorityLevel;\n try {\n return workLoop(hasTimeRemaining, initialTime);\n } finally {\n currentTask = null;\n currentPriorityLevel = previousPriorityLevel;\n isPerformingWork = false;\n }\n}\nfunction workLoop(hasTimeRemaining, initialTime, _currentTask) {\n let currentTime = initialTime;\n if (_currentTask) {\n currentTask = _currentTask;\n } else {\n advanceTimers(currentTime);\n currentTask = peek(taskQueue);\n }\n let zoneChanged = false;\n const hitDeadline = () => currentTask && currentTask.expirationTime > currentTime && (!hasTimeRemaining || shouldYieldToHost());\n if (!hitDeadline()) {\n const ngZone = currentTask.ngZone || defaultZone;\n ngZone.run(() => {\n while (currentTask !== null && !zoneChanged) {\n if (hitDeadline()) {\n break;\n }\n const callback = currentTask.callback;\n if (typeof callback === 'function') {\n currentTask.callback = null;\n currentPriorityLevel = currentTask.priorityLevel;\n const didUserCallbackTimeout = currentTask.expirationTime <= currentTime;\n const continuationCallback = callback(didUserCallbackTimeout);\n currentTime = getCurrentTime();\n if (typeof continuationCallback === 'function') {\n currentTask.callback = continuationCallback;\n } else {\n if (currentTask === peek(taskQueue)) {\n pop(taskQueue);\n }\n }\n advanceTimers(currentTime);\n } else {\n pop(taskQueue);\n }\n currentTask = peek(taskQueue);\n zoneChanged = currentTask?.ngZone != null && currentTask.ngZone !== ngZone;\n }\n });\n }\n // we need to check if leaving `NgZone` (tick => detectChanges) caused other\n // directives to add tasks to the queue. If there is one and we still didn't\n // hit the deadline, run the workLoop again in order to flush everything thats\n // left.\n // Otherwise, newly added tasks won't run as `performingWork` is still `true`\n currentTask = currentTask ?? peek(taskQueue);\n // We should also re-calculate the currentTime, as we need to account for the execution\n // time of the NgZone tasks as well.\n // If there is still a task in the queue, but no time is left for executing it,\n // the scheduler will re-schedule the next tick anyway\n currentTime = getCurrentTime();\n if (zoneChanged || currentTask && !hitDeadline()) {\n return workLoop(hasTimeRemaining, currentTime, currentTask);\n }\n // Return whether there's additional work\n if (currentTask !== null) {\n return true;\n } else {\n const firstTimer = peek(timerQueue);\n if (firstTimer !== null) {\n requestHostTimeout(handleTimeout, firstTimer.startTime - currentTime);\n }\n return false;\n }\n}\nfunction scheduleCallback(priorityLevel, callback, options) {\n const currentTime = getCurrentTime();\n let startTime;\n if (typeof options === 'object' && options !== null) {\n const delay = options.delay;\n if (typeof delay === 'number' && delay > 0) {\n startTime = currentTime + delay;\n } else {\n startTime = currentTime;\n }\n } else {\n startTime = currentTime;\n }\n let timeout;\n switch (priorityLevel) {\n case 1 /* PriorityLevel.ImmediatePriority */:\n timeout = IMMEDIATE_PRIORITY_TIMEOUT;\n break;\n case 2 /* PriorityLevel.UserBlockingPriority */:\n timeout = USER_BLOCKING_PRIORITY_TIMEOUT;\n break;\n case 5 /* PriorityLevel.IdlePriority */:\n timeout = IDLE_PRIORITY_TIMEOUT;\n break;\n case 4 /* PriorityLevel.LowPriority */:\n timeout = LOW_PRIORITY_TIMEOUT;\n break;\n case 3 /* PriorityLevel.NormalPriority */:\n default:\n timeout = NORMAL_PRIORITY_TIMEOUT;\n break;\n }\n const expirationTime = startTime + timeout;\n const newTask = {\n id: taskIdCounter++,\n callback,\n priorityLevel,\n startTime,\n expirationTime,\n sortIndex: -1,\n ngZone: options?.ngZone || null\n };\n if (startTime > currentTime) {\n // This is a delayed task.\n newTask.sortIndex = startTime;\n push(timerQueue, newTask);\n if (peek(taskQueue) === null && newTask === peek(timerQueue)) {\n // All tasks are delayed, and this is the task with the earliest delay.\n if (isHostTimeoutScheduled) {\n // Cancel an existing timeout.\n cancelHostTimeout();\n } else {\n isHostTimeoutScheduled = true;\n }\n // Schedule a timeout.\n requestHostTimeout(handleTimeout, startTime - currentTime);\n }\n } else {\n newTask.sortIndex = expirationTime;\n push(taskQueue, newTask);\n // Schedule a host callback, if needed. If we're already performing work,\n // wait until the next time we yield.\n if (!isHostCallbackScheduled && !isPerformingWork) {\n isHostCallbackScheduled = true;\n requestHostCallback(flushWork);\n }\n }\n return newTask;\n}\nfunction cancelCallback(task) {\n // Null out the callback to indicate the task has been canceled. (Can't\n // remove from the queue because you can't remove arbitrary nodes from an\n // array based heap, only the first one.)\n task.callback = null;\n}\nlet isMessageLoopRunning = false;\nlet scheduledHostCallback = null;\nlet taskTimeoutID = -1;\n// Scheduler periodically yields in case there is other work on the main\n// thread, like user events. By default, it yields multiple times per frame.\n// It does not attempt to align with frame boundaries, since most tasks don't\n// need to be frame aligned; for those that do, use requestAnimationFrame.\nlet yieldInterval = 16;\nlet needsPaint = false;\nlet queueStartTime = -1;\nfunction shouldYieldToHost() {\n if (needsPaint) {\n // There's a pending paint (signaled by `requestPaint`). Yield now.\n return true;\n }\n const timeElapsed = getCurrentTime() - queueStartTime;\n if (timeElapsed < yieldInterval) {\n // The main thread has only been blocked for a really short amount of time;\n // smaller than a single frame. Don't yield yet.\n return false;\n }\n // `isInputPending` isn't available. Yield now.\n return true;\n}\nfunction requestPaint() {\n needsPaint = true;\n}\nfunction forceFrameRate(fps) {\n if (fps < 0 || fps > 125) {\n if (typeof ngDevMode === 'undefined' || ngDevMode) {\n console.error('forceFrameRate takes a positive int between 0 and 125, ' + 'forcing frame rates higher than 125 fps is not supported');\n }\n return;\n }\n if (fps > 0) {\n yieldInterval = Math.floor(1000 / fps);\n } else {\n // reset the framerate\n yieldInterval = 5;\n }\n // be aware of browser housekeeping work (~6ms per frame)\n // according to https://developers.google.com/web/fundamentals/performance/rendering\n yieldInterval = Math.max(5, yieldInterval - 6);\n}\nconst performWorkUntilDeadline = () => {\n if (scheduledHostCallback !== null) {\n const currentTime = getCurrentTime();\n // Yield after `yieldInterval` ms, regardless of where we are in the vsync\n // cycle. This means there's always time remaining at the beginning of\n // the message event.\n queueStartTime = currentTime;\n const hasTimeRemaining = true;\n // If a scheduler task throws, exit the current browser task so the\n // error can be observed.\n //\n // Intentionally not using a try-catch, since that makes some debugging\n // techniques harder. Instead, if `scheduledHostCallback` errors, then\n // `hasMoreWork` will remain true, and we'll continue the work loop.\n let hasMoreWork = true;\n try {\n hasMoreWork = scheduledHostCallback(hasTimeRemaining, currentTime);\n } finally {\n if (hasMoreWork) {\n // If there's more work, schedule the next message event at the end\n // of the preceding one.\n schedulePerformWorkUntilDeadline();\n } else {\n isMessageLoopRunning = false;\n scheduledHostCallback = null;\n }\n }\n } else {\n isMessageLoopRunning = false;\n }\n // Yielding to the browser will give it a chance to paint, so we can\n // reset this.\n needsPaint = false;\n};\nlet schedulePerformWorkUntilDeadline;\nif (typeof setImmediate === 'function') {\n // Node.js and old IE.\n // There's a few reasons for why we prefer setImmediate.\n //\n // Unlike MessageChannel, it doesn't prevent a Node.js process from exiting.\n // (Even though this is a DOM fork of the Scheduler, you could get here\n // with a mix of Node.js 15+, which has a MessageChannel, and jsdom.)\n // https://github.com/facebook/react/issues/20756\n //\n // But also, it runs earlier which is the semantic we want.\n // If other browsers ever implement it, it's better to use it.\n // Although both of these would be inferior to native scheduling.\n schedulePerformWorkUntilDeadline = () => {\n setImmediate(performWorkUntilDeadline);\n };\n} else if (typeof messageChannel !== 'undefined') {\n const channel = new messageChannel();\n const port = channel.port2;\n channel.port1.onmessage = performWorkUntilDeadline;\n schedulePerformWorkUntilDeadline = () => {\n port.postMessage(null);\n };\n} else {\n // We should only fallback here in non-browser environments.\n schedulePerformWorkUntilDeadline = () => {\n setTimeout(performWorkUntilDeadline, 0);\n };\n}\nfunction requestHostCallback(callback) {\n scheduledHostCallback = callback;\n if (!isMessageLoopRunning) {\n isMessageLoopRunning = true;\n schedulePerformWorkUntilDeadline();\n }\n}\nfunction requestHostTimeout(callback, ms) {\n taskTimeoutID = setTimeout(() => {\n callback(getCurrentTime());\n }, ms);\n}\nfunction cancelHostTimeout() {\n clearTimeout(taskTimeoutID);\n taskTimeoutID = -1;\n}\n\n/**\n * Generated bundle index. Do not edit.\n */\n\nexport { cancelCallback, forceFrameRate, scheduleCallback };\n","import { isObservable, of, Subject } from 'rxjs';\nimport { map, switchAll, distinctUntilChanged } from 'rxjs/operators';\n\n/**\n * This Observable factory creates an Observable out of a static value or an Observable.\n *\n * @param o - the value to coerce\n */\nfunction coerceObservable(o) {\n return isObservable(o) ? o : of(o);\n}\n\n/**\n * This operator maps an Observable out of a static value or an Observable.\n *\n */\nfunction coerceObservableWith() {\n return o$ => map(coerceObservable)(o$);\n}\n\n/**\n * This operator takes an Observable of values ot Observables aof values and\n * It forwards only distinct values from distinct incoming Observables or values.\n * This comes in handy in any environment where you handle processing of incoming dynamic values and their state.\n *\n * Optionally you can pass a flatten strategy to get find grained control of the flattening process. E.g. mergeAll, switchAll\n *\n * @param flattenOperator - determines the flattening strategy e.g. mergeAll, concatAll, exhaust, switchAll. default is switchAll\n *\n */\nfunction coerceDistinctWith(flattenOperator) {\n flattenOperator = flattenOperator || switchAll();\n return o$ => o$.pipe(coerceObservableWith(), distinctUntilChanged(), flattenOperator, distinctUntilChanged());\n}\n\n/**\n * A factory function returning an object to handle the process of merging Observable next notifications into one\n * Observable. This API takes away the clumsy handling of static values and Observable, reduces the number of\n * emissions by:\n * - only merging distinct Observables\n * - only emit distinct values of the merged result\n *\n * You can next a Observable of `U` multiple times and merge them into the Observable exposed under one optimized\n * `values$`\n *\n */\nfunction coerceAllFactory(subjectFactory, flattenOperator) {\n const observablesSubject = subjectFactory ? subjectFactory() : new Subject();\n flattenOperator = flattenOperator || switchAll();\n const values$ = observablesSubject.pipe(coerceDistinctWith(flattenOperator));\n return {\n next(observable) {\n observablesSubject.next(observable);\n },\n values$\n };\n}\n\n/**\n * This Observable factory creates an Observable out of a static value or an Observable.\n * It forwards only distinct values from distinct incoming Observables or values.\n * This comes in handy in any environment where you handle processing of incoming dynamic values and their state.\n *\n * Optionally you can pass a flatten strategy to get find grained control of the flattening process. E.g. mergeAll, switchAll\n *\n * @param o$ - The Observable to coerce and map to a Observable with distinct values\n * @param flattenOperator - determines the flattening strategy e.g. mergeAll, concatAll, exhaust, switchAll. default is switchAll\n */\nfunction coerceDistinctObservable(o$, flattenOperator) {\n flattenOperator = flattenOperator || switchAll();\n return coerceObservable(o$).pipe(distinctUntilChanged(), flattenOperator, distinctUntilChanged());\n}\n\n/**\n * Generated bundle index. Do not edit.\n */\n\nexport { coerceAllFactory, coerceDistinctObservable, coerceDistinctWith, coerceObservable, coerceObservableWith };\n","import { coalescingManager, coalesceWith } from '@rx-angular/cdk/coalescing';\nimport { forceFrameRate, scheduleCallback, cancelCallback } from '@rx-angular/cdk/internals/scheduler';\nimport { Observable, throwError, ReplaySubject, BehaviorSubject, fromEvent } from 'rxjs';\nimport { filter, switchMap, mapTo, tap, catchError, map, take, switchAll, startWith, share, shareReplay, takeUntil } from 'rxjs/operators';\nimport * as i0 from '@angular/core';\nimport { NgZone, InjectionToken, Injectable, Optional, Inject } from '@angular/core';\nimport { getZoneUnPatchedApi } from '@rx-angular/cdk/internals/core';\nimport { coerceAllFactory } from '@rx-angular/cdk/coercing';\n\n// set default to 60fps\nforceFrameRate(60);\nconst immediateStrategy = {\n name: 'immediate',\n work: cdRef => cdRef.detectChanges(),\n behavior: ({\n work,\n scope,\n ngZone\n }) => {\n return o$ => o$.pipe(scheduleOnQueue(work, {\n ngZone,\n priority: 1 /* PriorityLevel.ImmediatePriority */,\n scope\n }));\n }\n};\nconst userBlockingStrategy = {\n name: 'userBlocking',\n work: cdRef => cdRef.detectChanges(),\n behavior: ({\n work,\n scope,\n ngZone\n }) => {\n return o$ => o$.pipe(scheduleOnQueue(work, {\n ngZone,\n priority: 2 /* PriorityLevel.UserBlockingPriority */,\n scope\n }));\n }\n};\nconst normalStrategy = {\n name: 'normal',\n work: cdRef => cdRef.detectChanges(),\n behavior: ({\n work,\n scope,\n ngZone\n }) => {\n return o$ => o$.pipe(scheduleOnQueue(work, {\n ngZone,\n priority: 3 /* PriorityLevel.NormalPriority */,\n scope\n }));\n }\n};\nconst lowStrategy = {\n name: 'low',\n work: cdRef => cdRef.detectChanges(),\n behavior: ({\n work,\n scope,\n ngZone\n }) => {\n return o$ => o$.pipe(scheduleOnQueue(work, {\n ngZone,\n priority: 4 /* PriorityLevel.LowPriority */,\n scope\n }));\n }\n};\nconst idleStrategy = {\n name: 'idle',\n work: cdRef => cdRef.detectChanges(),\n behavior: ({\n work,\n scope,\n ngZone\n }) => {\n return o$ => o$.pipe(scheduleOnQueue(work, {\n ngZone,\n priority: 5 /* PriorityLevel.IdlePriority */,\n scope\n }));\n }\n};\nfunction scheduleOnQueue(work, options) {\n const scope = options.scope || {};\n return o$ => o$.pipe(filter(() => !coalescingManager.isCoalescing(scope)), switchMap(v => new Observable(subscriber => {\n coalescingManager.add(scope);\n const task = scheduleCallback(options.priority, () => {\n work();\n coalescingManager.remove(scope);\n subscriber.next(v);\n }, {\n delay: options.delay,\n ngZone: options.ngZone\n });\n return () => {\n coalescingManager.remove(scope);\n cancelCallback(task);\n };\n }).pipe(mapTo(v))));\n}\nconst RX_CONCURRENT_STRATEGIES = {\n immediate: immediateStrategy,\n userBlocking: userBlockingStrategy,\n normal: normalStrategy,\n low: lowStrategy,\n idle: idleStrategy\n};\nconst animationFrameTick = () => new Observable(subscriber => {\n // use the unpatched API no avoid zone interference\n const id = getZoneUnPatchedApi('requestAnimationFrame')(() => {\n subscriber.next(0);\n subscriber.complete();\n });\n return () => {\n // use the unpatched API no avoid zone interference\n getZoneUnPatchedApi('cancelAnimationFrame')(id);\n };\n});\nconst localCredentials = {\n name: 'local',\n work: (cdRef, _, notification) => {\n cdRef.detectChanges();\n },\n behavior: ({\n work,\n scope,\n ngZone\n }) => o$ => o$.pipe(coalesceWith(animationFrameTick(), scope), tap(() => ngZone ? ngZone.run(() => work()) : work()))\n};\nconst noopCredentials = {\n name: 'noop',\n work: () => void 0,\n behavior: () => o$ => o$\n};\nconst nativeCredentials = {\n name: 'native',\n work: cdRef => cdRef.markForCheck(),\n behavior: ({\n work,\n ngZone\n }) => o$ => o$.pipe(tap(() => ngZone && !NgZone.isInAngularZone() ? ngZone.run(() => work()) : work()))\n};\nconst RX_NATIVE_STRATEGIES = {\n native: nativeCredentials,\n noop: noopCredentials,\n local: localCredentials\n};\nconst RX_RENDER_STRATEGIES_CONFIG = new InjectionToken('rxa-render-strategies-config');\nconst RX_RENDER_STRATEGIES_DEFAULTS = {\n primaryStrategy: 'normal',\n customStrategies: {\n ...RX_NATIVE_STRATEGIES,\n ...RX_CONCURRENT_STRATEGIES\n },\n patchZone: true,\n parent: false\n};\nfunction mergeDefaultConfig(cfg) {\n const custom = cfg ? cfg : {\n customStrategies: {}\n };\n return {\n ...RX_RENDER_STRATEGIES_DEFAULTS,\n ...custom,\n customStrategies: {\n ...custom.customStrategies,\n ...RX_RENDER_STRATEGIES_DEFAULTS.customStrategies\n }\n };\n}\n\n/**\n * @internal\n *\n * @param value\n * @param strategy\n * @param workFactory\n * @param options\n */\nfunction onStrategy(value, strategy, workFactory, options = {}) {\n return new Observable(subscriber => {\n subscriber.next(value);\n }).pipe(strategy.behavior({\n work: () => workFactory(value, strategy.work, options),\n scope: options.scope || {},\n ngZone: options.ngZone\n }), catchError(error => throwError(() => [error, value])), map(() => value), take(1));\n}\n\n/**\n * @internal\n *\n * A factory function returning an object to handle the process of turning strategy names into `RxStrategyCredentials`\n * You can next a strategy name as Observable or string and get an Observable of `RxStrategyCredentials`\n *\n * @param defaultStrategyName\n * @param strategies\n */\nfunction strategyHandling(defaultStrategyName, strategies) {\n const hotFlattened = coerceAllFactory(() => new ReplaySubject(1), switchAll());\n return {\n strategy$: hotFlattened.values$.pipe(startWith(defaultStrategyName), nameToStrategyCredentials(strategies, defaultStrategyName), share()),\n next(name) {\n hotFlattened.next(name);\n }\n };\n}\n/**\n * @internal\n */\nfunction nameToStrategyCredentials(strategies, defaultStrategyName) {\n return o$ => o$.pipe(map(name => name && Object.keys(strategies).includes(name) ? strategies[name] : strategies[defaultStrategyName]));\n}\n\n/**\n * @description\n * RxStrategyProvider is a wrapper service that you can use to consume strategies and schedule your code execution.\n *\n * @example\n * Component({\n * selector: 'app-service-communicator',\n * template: ``\n * });\n * export class ServiceCommunicationComponent {\n * private currentUserSettings;\n *\n * constructor(\n * private strategyProvider: RxStrategyProvider,\n * private userService: UserService,\n * private backgroundSync: BackgroundSyncService\n * ) {\n * this.userService.fetchCurrentUserSettings\n * .pipe(\n * tap(settings => (this.currentUserSettings = settings)),\n * this.strategyProvider.scheduleWith(\n * settings => this.backgroundSync.openConnection(settings),\n * { strategy: 'idle' }\n * )\n * )\n * .subscribe();\n * }\n * }\n *\n * @docsCategory RxStrategyProvider\n * @docsPage RxStrategyProvider\n */\nlet RxStrategyProvider = /*#__PURE__*/(() => {\n class RxStrategyProvider {\n _strategies$ = new BehaviorSubject(undefined);\n _primaryStrategy$ = new BehaviorSubject(undefined);\n _cfg;\n /**\n * @description\n * Returns current `RxAngularConfig` used in the service.\n * Config includes:\n * - strategy that currently in use - `primaryStrategy`\n * - array of custom user defined strategies - `customStrategies`\n * - setting that is responsible for running in our outside of the zone.js - `patchZone`\n */\n get config() {\n return this._cfg;\n }\n /**\n * @description\n * Returns object that contains key-value pairs of strategy names and their credentials (settings) that are available in the service.\n */\n get strategies() {\n return this._strategies$.getValue();\n }\n /**\n * @description\n * Returns an array of strategy names available in the service.\n */\n get strategyNames() {\n return Object.values(this.strategies).map(s => s.name);\n }\n /**\n * @description\n * Returns current strategy of the service.\n */\n get primaryStrategy() {\n return this._primaryStrategy$.getValue().name;\n }\n /**\n * @description\n * Set's the strategy that will be used by the service.\n */\n set primaryStrategy(strategyName) {\n this._primaryStrategy$.next(this.strategies[strategyName]);\n }\n /**\n * @description\n * Current strategy of the service as an observable.\n */\n primaryStrategy$ = this._primaryStrategy$.asObservable();\n /**\n * @description\n * Returns observable of an object that contains key-value pairs of strategy names and their credentials (settings) that are available in the service.\n */\n strategies$ = this._strategies$.asObservable();\n /**\n * @description\n * Returns an observable of an array of strategy names available in the service.\n */\n strategyNames$ = this.strategies$.pipe(map(strategies => Object.values(strategies).map(s => s.name)), shareReplay({\n bufferSize: 1,\n refCount: true\n }));\n /**\n * @internal\n */\n constructor(cfg) {\n this._cfg = mergeDefaultConfig(cfg);\n this._strategies$.next(this._cfg.customStrategies);\n this.primaryStrategy = this.config.primaryStrategy;\n }\n /**\n * @description\n * Allows to schedule a work inside rxjs `pipe`. Accepts the work and configuration options object.\n * - work is any function that should be executed\n * - (optional) options includes strategy, patchZone and scope\n *\n * Scope is by default a subscription but you can also pass `this` and then the scope will be current component.\n * Scope setup is useful if your work is some of the methods of `ChangeDetectorRef`. Only one change detection will be triggered if you have multiple schedules of change detection methods and scope is set to `this`.\n *\n * @example\n * myObservable$.pipe(\n * this.strategyProvider.scheduleWith(() => myWork(), {strategy: 'idle', patchZone: false})\n * ).subscribe();\n *\n * @return MonoTypeOperatorFunction\n */\n scheduleWith(work, options) {\n const strategy = this.strategies[options?.strategy || this.primaryStrategy];\n const scope = options?.scope || {};\n const _work = getWork(work, options?.patchZone);\n const ngZone = options?.patchZone || undefined;\n return o$ => o$.pipe(switchMap(v => onStrategy(v, strategy, _v => {\n _work(_v);\n }, {\n scope,\n ngZone\n })));\n }\n /**\n * @description\n * Allows to schedule a work as an observable. Accepts the work and configuration options object.\n * - work is any function that should be executed\n * - (optional) options includes strategy, patchZone and scope\n *\n * Scope is by default a subscription but you can also pass `this` and then the scope will be current component.\n * Scope setup is especially useful if you provide work that will trigger a change detection.\n *\n * @example\n * this.strategyProvider.schedule(() => myWork(), {strategy: 'idle', patchZone: false}).subscribe();\n *\n * @return Observable\n */\n schedule(work, options) {\n const strategy = this.strategies[options?.strategy || this.primaryStrategy];\n const scope = options?.scope || {};\n const _work = getWork(work, options?.patchZone);\n const ngZone = options?.patchZone || undefined;\n let returnVal;\n return onStrategy(null, strategy, () => {\n returnVal = _work();\n }, {\n scope,\n ngZone\n }).pipe(map(() => returnVal));\n }\n /**\n * @description\n * Allows to schedule a change detection cycle. Accepts the ChangeDetectorRef and configuration options object.\n * Options include:\n * - afterCD which is the work that should be executed after change detection cycle.\n * - abortCtrl is an AbortController that you can use to cancel the scheduled cycle.\n *\n * @example\n * this.strategyProvider.scheduleCd(this.changeDetectorRef, {afterCD: myWork()});\n *\n * @return AbortController\n */\n scheduleCD(cdRef, options) {\n const strategy = this.strategies[options?.strategy || this.primaryStrategy];\n const scope = options?.scope || cdRef;\n const abC = options?.abortCtrl || new AbortController();\n const ngZone = options?.patchZone || undefined;\n const work = getWork(() => {\n strategy.work(cdRef, scope);\n if (options?.afterCD) {\n options.afterCD();\n }\n }, options.patchZone);\n onStrategy(null, strategy, () => {\n work();\n }, {\n scope,\n ngZone\n }).pipe(takeUntil(fromEvent(abC.signal, 'abort'))).subscribe();\n return abC;\n }\n /** @nocollapse */\n static ɵfac = function RxStrategyProvider_Factory(__ngFactoryType__) {\n return new (__ngFactoryType__ || RxStrategyProvider)(i0.ɵɵinject(RX_RENDER_STRATEGIES_CONFIG, 8));\n };\n /** @nocollapse */\n static ɵprov = /* @__PURE__ */i0.ɵɵdefineInjectable({\n token: RxStrategyProvider,\n factory: RxStrategyProvider.ɵfac,\n providedIn: 'root'\n });\n }\n return RxStrategyProvider;\n})();\n(() => {\n (typeof ngDevMode === \"undefined\" || ngDevMode) && void 0;\n})();\nfunction getWork(work, patchZone) {\n let _work = work;\n if (patchZone) {\n _work = args => patchZone.run(() => work(args));\n }\n return _work;\n}\n\n/**\n * Generated bundle index. Do not edit.\n */\n\nexport { RX_CONCURRENT_STRATEGIES, RX_NATIVE_STRATEGIES, RX_RENDER_STRATEGIES_CONFIG, RxStrategyProvider, onStrategy, strategyHandling };\n","\n \n\n\n\n
    \n
  • \n \n \n \n \n \n \n \n \n
  • \n
\n
\n\n\n \n {{ badge.title }}\n\n
\n\n \n \n \n\n \n \n {{ tab.title }}\n \n\n \n {{ tab.count }}\n \n\n","import { Component, EventEmitter, HostBinding, Input, NgZone, OnChanges, Output, SimpleChanges, TemplateRef, ViewChild, inject } from '@angular/core';\n\nimport { ScrollAdapterButtonSize, ScrollAdapterComponent } from '@frontend/sports/common/core/feature/scroll-adapter';\nimport { TimerService } from '@frontend/sports/common/core/utils/timer';\nimport { DeviceService } from '@frontend/vanilla/core';\nimport { RxStrategyProvider } from '@rx-angular/cdk/render-strategies';\n\nimport { RedirectHelperService } from '../navigation-core/redirect-helper.service';\nimport { TabBarEvent, TabBarItem } from './tab-bar.models';\n\n@Component({\n selector: 'ms-tab-bar',\n templateUrl: 'tab-bar.html',\n})\nexport class TabBarComponent implements OnChanges {\n @Input() tabs: TabBarItem[];\n @Input() scrollSize = 200;\n @Input() scrollEnabled = true;\n @Input() stretch = false;\n @Input() class = '';\n @Input() iconTemplate: TemplateRef;\n @Input() buttonSize = ScrollAdapterButtonSize.Small;\n @Output() select = new EventEmitter();\n @Input() signPostTemplate: TemplateRef;\n @Input() isNewFixture = false;\n @ViewChild(ScrollAdapterComponent) scrollAdapter: ScrollAdapterComponent;\n @Output() arrowSelect = new EventEmitter<'left' | 'right'>();\n\n @HostBinding('class')\n get componentClass(): string {\n return `${this.hostClass} ${this.class}`.trim();\n }\n\n get hostClass(): string {\n return 'tab-bar';\n }\n\n get containerClass(): string {\n return 'tab-bar-container';\n }\n\n get itemClass(): string {\n return 'tab-bar-item';\n }\n\n get active(): TabBarItem | undefined {\n return this.tabs && this.tabs.filter((tab) => tab.active).pop();\n }\n\n private ngZone = inject(NgZone);\n\n constructor(\n private timer: TimerService,\n private redirectHelperService: RedirectHelperService,\n private deviceService: DeviceService,\n private strategy: RxStrategyProvider,\n ) {}\n\n ngOnChanges(changes: SimpleChanges): void {\n if (this.scrollEnabled && changes.tabs && !changes.tabs.firstChange) {\n this.timer.setTimeout(() => this.scrollAdapter.setArrows());\n }\n if (this.isNewFixture) {\n this.scrollAdapter?.resetArrows();\n }\n }\n\n selected(event: Event, selected: TabBarItem): void {\n event.preventDefault();\n\n const canBeSelected = !selected.disabled && !selected.active;\n if (canBeSelected) {\n this.tabs.forEach((tab) => (tab.active = tab.id === selected.id));\n\n this.strategy\n .schedule(\n () => {\n this.select.emit({ ...event, active: selected });\n\n const shouldNavigate = selected.url && !this.shouldNavigateByUrl(selected);\n if (shouldNavigate) {\n this.redirectHelperService.goToPage(selected.url, selected.ignoreBrowserHistory);\n }\n },\n { scope: this, patchZone: this.ngZone },\n )\n .subscribe();\n }\n }\n\n shouldNavigateByUrl(tab: TabBarItem): boolean {\n return Boolean(tab.url && (this.deviceService.isRobot || !tab.ignoreBrowserHistory));\n }\n\n trackTab(index: number, tab: TabBarItem): number {\n return tab.id;\n }\n\n tabClass(tab: TabBarItem): string {\n let classes = this.itemClass;\n\n if (tab.classes) {\n classes += ` ${tab.classes}`;\n }\n\n if (tab.active) {\n classes += ' active';\n }\n\n if (tab.disabled) {\n classes += ' disabled';\n }\n\n return classes;\n }\n\n tabFormatted(text: string): boolean {\n return text?.includes('<');\n }\n\n track(event: any) {\n this.arrowSelect.emit(event);\n }\n}\n","import { CommonModule } from '@angular/common';\nimport { NgModule } from '@angular/core';\n\nimport { AutomationModule } from '@frontend/sports/automation/core-feature';\nimport { ScrollAdapterComponent } from '@frontend/sports/common/core/feature/scroll-adapter';\nimport { PlainLinkComponent } from '@frontend/vanilla/core';\n\nimport { CrmPromotionModule } from '../crm-promotion/crm-promotion.module';\nimport { DirectivesModule } from '../directives/directives.module';\nimport { TabBarFontResizeDirective } from './tab-bar-font-resize.directive';\nimport { TabBarComponent } from './tab-bar.component';\n\n@NgModule({\n imports: [CommonModule, DirectivesModule, AutomationModule, CrmPromotionModule, PlainLinkComponent, ScrollAdapterComponent],\n exports: [TabBarComponent, TabBarFontResizeDirective],\n declarations: [TabBarComponent, TabBarFontResizeDirective],\n})\nexport class TabBarModule {}\n","import { isPlatformBrowser } from '@angular/common';\nimport { InjectionToken, PLATFORM_ID, Provider } from '@angular/core';\n\n/**\n * Injection token used for retrieving value indicating whether the browser is Safari.\n */\nexport const IS_SAFARI_BROWSER = new InjectionToken('safari-browser-token');\n\n// eslint-disable-next-line @typescript-eslint/ban-types\nexport function isSafariBrowserFactory(platformId: Object): boolean {\n if (isPlatformBrowser(platformId)) {\n const userAgent = window.navigator.userAgent.toLowerCase();\n return (userAgent.includes('safari') || userAgent.includes('applewebkit')) && !userAgent.includes('chrome');\n }\n return false;\n}\n\nexport function provideSafariBrowserToken(): Provider {\n return {\n provide: IS_SAFARI_BROWSER,\n useFactory: isSafariBrowserFactory,\n deps: [PLATFORM_ID],\n };\n}\n","import { DestroyRef, Directive, ElementRef, NgZone, OnInit, inject } from '@angular/core';\nimport { takeUntilDestroyed } from '@angular/core/rxjs-interop';\n\nimport { filter, fromEvent, tap } from 'rxjs';\n\nimport { IS_SAFARI_BROWSER } from './safari-browser-token.interface';\n\n@Directive({\n selector: '[dsTouchClick]',\n standalone: true,\n})\nexport class DsTouchClickDirective implements OnInit {\n private el = inject(ElementRef);\n private ngZone = inject(NgZone);\n private destroyRef = inject(DestroyRef);\n private isSafariBrowser = inject(IS_SAFARI_BROWSER);\n\n private touchStartElement: HTMLElement | null = null;\n\n private touchStarted$ = fromEvent(this.el.nativeElement, 'touchstart');\n private touchMoved$ = fromEvent(this.el.nativeElement, 'touchmove');\n private touchEnded$ = fromEvent(this.el.nativeElement, 'touchend');\n\n ngOnInit() {\n if (!this.isSafariBrowser) {\n return;\n }\n const clickTrigger = this.touchEnded$.pipe(\n filter(() => this.touchStartElement != null),\n tap((event: TouchEvent) => {\n if (this.touchStartElement != null && event.target === this.touchStartElement && event.changedTouches[0]) {\n const clickEvent = new MouseEvent('click', {\n bubbles: true,\n cancelable: true,\n view: window,\n clientX: event.changedTouches[0].clientX,\n clientY: event.changedTouches[0].clientY,\n });\n this.touchStartElement.dispatchEvent(clickEvent);\n event.preventDefault();\n this.touchStartElement = null;\n }\n }),\n );\n\n this.ngZone.runOutsideAngular(() => {\n this.touchStarted$\n .pipe(\n tap((event: Event) => (this.touchStartElement = event.target as HTMLElement)),\n takeUntilDestroyed(this.destroyRef),\n )\n .subscribe();\n\n this.touchMoved$\n .pipe(\n tap(() => (this.touchStartElement = null)),\n takeUntilDestroyed(this.destroyRef),\n )\n .subscribe();\n\n clickTrigger.pipe(takeUntilDestroyed(this.destroyRef)).subscribe();\n });\n }\n}\n","export interface FontResizerSettings {\n skipResize?: boolean;\n threshold?: number;\n minimumFontSize?: number;\n}\nexport abstract class FontResizerBaseService {\n abstract getSettings(valueLen: number): FontResizerSettings;\n}\n","{{ value }}\n{{ value }}\n","import { NgIf } from '@angular/common';\nimport { Component, EventEmitter, HostBinding, Input, OnChanges, OnInit, Optional, Output, SimpleChanges } from '@angular/core';\n\nimport { OddsConfig } from '@frontend/sports/common/client-config-data-access';\n\nimport { FontResizerBaseService, FontResizerSettings } from './font-resizer-base.service';\n\n@Component({\n selector: 'ms-font-resizer',\n templateUrl: 'font-resizer.html',\n standalone: true,\n imports: [NgIf],\n})\nexport class FontResizerComponent implements OnChanges, OnInit {\n // added to give a small space to text and borders\n private fontSizeThreshold = 0.03;\n private oldLength = 0;\n\n @Input() value: string;\n @Input() maxChars: number;\n @Input() defaultSize: number;\n @Input() shouldGetExtSettings?: boolean;\n @Input() shouldSkipCustomStyling?: boolean = false;\n @Output() fontChange = new EventEmitter();\n\n @HostBinding('style.font-size.em') fontSize?: number;\n\n private settings?: FontResizerSettings;\n shouldUseCustomStyle: boolean;\n\n constructor(\n @Optional() private fontResizerService: FontResizerBaseService | null,\n private oddsConfig: OddsConfig,\n ) {}\n\n ngOnInit(): void {\n this.shouldUseCustomStyle = this.oddsConfig.display.useCustomOddsFontStyle && !this.shouldSkipCustomStyling;\n }\n\n ngOnChanges(changes: SimpleChanges): void {\n const valueChange = changes['value'];\n\n if (!valueChange) {\n return;\n }\n\n if (valueChange.previousValue === valueChange.currentValue || this.shouldUseCustomStyle) {\n return;\n }\n\n if (this.shouldGetExtSettings) {\n // eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-unsafe-member-access\n this.settings = this.fontResizerService?.getSettings(valueChange.currentValue.length);\n }\n if (this.settings?.threshold !== undefined) {\n this.fontSizeThreshold = this.settings.threshold;\n }\n\n // eslint-disable-next-line @typescript-eslint/no-unsafe-argument\n this.calculateSize(valueChange.currentValue);\n }\n\n private calculateSize(value: string): void {\n if (!value) {\n this.oldLength = 0;\n\n return;\n }\n\n const valueLength = value.length;\n if (valueLength === this.oldLength) {\n return;\n }\n\n this.oldLength = valueLength;\n\n const newFontSize = this.getFontSize(valueLength);\n if (this.fontSize === newFontSize) {\n return;\n }\n\n this.fontSize = newFontSize;\n this.fontChange.emit(newFontSize);\n }\n\n private getFontSize(valueLength: number): number {\n const newFontSize =\n valueLength > this.getMaxChars() && !this.settings?.skipResize\n ? +((this.defaultSize * this.getMaxChars()) / valueLength - this.fontSizeThreshold).toFixed(2)\n : this.defaultSize;\n\n return Math.max(newFontSize, this.settings?.minimumFontSize || newFontSize);\n }\n\n private getMaxChars(): number {\n return this.maxChars - 1;\n }\n}\n","\n@if (showPriceBoostCount && count) {\n \n \n \n}\n","import { ChangeDetectionStrategy, Component, HostBinding, Input, OnInit } from '@angular/core';\n\nimport { PriceBoostConfig } from '@frontend/sports/common/client-config-data-access';\nimport { EpcotConfigService } from 'packages/sports/web/app/src/common/epcot-config.service';\n\n@Component({\n selector: 'ms-priceboost-count-signpost',\n templateUrl: './priceboost-count-signpost.component.html',\n changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class PriceboostCountSignpostComponent implements OnInit {\n @HostBinding('class') className = 'count-signpost-container';\n @HostBinding('class.large-signpost') largeSignpost = false;\n @HostBinding('class.count-signpost') countSignpost: boolean;\n @HostBinding('class.epcot-signpost')\n get shouldApplyClass(): boolean {\n return this.epcotConfigService.isEnabled();\n }\n\n @Input() count: string;\n @Input() isLargeSignPost = false;\n showPriceBoostCount = false;\n constructor(\n public priceBoostConfig: PriceBoostConfig,\n private epcotConfigService: EpcotConfigService,\n ) {}\n\n ngOnInit(): void {\n this.showPriceBoostCount = this.priceBoostConfig.showCount || this.isLargeSignPost;\n this.largeSignpost = this.isLargeSignPost;\n this.countSignpost = !this.isLargeSignPost;\n }\n}\n","import { CommonModule } from '@angular/common';\nimport { NgModule } from '@angular/core';\n\nimport { FontResizerComponent } from '@frontend/sports/common/core/feature/font-resizer';\n\nimport { PriceboostCountSignpostComponent } from './Components/priceboost-count-signpost.component';\n\n@NgModule({\n imports: [CommonModule, FontResizerComponent],\n declarations: [PriceboostCountSignpostComponent],\n exports: [PriceboostCountSignpostComponent],\n})\nexport class SharedPriceboostModule {}\n","import { Injectable, Pipe, PipeTransform } from '@angular/core';\nimport { DomSanitizer, SafeHtml, SafeResourceUrl, SafeStyle, SafeUrl } from '@angular/platform-browser';\n\n@Injectable({ providedIn: 'root' })\n@Pipe({\n name: 'msTrust',\n standalone: true,\n})\nexport class TrustPipe implements PipeTransform {\n constructor(private sanitizer: DomSanitizer) {}\n\n transform(value: string, type: 'html' | 'url' | 'style' | 'resourceurl'): SafeHtml | SafeStyle | SafeUrl | SafeResourceUrl {\n switch (type) {\n case 'html':\n return this.sanitizer.bypassSecurityTrustHtml(value);\n case 'url':\n return this.sanitizer.bypassSecurityTrustUrl(value);\n case 'style':\n return this.sanitizer.bypassSecurityTrustStyle(value);\n case 'resourceurl':\n return this.sanitizer.bypassSecurityTrustResourceUrl(value);\n default:\n throw new Error(`Can not trust type: ${type as string}`);\n }\n }\n}\n","@if (tabs) {\n
\n \n @for (tab of tabs; track tab.id) {\n \n \n @if (tab.icon) {\n @if (tab.display === display.Logo) {\n \n \n } @else {\n \n }\n }\n {{ tab.title }}\n @if (!!tab.count) {\n @if (tab.showSignPost) {\n \n } @else {\n \n {{ tab.count }}\n }\n }\n \n \n }\n \n \n
\n}\n","import { NgClass } from '@angular/common';\nimport { ChangeDetectionStrategy, Component, EventEmitter, HostBinding, Input, OnChanges, Output, SimpleChanges } from '@angular/core';\n\nimport { SportsThemedNotificationDirective, ThemeNotificationVariant } from '@frontend/sports/common/ui/sports-themed-notifcation-bubble';\nimport { CompetitionLogoComponent } from '@frontend/sports/competition-logos/feature';\nimport { WidgetContext } from '@frontend/sports/types/components/widget';\nimport { DsDivider } from '@frontend/ui/divider';\nimport { DsNotificationBubble } from '@frontend/ui/notification-bubble';\nimport { DsTabsModule } from '@frontend/ui/tabsgroup';\n\nimport { SharedPriceboostModule } from '../../shared-priceboost/shared-priceboost.module';\nimport { TabBarItem } from '../../tab-bar/tab-bar.models';\n\nexport enum WidgetTabBarDisplay {\n Default,\n Icon,\n Logo,\n}\n\nexport interface WidgetTabBarItem extends TabBarItem {\n display: WidgetTabBarDisplay;\n context: Partial;\n itemId: string | number;\n sportId?: number;\n widgetId: string;\n showSignPost?: boolean;\n}\n\n@Component({\n selector: 'ms-widget-tab-bar',\n styleUrls: ['./widget-tab-bar.component.scss'],\n templateUrl: './widget-tab-bar.component.html',\n standalone: true,\n styles: [\n `\n :host {\n .icon {\n display: flex;\n font-size: 16px;\n }\n }\n `,\n ],\n imports: [\n DsTabsModule,\n CompetitionLogoComponent,\n SharedPriceboostModule,\n DsNotificationBubble,\n SportsThemedNotificationDirective,\n NgClass,\n DsDivider,\n ],\n changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class WidgetTabBarComponent implements OnChanges {\n @Input() tabs: WidgetTabBarItem[] = [];\n @Input() stretch = false;\n @Output() select = new EventEmitter();\n @HostBinding('class') className = 'widget-tab-bar';\n\n variant: ThemeNotificationVariant = {\n 'default': 'neutral',\n 'mgm-4': 'utility',\n 'sports-interaction': 'utility',\n };\n\n selectedTabBubbleVariant: ThemeNotificationVariant = {\n 'default': 'primary',\n 'mgm-4': 'utility',\n 'sports-interaction': 'utility',\n };\n\n display = WidgetTabBarDisplay;\n\n ngOnChanges(changes: SimpleChanges): void {\n if (changes.tabs) {\n this.updateTabTrackingIds();\n }\n }\n\n selected(id: string) {\n this.tabs = this.tabs.map((item) => ({\n ...item,\n active: item.attribute === id,\n }));\n const activetab = this.tabs.find((item) => item.active);\n this.select.emit(activetab);\n }\n\n private updateTabTrackingIds(): void {\n this.tabs = this.tabs.map((tab, index) => ({\n ...tab,\n attribute: (index + 1).toString(),\n }));\n }\n\n get active(): string {\n const activetab = this.tabs.filter((tab) => tab.active).pop() ?? this.tabs[0];\n\n return activetab.attribute?.toString() ?? '';\n }\n}\n","@if (titleIcon && titleData?.icon) {\n
\n @if (titleLogo && titleData.id) {\n \n } @else if (titleData?.icon) {\n \n }\n
\n}\n\n@if (title) {\n
{{ title }}
\n}\n\n@if (linkConfig && linkConfig.link && (linkConfig.linkText || linkConfig.disableLinkTextAndClick)) {\n
\n \n @if (linkConfig.linkText && !linkConfig.disableLinkTextAndClick) {\n \n }\n \n
\n}\n\n","import { ChangeDetectionStrategy, Component, EventEmitter, HostBinding, HostListener, Input, OnChanges, Output } from '@angular/core';\n\nimport { TitleIconOptions } from '@frontend/sports/types/components/content';\nimport { DsTouchClickDirective } from '@frontend/ui/shared';\n\nimport { LinkConfig } from '../../top-events/top-events.model';\nimport { WidgetTabBarItem } from './widget-tab-bar.component';\n\n@Component({\n selector: 'ms-widget-header',\n templateUrl: './widget-header.component.html',\n styleUrl: './widget-header.component.scss',\n changeDetection: ChangeDetectionStrategy.OnPush,\n hostDirectives: [{ directive: DsTouchClickDirective }],\n})\nexport class WidgetHeaderComponent implements OnChanges {\n @HostBinding('class') className = 'widget-header grid-outer-title';\n @HostBinding('class.clickable-widget-header')\n get isClickableHeader() {\n return this.linkConfig?.disableLinkTextAndClick;\n }\n\n @Input() title: string;\n @Input() titleIcon: string;\n @Input() titleData: WidgetTabBarItem;\n @Input() linkConfig: LinkConfig;\n\n @Output() linkClick = new EventEmitter();\n titleLogo: boolean;\n\n @HostListener('click', ['$event'])\n onClick(): void {\n if (this.linkConfig.disableLinkTextAndClick) {\n this.linkClicked();\n }\n }\n\n ngOnChanges(): void {\n this.titleLogo = this.titleIcon === TitleIconOptions.CompetitionLogo;\n }\n\n linkClicked(): void {\n this.linkClick.emit(true);\n }\n}\n","import { CommonModule } from '@angular/common';\nimport { NgModule } from '@angular/core';\n\nimport { TrustPipe } from '@frontend/sports/common/core/utils/dom';\nimport { CompetitionLogoComponent } from '@frontend/sports/competition-logos/feature';\nimport { IconCustomComponent } from '@frontend/vanilla/features/icons';\n\nimport { ItemComponent } from '../../common/item/item.component';\nimport { SharedPriceboostModule } from '../../shared-priceboost/shared-priceboost.module';\nimport { TabBarModule } from '../../tab-bar/tab-bar.module';\nimport { WidgetHeaderComponent } from './widget-header.component';\nimport { WidgetTabBarComponent } from './widget-tab-bar.component';\n\n@NgModule({\n imports: [\n WidgetTabBarComponent,\n CommonModule,\n TabBarModule,\n CompetitionLogoComponent,\n SharedPriceboostModule,\n ItemComponent,\n TrustPipe,\n IconCustomComponent,\n ],\n exports: [WidgetTabBarComponent, WidgetHeaderComponent],\n declarations: [WidgetHeaderComponent],\n})\nexport class WidgetCommonModule {}\n","import { Injectable } from '@angular/core';\n\nimport { AnimationConfig, BetradarVisualizationConfig } from '@frontend/sports/common/client-config-data-access';\nimport { SportConstant } from '@frontend/sports/common/core/data-access/constants';\nimport { NumberDictionary } from '@frontend/sports/common/core/utils/extended-types';\nimport { keyBy } from 'lodash-es';\n\nclass SportAnimation {\n [key: number]: boolean;\n}\n\n@Injectable({ providedIn: 'root' })\nexport class AnimationProviderService {\n private readonly ms2Animations: SportAnimation;\n private readonly betRadarStickySupportedSports: NumberDictionary;\n private readonly betRadarSupportedSports: NumberDictionary;\n private readonly pitchLogoSupportedSports: NumberDictionary;\n private readonly sideLineLogoSupportedSports: NumberDictionary;\n\n constructor(\n animationConfig: AnimationConfig,\n private betradarVisualizationConfig: BetradarVisualizationConfig,\n ) {\n this.ms2Animations = {\n [SportConstant.Tennis]: animationConfig.isTennisAnimationEnabled,\n [SportConstant.Basketball]: animationConfig.isBasketballAnimationEnabled,\n [SportConstant.Icehockey]: animationConfig.isIceHockeyAnimationEnabled,\n };\n this.betRadarSupportedSports = keyBy(this.betradarVisualizationConfig.supportedSports, (sport) => sport);\n this.betRadarStickySupportedSports = keyBy(this.betradarVisualizationConfig.stickyAddonSupportedSports, (sport) => sport);\n this.pitchLogoSupportedSports = keyBy(this.betradarVisualizationConfig.pitchLogoEnabled, (sport) => sport);\n this.sideLineLogoSupportedSports = keyBy(this.betradarVisualizationConfig.sideLineLogoEnabled, (sport) => sport);\n }\n\n get sportAnimations(): SportAnimation {\n return this.ms2Animations;\n }\n\n get betRadarStickySupportedAnimations(): NumberDictionary {\n return this.betRadarStickySupportedSports;\n }\n\n get betRadarSupportedAnimations(): NumberDictionary {\n return this.betRadarSupportedSports;\n }\n\n get pitchLogoEnabledSports(): NumberDictionary {\n return this.pitchLogoSupportedSports;\n }\n\n get sideLineLogoEnabledSports(): NumberDictionary {\n return this.sideLineLogoSupportedSports;\n }\n\n get betRadarSupportedCoverageLevel(): { [sportId: number]: number } {\n return this.betradarVisualizationConfig.supportedCoverageLevel;\n }\n\n get teamInvertEnabledCompetitions(): { [sportId: number]: number[] } {\n return this.betradarVisualizationConfig.teamInvertCompetitions;\n }\n}\n","export enum EventDetailsColumnType {\n Video = 0,\n Animation = 1,\n Stats = 2,\n}\n","import { EventDetailsColumnType, EventModel } from '@frontend/sports/betting-offer/feature/model';\nimport { TrackingVideoSource } from '@frontend/sports/tracking/feature';\n\nexport interface MediaSetEventBase {\n tab: EventDetailsColumnType | null;\n source: MediaEventSource;\n trackingVideoSource: TrackingVideoSource;\n}\n\nexport interface MediaSetEvent extends MediaSetEventBase {\n event: EventModel;\n}\n\nexport interface MediaSetEventId extends MediaSetEventBase {\n eventId: string;\n}\n\nexport interface MediaSetEventInternal extends MediaSetEvent {\n hasVideo: boolean;\n hasAnimation: boolean;\n hasStats: boolean;\n showStatsCenterButton?: boolean;\n}\n\nexport enum MediaEventSource {\n Default = 0,\n Grid = 1,\n EventDetails = 2,\n EventSwitcher = 3,\n Scoreboard = 4,\n}\n\nexport enum MediaModuleContextPage {\n Default = 0,\n EventDetails = 1,\n}\n\nexport interface EventSwitcherItem {\n id: number;\n name: string;\n children?: EventSwitcherEventItem[];\n}\n\nexport interface EventSwitcherEventItem {\n id: string;\n name: string;\n}\n\nexport interface MediaPersistentState {\n video: MediaTabState & { pinned: boolean };\n animation: MediaTabState;\n statistics: MediaTabState;\n activeTab: EventDetailsColumnType;\n}\n\nexport interface RestoreStatePayload extends MediaPersistentState {\n events: { [key: number]: MediaSetEventInternal };\n}\n\nexport interface SetMediaWidgetContextPayload {\n page: MediaModuleContextPage;\n eventId: string | null;\n}\nexport interface MediaTab {\n tabId: number;\n trackingSource: TrackingVideoSource;\n}\n\nexport interface MediaTabState {\n eventId: string | null;\n}\n","import { Injectable } from '@angular/core';\n\nimport { AnimationProviderService } from '@frontend/sports/betting-offer/feature/animation';\nimport { EventModel, getBackgroundColor } from '@frontend/sports/betting-offer/feature/model';\nimport { ImgConfigurationData, MediaConfig } from '@frontend/sports/common/client-config-data-access';\nimport { SportConstant } from '@frontend/sports/common/core/data-access/constants';\nimport { DateProviderService } from '@frontend/sports/common/core/utils/date';\nimport { NativeAppService } from '@frontend/vanilla/core';\n\n@Injectable({ providedIn: 'root' })\nexport class EventDetailsHelperService {\n sportTypes: { [key: number]: string } = {\n 4: 'soccer',\n 5: 'tennis',\n 7: 'basketball',\n };\n\n constructor(\n private mediaConfig: MediaConfig,\n private animationProvider: AnimationProviderService,\n private imgConfig: ImgConfigurationData,\n private nativeApp: NativeAppService,\n ) {}\n\n hasStats(event: EventModel | null): boolean {\n const isLiveStatsAvailable = !!(event && event.scoreboard.started && event.scoreboard.statistics);\n const isPreMatchStatsAvailable = !!(event && (event.hasFixtureStats || event.hasLeagueStats));\n const areParticipantsAvailable = !!(event && event.participants && event.participants.length);\n const isLeagueTableOnlyAvailable = !!(event && !event.hasFixtureStats && event.hasLeagueStats);\n const isGenericBetRadarIdAvailable = !!(event && event.statsId && this.mediaConfig.showStatsCenterButton);\n\n return !!(\n (event &&\n (this.sportTypes[event.sport.id] || this.nativeApp.isTerminal) &&\n (isLeagueTableOnlyAvailable || areParticipantsAvailable) &&\n (isLiveStatsAvailable || isPreMatchStatsAvailable)) ||\n isGenericBetRadarIdAvailable\n );\n }\n\n hasAnimation(event: EventModel | null): boolean {\n return !!(\n event &&\n event.live &&\n !!this.animationProvider.sportAnimations[event.sport.id] &&\n event.participants &&\n !!event.participants.length\n );\n }\n\n hasBetRadarMappingID(event: EventModel | null): boolean {\n return !!(event && event.statsId && event.sport.id === SportConstant.Soccer);\n }\n\n hasBetRadarVisualization(event: EventModel | null): boolean {\n const result = !!(\n event &&\n event.betRadarVisualizationId &&\n event.betRadarCoverageLevel &&\n event.betRadarCoverageLevel >= this.animationProvider.betRadarSupportedCoverageLevel[event.sport.id] &&\n event.live &&\n this.animationProvider.betRadarSupportedAnimations[event.sport.id] &&\n event.participants &&\n !!event.participants.length\n );\n\n return result;\n }\n\n hasImgAnimationVisualization(event: EventModel): boolean {\n const animationShowTime = DateProviderService.addHours(DateProviderService.getNow(), this.imgConfig.hoursToShowImgAnimation);\n const showPrematchAnimation =\n animationShowTime.getTime() >= event.startDate.getTime() && DateProviderService.getNow().getTime() <= event.startDate.getTime();\n\n return !!((showPrematchAnimation || event.live) && event.imgEventId);\n }\n\n hasBetRadarStickyAddonVisualization(event: EventModel | null): boolean {\n return !!(event && this.hasBetRadarVisualization(event) && this.animationProvider.betRadarStickySupportedAnimations[event.sport.id]);\n }\n\n getBackgroundColor(event: EventModel, useSolid: boolean): string | undefined {\n return getBackgroundColor(event, useSolid);\n }\n}\n","import { Injectable } from '@angular/core';\n\nimport { AnimationProviderService } from '@frontend/sports/betting-offer/feature/animation';\nimport { EventModel } from '@frontend/sports/betting-offer/feature/model';\nimport { ImgConfigurationData, MediaConfig } from '@frontend/sports/common/client-config-data-access';\nimport { SPORTS_WITH_MAPPED_STATS } from '@frontend/sports/common/core/data-access/constants';\nimport { DateProviderService } from '@frontend/sports/common/core/utils/date';\n\n@Injectable({ providedIn: 'root' })\nexport class EventMediaHelperService {\n constructor(\n private animationProvider: AnimationProviderService,\n private mediaConfig: MediaConfig,\n private imgConfig: ImgConfigurationData,\n ) {}\n\n hasVideo(event: EventModel): boolean {\n return !!event.stream && !!event.stream.id;\n }\n\n hasLiveVideo(event: EventModel): boolean {\n return event.live && this.hasVideo(event);\n }\n\n hasAnimationOrSimulation(event: EventModel): boolean {\n return this.isRunningBallSupported(event) || this.isAnimationSupported(event);\n }\n\n hasAnimation(event: EventModel): boolean {\n return this.isAnimationSupported(event);\n }\n\n hasBetradarAnimation(event: EventModel): boolean {\n return this.hasBetradarAnimationSupported(event);\n }\n\n hasStats(event: EventModel): boolean {\n const currentEventStats = event.scoreboard.visible && event.scoreboard.started;\n\n return Boolean(\n (!!event.statsId && this.mediaConfig.showStatsCenterButton) ||\n (SPORTS_WITH_MAPPED_STATS.includes(event.sport.id) && (event.hasFixtureStats || event.hasLeagueStats || currentEventStats)),\n );\n }\n\n private isAnimationSupported(event: EventModel): boolean {\n return this.hasAnimationSupported(event) || this.hasBetradarAnimationSupported(event) || this.hasImgAnimationSupported(event);\n }\n\n private isRunningBallSupported(event: EventModel): boolean {\n return !!(event.live && event.runningBallDataId && event.participants?.length);\n }\n\n private hasAnimationSupported(event: EventModel): boolean {\n return !!(event.live && this.animationProvider.sportAnimations[event.sport.id] && event.participants && event.participants.length);\n }\n\n private hasBetradarAnimationSupported(event: EventModel): boolean {\n return !!(\n event.live &&\n event.betRadarVisualizationId &&\n event.betRadarCoverageLevel &&\n event.betRadarCoverageLevel >= this.animationProvider.betRadarSupportedCoverageLevel[event.sport.id] &&\n event.participants &&\n event.participants.length &&\n this.animationProvider.betRadarSupportedAnimations[event.sport.id]\n );\n }\n\n private hasImgAnimationSupported(event: EventModel): boolean {\n const animationShowTime = DateProviderService.addHours(DateProviderService.getNow(), this.imgConfig.hoursToShowImgAnimation);\n const showPrematchAnimation =\n animationShowTime.getTime() >= event.startDate.getTime() && DateProviderService.getNow().getTime() <= event.startDate.getTime();\n\n return !!((showPrematchAnimation || event.live) && event.imgEventId);\n }\n}\n","export enum TrackingVideoSource {\n Default = 'Default',\n Grid = 'Event List Video Icon',\n EventDetails = 'Event Details',\n EventSwitcher = 'Media Module Events Selector',\n MediaColumn = 'Media Module Video Play Icon',\n VideoPlay = 'Play Button',\n ShowcaseModule = 'Showcase Play Button',\n}\n","import { pprops } from '@frontend/sports/common/core/utils/redux';\nimport { MediaSetEvent, MediaSetEventId, SetMediaWidgetContextPayload } from '@frontend/sports/grid/media/feature/model';\nimport { createAction, props } from '@ngrx/store';\n\nexport class MediaApiActions {\n private static readonly SET_EVENT = '@media/SET_EVENT';\n private static readonly SET_EVENT_ID = '@media/SET_EVENT_ID';\n private static readonly TOGGLE_PLAYING = '@media/TOGGLE_PLAYING';\n private static readonly SET_MEDIA_WIDGET_CONTEXT = '@media/SET_MEDIA_WIDGET_CONTEXT';\n\n static readonly togglePlaying = createAction(MediaApiActions.TOGGLE_PLAYING, props<{ value: boolean }>());\n static readonly setMediaWidgetContext = createAction(MediaApiActions.SET_MEDIA_WIDGET_CONTEXT, pprops());\n static readonly setEvent = createAction(MediaApiActions.SET_EVENT, pprops());\n static readonly setEventId = createAction(MediaApiActions.SET_EVENT_ID, pprops());\n}\n","import { EventDetailsColumnType, EventModel } from '@frontend/sports/betting-offer/feature/model';\nimport { EventSwitcherItem, MediaModuleContextPage, MediaTabState } from '@frontend/sports/grid/media/feature/model';\nimport { TrackingVideoSource } from '@frontend/sports/tracking/feature';\nimport { createFeatureSelector, createSelector } from '@ngrx/store';\n\nexport const mediaInitialState: MediaState = {\n active: false,\n video: {\n pinned: false,\n autoplay: false,\n playing: false,\n dropdownEvents: [],\n eventId: null,\n unpinInfoVisible: false,\n pinEnabled: false,\n trackingSource: TrackingVideoSource.Default,\n },\n activeTab: EventDetailsColumnType.Video,\n animation: { eventId: null },\n statistics: { eventId: null },\n eventInfos: {},\n context: { page: MediaModuleContextPage.Default, eventId: null },\n isCollapsed: false,\n mediaExpandState: {\n isLocationAllowsExpand: false,\n isExpanded: false,\n restoreExpandState: false,\n isDefaultExpanded: false,\n },\n};\n\nexport const mediaFeatureKey = 'media';\n\nexport interface IMediaRootState {\n media: MediaState;\n}\n\nexport const mediaStateSelector = createFeatureSelector(mediaFeatureKey);\n\nexport const mediaExpandStateSelector = createSelector(mediaStateSelector, (state: MediaState) => state.mediaExpandState);\n\nexport const mediaEventInfoSelector = (eventId: string) => createSelector(mediaStateSelector, (state: MediaState) => state.eventInfos[eventId]);\n\nexport const mediaActiveSelector = createSelector(mediaStateSelector, (state: MediaState) => state.active);\n\nexport const mediaExpandedStateSelector = createSelector(mediaStateSelector, (state: MediaState) => state.mediaExpandState);\n\ninterface VideoState extends MediaTabState {\n pinned: boolean;\n autoplay: boolean;\n playing: boolean;\n dropdownEvents: EventSwitcherItem[];\n unpinInfoVisible: boolean;\n pinEnabled: boolean;\n trackingSource: TrackingVideoSource;\n}\n\nexport interface MediaModuleContext {\n page: MediaModuleContextPage;\n eventId: string | null;\n}\n\nexport interface EventInfo {\n event: EventModel;\n hasVideo: boolean;\n hasAnimation: boolean;\n hasStats: boolean;\n}\n\nexport interface MediaState {\n active: boolean;\n activeTab: EventDetailsColumnType;\n video: VideoState;\n animation: MediaTabState;\n statistics: MediaTabState;\n eventInfos: { [eventId: string]: EventInfo };\n context: MediaModuleContext;\n isCollapsed: boolean;\n mediaExpandState: MediaExpandState;\n pendingEsport?: EventModel;\n}\n\nexport interface MediaExpandState {\n isExpanded: boolean;\n isLocationAllowsExpand: boolean;\n restoreExpandState: boolean;\n isDefaultExpanded: boolean;\n}\n\nexport interface EventsSelector {\n events: EventSwitcherItem[];\n}\n","import { Fixture } from '@cds/betting-offer';\nimport { pprops } from '@frontend/sports/common/core/utils/redux';\nimport { CouponTypes } from '@frontend/sports/coupons/feature/models';\nimport { GridEvent, GridGrouping, GridModel, GridSorting, Group, GroupBadge, SubscriptionTopic } from '@frontend/sports/grid/core/feature/model';\nimport { createAction, props } from '@ngrx/store';\n\nexport interface IGrid {\n id: string;\n}\n\nexport interface IGridGroup {\n columnId: string;\n groupId: string;\n}\n\nexport interface IGridAddEvent extends IGrid {\n events: GridEvent | GridEvent[];\n grouping?: GridGrouping;\n}\n\nexport interface IGridAddCouponEvent extends IGrid {\n events: Fixture[];\n couponType: CouponTypes;\n}\n\nexport interface IGridSetEvent extends IGridAddEvent {\n topic?: SubscriptionTopic;\n}\n\nexport interface IGridUpdateEvent extends IGrid {\n event: GridEvent;\n}\n\nexport interface IGridReplaceEvent extends IGrid {\n targetFixtureId: string; // Event to be removed\n newFixture: GridEvent; // Event to be inserted\n}\n\nexport interface IGridEventsToReplace {\n currentEvent: GridEvent;\n newEvent?: GridEvent;\n}\n\nexport interface IGridReplaceEvents extends IGrid {\n events: IGridEventsToReplace[];\n}\n\nexport interface IGridCollapseEvent extends IGrid {\n eventId: string;\n optionGroupId?: string;\n}\n\nexport interface IGridSetActiveGroup extends IGrid, IGridGroup {}\n\nexport interface IGridSetActiveGroupType extends IGridSetActiveGroup {\n extended: boolean;\n}\n\nexport interface IGridSetGroupCollapse extends IGrid {\n groupId: number;\n}\n\nexport interface IGridAddGroups extends IGrid {\n groups: Group[];\n}\n\nexport interface IGridUpdateGroupsBadges extends IGrid {\n badges: Record;\n}\n\nexport interface IGridChangeSorting extends IGrid {\n sorting: GridSorting;\n}\n\nexport interface IGridRemoveEvent extends IGrid {\n eventIds: string[];\n}\n\nexport const GRID_ACTION_SCHEMA = '@grid';\n\nexport class GridActions {\n static ADD_EVENTS = `${GRID_ACTION_SCHEMA}/ADD_EVENTS`;\n static SET_EVENTS = `${GRID_ACTION_SCHEMA}/SET_EVENTS`;\n static UPDATE_EVENTS = `${GRID_ACTION_SCHEMA}/UPDATE_EVENTS`;\n static REPLACE_EVENTS = `${GRID_ACTION_SCHEMA}/REPLACE_EVENTS`;\n static REMOVE_EVENTS = `${GRID_ACTION_SCHEMA}/REMOVE_EVENTS`;\n static REPLACE_EVENT = `${GRID_ACTION_SCHEMA}/REPLACE_EVENT`;\n static COLLAPSE_EVENTS = `${GRID_ACTION_SCHEMA}/COLLAPSE_EVENTS`;\n static DESTROY = `${GRID_ACTION_SCHEMA}/DESTROY`;\n static INIT = `${GRID_ACTION_SCHEMA}/INIT`;\n static SET_ACTIVE_GROUP = `${GRID_ACTION_SCHEMA}/SET_ACTIVE_GROUP`;\n static SET_ACTIVE_GROUP_TYPE = `${GRID_ACTION_SCHEMA}/SET_ACTIVE_GROUP_TYPE`;\n static SET_STATE_COLLAPSE = `${GRID_ACTION_SCHEMA}/SET_STATE_COLLAPSE`;\n static SET_GROUP_COLLAPSE = `${GRID_ACTION_SCHEMA}/SET_GROUP_COLLAPSE`;\n static SET_MORE_COLLAPSE = `${GRID_ACTION_SCHEMA}/SET_MORE_COLLAPSE`;\n static ADD_GROUPS = `${GRID_ACTION_SCHEMA}/ADD_GROUPS`;\n static UPDATE_GROUPS_BADGES = `${GRID_ACTION_SCHEMA}/UPDATE_GROUPS_BADGES`;\n static CHANGE_SORTING = `${GRID_ACTION_SCHEMA}/CHANGE_SORTING`;\n static ADD_COUPON_EVENTS = `${GRID_ACTION_SCHEMA}/ADD_COUPON_EVENTS`;\n\n static addEvents = createAction(GridActions.ADD_EVENTS, pprops());\n static setEvents = createAction(GridActions.SET_EVENTS, pprops());\n static updateEvents = createAction(GridActions.UPDATE_EVENTS, pprops());\n static replaceEvents = createAction(GridActions.REPLACE_EVENTS, pprops());\n static removeEvents = createAction(GridActions.REMOVE_EVENTS, pprops());\n static replaceEvent = createAction(GridActions.REPLACE_EVENT, pprops());\n static collapseEvents = createAction(GridActions.COLLAPSE_EVENTS, pprops());\n static destroy = createAction(GridActions.DESTROY, props<{ gridId: string }>());\n static init = createAction(GridActions.INIT, pprops());\n static setActiveGroup = createAction(GridActions.SET_ACTIVE_GROUP, pprops());\n static setActiveGroupType = createAction(GridActions.SET_ACTIVE_GROUP_TYPE, pprops());\n static setGroupCollapse = createAction(GridActions.SET_GROUP_COLLAPSE, pprops());\n static setMoreCollapse = createAction(GridActions.SET_MORE_COLLAPSE, props<{ gridId: string }>());\n static addGroups = createAction(GridActions.ADD_GROUPS, pprops());\n static updateGroupsBadges = createAction(GridActions.UPDATE_GROUPS_BADGES, pprops());\n static changeSorting = createAction(GridActions.CHANGE_SORTING, pprops());\n static addCouponEvents = createAction(GridActions.ADD_COUPON_EVENTS, pprops());\n}\n","import { GridModel } from '@frontend/sports/grid/core/feature/model';\nimport { createFeatureSelector } from '@ngrx/store';\n\nexport interface IGridState {\n [key: string]: GridModel;\n}\n\nexport const gridInitialState: IGridState = {};\n\nexport const gridStateKey = 'grid';\n\nexport interface IGridRootState {\n grid: IGridState;\n}\n\nexport const gridStateSelector = createFeatureSelector(gridStateKey);\n","import { ImageProfile } from '@cds/betting-offer';\nimport { CompetitionStatistics } from '@cds/statistics/competition';\nimport { EventModel, EventOptionGroup, RegionModel, SportModel } from '@frontend/sports/betting-offer/feature/model';\nimport { StringDictionary } from '@frontend/sports/common/core/utils/extended-types';\n\nexport enum SubscriptionTopic {\n Grid,\n NonGridable,\n Specials,\n Outrights,\n All,\n}\nexport interface Column {\n id: string;\n groups: Group[];\n enabled: boolean;\n more: boolean;\n}\n\nexport interface OptionsToShow {\n v1?: number[];\n v2?: number[];\n}\n\nexport interface Group {\n id: string;\n name: string;\n active: boolean;\n extended: boolean;\n visible: boolean;\n balancedMarket?: boolean;\n marketAttribute?: boolean;\n options?: string[];\n childGroups?: string[];\n optionsToShow?: OptionsToShow;\n badge?: GroupBadge;\n scoreBoardPeriodId?: number;\n fallbackGridGroupIds?: string[];\n isFallbackGroup: boolean;\n}\n\nexport enum GridLayout {\n Default,\n SixPack,\n Hybrid,\n}\n\nexport interface GridBreakpointSize {\n columns: number;\n default: number;\n}\n\nexport enum GridGrouping {\n None,\n Date,\n League,\n HomeForm,\n AwayForm,\n OverallForm,\n}\n\nexport interface GroupBadge {\n text: string;\n cssClass: string;\n}\n\nexport enum GridSorting {\n Competition,\n Time,\n HomeForm,\n AwayForm,\n OverallForm,\n}\n\nexport interface CollapsedState {\n collapsed: boolean;\n collapsedChildren: string[];\n}\n\nexport interface GridEvent extends EventModel {\n favourited?: boolean;\n}\n\nexport interface EventGroup {\n id: number;\n name: string;\n count: number;\n events: GridEvent[];\n collapsed: boolean;\n collapsible: boolean;\n deferred: boolean;\n}\n\nexport interface LeagueGroup extends EventGroup {\n siblings: number[];\n region: RegionModel;\n canBeFavourited: boolean;\n statistics?: CompetitionStatistics | undefined;\n virtualCompetitionId?: number | undefined;\n virtualCompetitionGroupId?: number | undefined;\n imageProfile?: ImageProfile;\n}\n\nexport interface StandingsData {\n groups: LeagueGroup[];\n virtualCompetitionId?: number | undefined;\n}\n\nexport interface DateGroup extends EventGroup {\n date: Date;\n}\n\nexport interface FormGroup extends EventGroup {\n winCount: number;\n}\n\nexport interface GridMedia {\n videoEvent?: string;\n animationEvent?: string;\n statsEvent?: string;\n enabled: boolean;\n}\n\nexport interface GridCollapsedModel {\n groups: number[];\n events: StringDictionary;\n}\n\nexport interface GridModel {\n columns: Column[];\n grouping: GridGrouping;\n disableGroupSorting?: boolean;\n groupingThreshold?: number;\n groups: (EventGroup | LeagueGroup)[];\n id: string;\n layout: GridLayout;\n sport: SportModel;\n media: GridMedia;\n topic: SubscriptionTopic;\n sorting?: GridSorting;\n collapsed: GridCollapsedModel;\n}\n\nexport interface FallbackGroup {\n optionGroup: EventOptionGroup | undefined;\n fallbackMarketName: string;\n showNoGoalText: boolean;\n isFallbackMarket: boolean;\n}\n","/* eslint-disable no-param-reassign */\nimport { EventModel, LeagueModel, RegionModel, SportModel } from '@frontend/sports/betting-offer/feature/model';\nimport { StringDictionary } from '@frontend/sports/common/core/utils/extended-types';\nimport {\n CollapsedState,\n DateGroup,\n EventGroup,\n FormGroup,\n GridEvent,\n GridGrouping,\n GridModel,\n GridSorting,\n Group,\n GroupBadge,\n LeagueGroup,\n SubscriptionTopic,\n} from '@frontend/sports/grid/core/feature/model';\nimport { createReducer, on } from '@ngrx/store';\nimport { produce } from 'immer';\nimport { ceil, clone, differenceBy, find, findIndex, flatten, isArray, isNumber, keyBy, sortBy } from 'lodash-es';\n\nimport { GridActions, IGridEventsToReplace, IGridGroup } from './grid.actions';\nimport { IGridState, gridInitialState } from './grid.state';\n\nconst producer = (\n state: IGridState,\n action: TAction,\n gridUpdate: (model: GridModel) => GridModel,\n): IGridState => {\n const currentGrid = state[action.payload.id];\n\n return {\n ...state,\n [action.payload.id]: gridUpdate(currentGrid),\n };\n};\n\nexport const gridReducer = createReducer(\n gridInitialState,\n on(GridActions.init, (state, action) => ({\n ...state,\n [action.payload.id]: action.payload,\n })),\n on(GridActions.addEvents, (state, action) =>\n producer(state, action, (grid) => addEvents(grid, action.payload.events, { grouping: action.payload.grouping })),\n ),\n on(GridActions.setEvents, (state, action) =>\n producer(state, action, (grid) =>\n addEvents(grid, action.payload.events, { set: true, topic: action.payload.topic, grouping: action.payload.grouping }),\n ),\n ),\n on(GridActions.updateEvents, (state, action) => producer(state, action, (grid) => updateEvent(grid, action.payload.event))),\n on(GridActions.replaceEvents, (state, action) => producer(state, action, (grid) => replaceEvents(grid, action.payload.events))),\n on(GridActions.removeEvents, (state, action) => producer(state, action, (grid) => removeEvents(grid, action.payload.eventIds))),\n on(GridActions.collapseEvents, (state, action) =>\n producer(state, action, (grid) => collapseEvent(grid, action.payload.eventId, action.payload.optionGroupId)),\n ),\n on(GridActions.setActiveGroup, (state, action) =>\n producer(state, action, (grid) => setActiveGroup(grid, action.payload.columnId, action.payload.groupId)),\n ),\n on(GridActions.setGroupCollapse, (state, action) => producer(state, action, (grid) => setGroupCollapse(grid, action.payload.groupId))),\n on(GridActions.addGroups, (state, action) => producer(state, action, (grid) => addGroups(grid, action.payload.groups))),\n on(GridActions.updateGroupsBadges, (state, action) => producer(state, action, (grid) => updateGroupsBadges(grid, action.payload.badges))),\n on(GridActions.changeSorting, (state, action) => producer(state, action, (grid) => changeSorting(grid, action.payload.sorting))),\n on(GridActions.replaceEvent, (state, action) =>\n producer(state, action, (grid) => replaceEvent(grid, action.payload.targetFixtureId, action.payload.newFixture)),\n ),\n on(GridActions.destroy, (state, action): IGridState => {\n const copy = { ...state };\n // eslint-disable-next-line @typescript-eslint/no-dynamic-delete\n delete copy[action.gridId];\n\n return copy;\n }),\n);\n\nexport function mapLeague(league: LeagueModel, region: RegionModel, _sport: SportModel): LeagueGroup {\n const groupId = league.parentLeagueId || league.id;\n\n return {\n id: groupId,\n name: league.name,\n count: 0,\n events: [],\n siblings: [league.id],\n region,\n collapsed: false,\n collapsible: true,\n deferred: false,\n canBeFavourited: true,\n imageProfile: league.imageProfile,\n };\n}\n\nexport function getLeague(grid: GridModel, event: EventModel): LeagueGroup | undefined {\n if (!grid.groups) {\n error('Groupped grid model should define groups');\n return;\n }\n\n let league = find(grid.groups, (current) => current.id === event.league.parentLeagueId || current.id === event.league.id) as LeagueGroup;\n\n if (!league) {\n league = mapLeague(event.league, event.region, event.sport);\n grid.groups.push(league);\n }\n\n return league;\n}\n\nexport function changeSorting(grid: GridModel, sorting?: GridSorting): GridModel {\n return produce(grid, (draft) => {\n if (draft) {\n draft.sorting = sorting;\n }\n });\n}\n\nexport function mapForm(id: number, winCount: number): FormGroup {\n return {\n id,\n name: '',\n winCount,\n count: 0,\n events: [],\n collapsed: false,\n collapsible: true,\n deferred: false,\n };\n}\n\nexport function mapDate(id: number, date: Date): DateGroup {\n return {\n id,\n name: date.toDateString(),\n date,\n count: 0,\n events: [],\n collapsed: false,\n collapsible: true,\n deferred: false,\n };\n}\n\nexport function getDate(grid: GridModel, event: EventModel): DateGroup | undefined {\n if (!grid.groups) {\n error('Groupped grid model should define groups');\n return;\n }\n\n // to make the id a bit more readable\n const day = 24 * 60 * 60 * 1000;\n const base = new Date(event.startDate);\n\n base.setHours(0);\n base.setMinutes(0);\n base.setSeconds(0);\n base.setMilliseconds(0);\n\n const id = ceil(base.getTime() / day);\n\n let group = find(grid.groups, (current) => current.id === id) as DateGroup;\n\n if (!group) {\n group = mapDate(id, base);\n grid.groups.push(group);\n }\n\n return group;\n}\n\nexport function getForm(grid: GridModel, event: EventModel, grouping: GridGrouping): FormGroup | undefined {\n if (!grid.groups) {\n error('Groupped grid model should define groups');\n return;\n }\n\n let id = 0;\n\n switch (grouping) {\n case GridGrouping.AwayForm:\n id = event.fixtureForm?.awayTeamAwayForm?.winCount ?? 0;\n break;\n case GridGrouping.OverallForm:\n const homeWins = event.fixtureForm?.overallHomeTeamForm?.winCount;\n const awayWins = event.fixtureForm?.overallAwayTeamForm?.winCount;\n id = Math.max(homeWins ?? 0, awayWins ?? 0);\n break;\n default:\n id = event.fixtureForm?.homeTeamHomeForm?.winCount ?? 0;\n }\n\n let group = find(grid.groups, (current) => current.id === id) as FormGroup;\n\n if (!group) {\n group = mapForm(id, id);\n grid.groups.push(group);\n }\n\n return group;\n}\n\nexport function getGroup(grid: GridModel, entity: EventModel | number): EventGroup | undefined {\n if (!grid.groups) {\n error('Groupped grid model should define groups');\n return;\n }\n\n if (grid.grouping === GridGrouping.None) {\n if (grid.groups.length === 0) {\n grid.groups.push({\n collapsed: false,\n collapsible: false,\n count: 0,\n deferred: false,\n events: [],\n id: 1,\n name: '',\n });\n }\n\n return grid.groups[0];\n }\n\n if (!entity) {\n error('Entity should be defined to query group');\n return;\n }\n\n if (!isNumber(entity)) {\n switch (grid.grouping) {\n case GridGrouping.Date:\n return getDate(grid, entity);\n case GridGrouping.League:\n return getLeague(grid, entity);\n case GridGrouping.HomeForm:\n case GridGrouping.AwayForm:\n case GridGrouping.OverallForm:\n return getForm(grid, entity, grid.grouping);\n }\n }\n\n return find(grid.groups, (group) => group.id === entity);\n}\n\nexport function setActiveGroup(grid: GridModel, columnId: string, groupId: string): GridModel {\n return produce(grid, (draft) => {\n const currentColumn = find(draft.columns, (current) => current.id === columnId);\n\n if (!currentColumn) {\n error('Could not find specified column');\n return;\n }\n\n const currentGroup = find(currentColumn.groups, (current) => current.id === groupId);\n\n if (!currentGroup) {\n error('Could not find specified group');\n return;\n }\n\n currentColumn.groups.forEach((current) => (current.active = current.id === currentGroup.id));\n\n if (currentGroup.extended) {\n draft.columns.forEach((column) => (column.enabled = column === currentColumn));\n } else {\n draft.columns.forEach((column) => (column.enabled = true));\n }\n });\n}\n\nexport function setActiveGroupState(grid: GridModel, state: IGridGroup[]): GridModel {\n let result = grid;\n const groups = keyBy(result.columns[0]?.groups, (group) => group.id);\n const ordered = sortBy(state, (current) => groups[current.groupId] && groups[current.groupId].extended);\n\n for (const current of ordered) {\n result = setActiveGroup(result, current.columnId, current.groupId);\n }\n\n return result;\n}\n\nexport function setStateCollapse(grid: GridModel, collapsedGroups: number[], collapsedEvents: StringDictionary): GridModel {\n return produce(grid, (draft) => {\n draft.collapsed.groups = collapsedGroups;\n draft.collapsed.events = collapsedEvents;\n });\n}\n\nexport function setGroupCollapse(grid: GridModel, groupId: number): GridModel {\n return produce(grid, (draft) => {\n if (!draft.groups) {\n error('Groupped grid model should define groups');\n return;\n }\n\n const existing = draft.groups.find((x) => x.id === groupId);\n\n if (!existing) {\n error('Group not found');\n return;\n }\n\n if (!existing.collapsible) {\n error('Group not collapsible');\n return;\n }\n\n existing.collapsed = !existing.collapsed;\n\n const position = draft.collapsed.groups.indexOf(existing.id);\n\n if (existing.collapsed) {\n if (position === -1) {\n draft.collapsed.groups.push(existing.id);\n }\n } else {\n if (position === -1) {\n draft.collapsed.groups.splice(existing.id, 1);\n }\n }\n });\n}\n\nexport function setGroupCollapseThreshold(grid: GridModel, threshold: number): GridModel {\n return produce(grid, (draft) => {\n if (draft.grouping === GridGrouping.None) {\n return;\n }\n\n let total = 0;\n\n draft.groups.forEach((group, index) => {\n if (group.events.length === 0) {\n group.collapsed = true;\n group.deferred = true;\n } else if (threshold) {\n total += group.events.length;\n group.collapsed = index !== 0 && total > threshold;\n }\n });\n });\n}\n\nexport function addGroups(grid: GridModel, groups: Group[]): GridModel {\n return produce(grid, (draft) => {\n draft.columns.forEach((column) => {\n groups.forEach((group) => {\n const existing = column.groups.find((current) => current.id === group.id && current.extended === group.extended);\n\n if (existing) {\n existing.name = group.name;\n existing.visible = true;\n } else {\n column.groups.push(group);\n }\n });\n //TODO consider refactoring this file!\n // eslint-disable-next-line max-lines\n });\n });\n}\n\nexport function updateGroupsBadges(grid: GridModel, badges: Record): GridModel {\n return produce(grid, (draft) => {\n draft.columns.forEach((column) => {\n column.groups.forEach((group) => {\n group.badge = badges[group.id];\n });\n });\n });\n}\n\nexport function addLeague(grid: GridModel, league: LeagueGroup): GridModel {\n return produce(grid, (draft) => {\n if (!draft.groups) {\n error('Groupped grid model should define groups');\n return;\n }\n\n const existing = draft.groups.find((x) => x.id === league.id) as LeagueGroup;\n\n if (existing) {\n if (!existing.siblings.includes(league.siblings[0])) {\n existing.siblings.push(league.siblings[0]);\n }\n } else {\n draft.groups.push(league);\n }\n });\n}\n\nexport function collapseEvent(grid: GridModel, eventId: string, optionGroupId?: string): GridModel {\n // the new version of this function was proposed by Ventsislav Mladenov\n const group = grid.groups.find((current) => current.events.some((currentEvent) => currentEvent.id === eventId));\n if (!group) {\n console.error('Expected to have group but not found');\n\n return grid;\n }\n const event = group.events.find((current) => current.id === eventId);\n if (!event) {\n console.error('Event not found');\n\n return grid;\n }\n const newGrid = {\n ...grid,\n collapsed: {\n ...grid.collapsed,\n events: {\n ...grid.collapsed.events,\n [event.id]: grid.collapsed.events[event.id]\n ? {\n collapsed: grid.collapsed.events[event.id].collapsed,\n collapsedChildren: [...grid.collapsed.events[event.id].collapsedChildren],\n }\n : {\n collapsed: false,\n collapsedChildren: [],\n },\n },\n },\n };\n const eventState = newGrid.collapsed.events[event.id];\n if (optionGroupId) {\n const index = eventState.collapsedChildren.indexOf(optionGroupId);\n if (index === -1) {\n eventState.collapsedChildren.push(optionGroupId);\n } else {\n eventState.collapsedChildren.splice(index, 1);\n }\n } else {\n eventState.collapsed = !eventState.collapsed;\n }\n\n return newGrid;\n}\n\nexport function updateEvent(grid: GridModel, event: EventModel): GridModel {\n return replaceEvents(grid, { currentEvent: event });\n}\n\nexport function replaceEvents(grid: GridModel, events: IGridEventsToReplace | IGridEventsToReplace[]): GridModel {\n return produce(grid, (draft) => {\n if (!draft) {\n return;\n }\n\n const eventsArray = isArray(events) ? events : [events];\n\n eventsArray.forEach((event) => {\n const group = getGroup(draft as GridModel, event.currentEvent);\n\n if (!group) {\n error('Expected to have group but not found');\n return;\n }\n\n const index = findIndex(group.events, (current) => current.id === event.currentEvent.id);\n\n if (index < 0) {\n error('Event not found', event.currentEvent);\n return;\n }\n\n group.events[index] = clone(event.newEvent || event.currentEvent);\n });\n });\n}\n\nexport function getEvents(grid: GridModel): EventModel[] {\n return flatten(grid.groups.map((group) => group.events));\n}\n\n//TODO please don't modify this function any further without breaking it into smaller functions, it's already too complex\n// eslint-disable-next-line max-lines-per-function\nexport function addEvents(\n grid: GridModel,\n events: EventModel | EventModel[],\n options: { set?: boolean; topic?: SubscriptionTopic; grouping?: GridGrouping } = {},\n): GridModel {\n return produce(grid, (draft) => {\n if (!events || !draft) {\n return;\n }\n\n const collection = isArray(events) ? events : [events];\n\n if (collection.length === 0 || !grid || !draft) {\n return;\n }\n\n if (!draft.groups || options.set) {\n draft.groups = [];\n }\n\n if (options.topic !== undefined) {\n draft.topic = options.topic;\n }\n\n if (options.grouping !== undefined) {\n draft.grouping = options.grouping;\n }\n\n collection.forEach((event) => {\n const group = getGroup(draft as GridModel, event);\n\n if (!group) {\n error('Expected to have group but not found');\n return;\n }\n\n if (group.events.find((gridEvent) => gridEvent.id === event.id)) {\n return;\n }\n group.events.push(event);\n group.deferred = false;\n group.collapsed = draft.collapsed.groups.includes(group.id);\n });\n\n if (!grid.disableGroupSorting) {\n draft.groups.forEach((group) => {\n group.events = sortBy(group.events, (event) => event.startDate);\n\n if (group.count < group.events.length) {\n group.count = group.events.length;\n }\n });\n }\n\n if (!draft.groupingThreshold) {\n return;\n }\n\n let visible = flatten(draft.groups.map((group) => group.events)).length;\n const initial = (grid.groups || []).length === 0;\n\n differenceBy(draft.groups, grid.groups, (group) => group.id).forEach((group, index) => {\n if (!initial || index !== 0) {\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n group.collapsed = visible >= draft.groupingThreshold!;\n }\n\n visible += group.events.length;\n });\n });\n}\n\nexport function removeEvents(grid: GridModel, eventIds: string[]): GridModel {\n return produce(grid, (draft) => {\n draft.groups.forEach((group) => {\n if (group.events.some((e) => eventIds.includes(e.id))) {\n group.events = group.events.filter((e) => eventIds.every((id) => id !== e.id));\n }\n });\n draft.groups = draft.groups.filter((g) => g.events.length > 0);\n });\n}\n\nexport function replaceEvent(grid: GridModel, targetFixtureId: string, newFixture: GridEvent): GridModel {\n return produce(grid, (draft) => {\n draft.groups.forEach((group) => {\n if (group.events.some((e) => e.id === targetFixtureId)) {\n group.events = group.events.map((event) => (event.id === targetFixtureId ? newFixture : event));\n }\n });\n });\n}\n\nfunction error(message?: unknown, ...optionalParams: unknown[]) {\n console.error(message, optionalParams);\n}\n","import { Injectable } from '@angular/core';\n\nimport { StringDictionary, isDefined } from '@frontend/sports/common/core/utils/extended-types';\nimport { GridModel } from '@frontend/sports/grid/core/feature/model';\nimport { flatMap, keyBy, keys, now } from 'lodash-es';\n\nexport interface EventCollapsedState {\n until: Date;\n collapsed: boolean;\n collapsedChildren: string[];\n}\n\nexport interface GroupCollapsedState {\n collapsed: boolean;\n}\n\nexport interface ColumnActiveState {\n index: number;\n group: string;\n extended: boolean;\n}\n\nexport interface GridStoreState {\n groups: number[];\n events: StringDictionary;\n columns: ColumnActiveState[];\n}\n\n@Injectable({ providedIn: 'root' })\nexport class GridStoreService {\n private state = new Map();\n\n set(grid: GridModel, group?: number): void {\n const actualState = this.get(grid.id);\n\n const events = this.getEventsState(grid);\n const columns = this.getColumnState(grid);\n const groups = actualState ? actualState.groups : [];\n\n if (group) {\n const groupIndex = groups.indexOf(group);\n\n if (groupIndex === -1) {\n groups.push(group);\n } else {\n groups.splice(groupIndex, 1);\n }\n }\n\n if (columns.length > 0 || groups.length > 0 || keys(events).length > 0) {\n this.state.set(grid.id, { events, columns, groups });\n } else {\n this.state.delete(grid.id);\n }\n }\n\n get(id: string): GridStoreState | undefined {\n const expiry = now();\n\n for (const state of this.state.values()) {\n // eslint-disable-next-line guard-for-in\n for (const eventKey in state.events) {\n const event = state.events[eventKey];\n\n if (event.until.getTime() < expiry) {\n // eslint-disable-next-line @typescript-eslint/no-dynamic-delete\n delete state.events[eventKey];\n }\n }\n }\n\n return this.state.get(id);\n }\n\n private getColumnState(grid: GridModel): ColumnActiveState[] {\n return grid.columns\n .map((column, index) => {\n const activeIndex = column.groups.findIndex((group) => group.active);\n\n if (activeIndex === -1 || activeIndex === index) {\n return;\n }\n const active = column.groups[activeIndex];\n\n return {\n index,\n group: active.id,\n extended: active.extended,\n };\n })\n .filter(isDefined);\n }\n\n private getEventsState(grid: GridModel): StringDictionary {\n const state: StringDictionary = {};\n const until = now();\n\n const events = flatMap(grid.groups, (group) => group.events);\n const eventsMap = keyBy(events, (event) => event.id);\n\n // eslint-disable-next-line guard-for-in\n for (const id in grid.collapsed.events) {\n const event = eventsMap[id];\n const eventState = grid.collapsed.events[id];\n\n if (!event || event.cutOffDate.getTime() <= until) {\n continue;\n }\n\n if (eventState.collapsed || eventState.collapsedChildren.length > 0) {\n state[id] = {\n until: event.cutOffDate,\n collapsed: eventState.collapsed,\n collapsedChildren: eventState.collapsedChildren,\n };\n }\n }\n\n return state;\n }\n}\n","export enum AngstromTagIdValues {\n Sgp = 'SGP',\n Popular = 'Popular',\n}\n","/* eslint-disable max-lines */\nimport { Injectable } from '@angular/core';\n\nimport { OfferSource } from '@cds';\nimport { DisplayType, FixtureParticipantType } from '@cds/betting-offer';\nimport { BetBuilderOptionGroupInfo } from '@cds/betting-offer/domain-specific/bet-builder';\nimport { OfferGroup } from '@cds/betting-offer/grouping/fixture-view';\nimport {\n EnhancedFixtureViewGroupingConfiguration,\n EventOptionGroup,\n EventOptionGroupType,\n EventOptionSet,\n ExtendedDisplayType,\n Tv2MarketParameters,\n} from '@frontend/sports/betting-offer/feature/model';\nimport { AngstromTagIdValues } from '@frontend/sports/betting-offer/feature/types';\nimport { LoggerFactory, SportsRemoteLogger } from '@frontend/sports/common/core/feature/logging';\nimport { clone, findIndex, isEqual, sortBy, toNumber, uniq, uniqWith } from 'lodash-es';\n\ninterface GroupingFactory {\n canProcess(source: OfferSource | undefined): boolean;\n getMarketGroupsConfig(grouping: EnhancedFixtureViewGroupingConfiguration): OfferGroup[];\n}\n\nexport enum PreCreatedBuildABet {\n PreCreated = 'BAB',\n}\n\nclass GameMarketGroupingFactory implements GroupingFactory {\n canProcess(source: OfferSource | undefined): boolean {\n return source === OfferSource.V1;\n }\n\n getMarketGroupsConfig(grouping: EnhancedFixtureViewGroupingConfiguration): OfferGroup[] {\n return grouping.marketGroups;\n }\n}\n\nclass OptionMarketGroupingFactory implements GroupingFactory {\n canProcess(source: OfferSource | undefined): boolean {\n return source === OfferSource.V2;\n }\n\n getMarketGroupsConfig(grouping: EnhancedFixtureViewGroupingConfiguration): OfferGroup[] {\n return grouping.marketGroups;\n }\n}\n\n@Injectable({ providedIn: 'root' })\nexport class DetailedGroupingFactory {\n private factories = [new GameMarketGroupingFactory(), new OptionMarketGroupingFactory()];\n\n private readonly logger: SportsRemoteLogger;\n\n constructor(loggerFactory: LoggerFactory) {\n this.logger = loggerFactory.getLogger('DetailedGroupingFactory');\n }\n\n getOptionSets(\n optionGroups: EventOptionGroup[],\n grouping: EnhancedFixtureViewGroupingConfiguration,\n source: OfferSource | undefined,\n betBuilderId?: string | null,\n ): EventOptionSet[] {\n const optionSets: EventOptionSet[] = [];\n const marketGroups: OfferGroup[] = this.getMarketGroupsConfig(grouping, source);\n const isSgpGroup = (group: OfferGroup) => group.tag?.key === AngstromTagIdValues.Sgp;\n\n marketGroups.forEach((marketGroup) => {\n const selectedOptionGroups = optionGroups\n .filter((optionGroup) => optionGroup.detailedGrouping.marketSet === marketGroup.id)\n .filter((optionGroup) => (isSgpGroup(marketGroup) ? optionGroup.isBetBuilder : true));\n\n if (!selectedOptionGroups.length) {\n return;\n }\n\n const availableTags = selectedOptionGroups.flatMap((gr) => gr.detailedGrouping.tags || []);\n const tags = availableTags ? uniqWith(availableTags, isEqual) : [];\n optionSets.push({\n id: marketGroup.id,\n name: isSgpGroup(marketGroup) ? (marketGroup.tag?.value ?? marketGroup.name) : marketGroup.name,\n count: selectedOptionGroups.length,\n storageId: marketGroup.groupId,\n tags,\n marketGroupTag: marketGroup.tag,\n isPrecreated: selectedOptionGroups.every((og) => og.detailedGrouping?.displayType === ExtendedDisplayType.BetBuilder),\n });\n });\n\n if (!betBuilderId) {\n const sgpIndex = findIndex(optionSets, (e) => e.marketGroupTag?.key === AngstromTagIdValues.Sgp && e.name === e.marketGroupTag?.value);\n if (sgpIndex !== -1) {\n optionSets.splice(sgpIndex, 1);\n }\n }\n\n return this.fakeMarketGroupingIfNotFound(optionGroups, optionSets);\n }\n\n groupMarkets(optionGroups: EventOptionGroup[], restrictPeriodGrouping?: boolean, restrictSorting?: boolean): EventOptionGroup[] {\n const groupedMarkets: EventOptionGroup[] = [];\n\n const preSortedOptionGroups: EventOptionGroup[] = [];\n if (!restrictSorting) {\n preSortedOptionGroups.push(...this.sortMarkets(optionGroups));\n } else {\n preSortedOptionGroups.push(...optionGroups);\n }\n\n const marketSetIds = uniq(preSortedOptionGroups.map((optionGroup) => optionGroup.detailedGrouping.marketSet));\n marketSetIds.forEach((setId) => {\n const marketGroupMarkets = preSortedOptionGroups.filter((optionGroup) => optionGroup.detailedGrouping.marketSet === setId);\n const groupedMarketGroupMarkets: EventOptionGroup[] = [];\n\n groupedMarketGroupMarkets.push(\n ...marketGroupMarkets.filter(\n (market) => market.detailedGrouping.marketGroup === undefined && market.detailedGrouping.periodGroup === undefined,\n ),\n );\n if (restrictPeriodGrouping) {\n groupedMarketGroupMarkets.push(\n ...marketGroupMarkets.filter(\n (market) => market.detailedGrouping.marketGroup === undefined && market.detailedGrouping.periodGroup !== undefined,\n ),\n );\n } else {\n groupedMarketGroupMarkets.push(\n ...this.groupMarketsByPeriods(\n marketGroupMarkets.filter(\n (market) => market.detailedGrouping.marketGroup === undefined && market.detailedGrouping.periodGroup !== undefined,\n ),\n ),\n );\n }\n groupedMarketGroupMarkets.push(\n ...this.groupMarketsBySubGroups(marketGroupMarkets.filter((market) => market.detailedGrouping.marketGroup !== undefined)),\n );\n if (!restrictSorting) {\n groupedMarkets.push(...this.sortMarkets(groupedMarketGroupMarkets));\n } else {\n groupedMarkets.push(...groupedMarketGroupMarkets);\n }\n });\n\n return groupedMarkets;\n }\n\n transformEventGroupsPerPrecreatedGroup(\n optionGroups: EventOptionGroup[],\n precreatedOptionGroups: BetBuilderOptionGroupInfo[],\n ): EventOptionGroup[] {\n const transformedOptionGroups: EventOptionGroup[] = [];\n const marketSetIds = uniq(optionGroups.map((optionGroup) => optionGroup.detailedGrouping.marketSet));\n marketSetIds.forEach((setId) => {\n const optionGroupsBySetId = optionGroups.filter((optionGroup) => optionGroup.detailedGrouping.marketSet === setId);\n\n // we are transforming the event groups so that we end up with one event group per precreated group, with several options in it\n precreatedOptionGroups.forEach((precreatedGroup) => {\n const eventOptionGroups = optionGroupsBySetId.filter((market) =>\n precreatedGroup.options.some((o) => o.marketId.toString() === market.id),\n );\n\n if (eventOptionGroups.length === 0) {\n return;\n }\n\n if (eventOptionGroups.length !== precreatedGroup.options.length) {\n this.logger.warning(\n `Precreated group '${precreatedGroup.groupId}' resolved with invalid amount of options: expected ${precreatedGroup.options.length}, resolved ${eventOptionGroups.length}, fixture: ${precreatedGroup.options[0].fixtureId}`,\n );\n\n return;\n }\n\n const newGroupWithAllOptions = this.groupOptions(eventOptionGroups);\n newGroupWithAllOptions.id = precreatedGroup.groupId;\n transformedOptionGroups.push(newGroupWithAllOptions);\n });\n });\n\n return transformedOptionGroups;\n }\n\n createSubGroup(markets: EventOptionGroup[]): EventOptionGroup {\n const subGroupMarkets = this.sortSubGroupMarkets(markets);\n\n return this.groupOptions(subGroupMarkets);\n }\n\n private fakeMarketGroupingIfNotFound(optionGroups: EventOptionGroup[], optionSets: EventOptionSet[]): EventOptionSet[] {\n if (!optionSets.length && optionGroups.length) {\n // eslint-disable-next-line no-param-reassign\n optionGroups.forEach((g) => (g.detailedGrouping.marketSet = 10000));\n\n optionSets.push({\n id: 10000,\n name: 'All',\n count: optionGroups.length,\n storageId: '',\n });\n }\n\n return optionSets;\n }\n\n private groupMarketsBySubGroups(markets: EventOptionGroup[]): EventOptionGroup[] {\n const groupedMarkets: EventOptionGroup[] = [];\n const subGroupIds = uniq(markets.map((market) => this.getSubGroupId(market)));\n\n subGroupIds.forEach((subGroupId) => {\n const subGroupMarkets = markets.filter((market) => this.getSubGroupId(market) === subGroupId);\n const periodOptionGroup = this.getPeriodOptionGroup(subGroupMarkets);\n if (periodOptionGroup) {\n groupedMarkets.push(periodOptionGroup);\n } else {\n groupedMarkets.push(this.createSubGroup(subGroupMarkets));\n }\n });\n\n return groupedMarkets;\n }\n\n private getSubGroupId(market: EventOptionGroup): string | number {\n if (market.subPeriodNumber) {\n return `${market.detailedGrouping.marketOrder}sub-period${market.subPeriodNumber}`;\n }\n\n if (market.rangeValue) {\n return `${market.detailedGrouping.marketOrder}${market.rangeValue}`;\n }\n\n if (market.participantIds) {\n if (market.detailedGrouping.displayType === DisplayType.SixPack && !market.sixPackGridGroupId) {\n return `${market.parameters?.find((p) => p.key === Tv2MarketParameters.Happening)?.value}${market.detailedGrouping.displayType}${market.participantIds}`;\n }\n\n return `${market.detailedGrouping.marketOrder}${market.participantIds}`;\n }\n\n const fixtureParticipant = market.parameters?.find((param) => param.key === Tv2MarketParameters.FixtureParticipant);\n\n if (market.detailedGrouping.fixtureParticipantType === FixtureParticipantType.Other && fixtureParticipant) {\n return `${market.detailedGrouping.marketOrder}${fixtureParticipant.value}`;\n }\n\n return market.detailedGrouping.marketOrder;\n }\n\n protected getPeriodOptionGroup(markets: EventOptionGroup[]): EventOptionGroup | undefined {\n if (!markets) {\n return;\n }\n const periodMarkets = this.createEventOptionGroupByPeriodIdDictionary(markets);\n const periodKeys = sortBy(\n Object.keys(periodMarkets).map((x) => {\n return toNumber(x);\n }),\n );\n\n const periodGroupedMarkets: EventOptionGroup[] = [];\n if (periodKeys.length > 1) {\n periodKeys.forEach((k) => {\n periodGroupedMarkets.push(this.createSubGroup(periodMarkets[k]));\n });\n\n return this.createPeriodOptionGroup(periodGroupedMarkets[0], periodGroupedMarkets);\n } else if (periodKeys.length === 1) {\n periodGroupedMarkets[0] = this.groupOptions(periodMarkets[periodKeys[0]]);\n\n return this.singlePeriodOptionGroup(periodGroupedMarkets[0]);\n }\n\n return undefined;\n }\n\n protected createEventOptionGroupByPeriodIdDictionary(optionGroups: EventOptionGroup[]): Record {\n const res: EventOptionGroup[][] = [];\n for (const optionGroup of optionGroups) {\n if (typeof optionGroup.detailedGrouping.periodGroup === 'undefined') {\n continue;\n }\n res[optionGroup.detailedGrouping.periodGroup] = res[optionGroup.detailedGrouping.periodGroup] || [];\n res[optionGroup.detailedGrouping.periodGroup].push(optionGroup);\n }\n\n return res;\n }\n\n private createEventOptionGroupByMarketOrderIdWithPeriodDictionary(optionGroups: EventOptionGroup[]): EventOptionGroup[][] {\n const res: EventOptionGroup[][] = [];\n for (const optionGroup of optionGroups) {\n if (typeof optionGroup.detailedGrouping.marketOrder === 'undefined' || typeof optionGroup.detailedGrouping.periodGroup === 'undefined') {\n continue;\n }\n res[optionGroup.detailedGrouping.marketOrder] = res[optionGroup.detailedGrouping.marketOrder] || [];\n res[optionGroup.detailedGrouping.marketOrder].push(optionGroup);\n }\n\n return res;\n }\n\n private groupMarketsByPeriods(markets: EventOptionGroup[]): EventOptionGroup[] {\n const groupedMarkets: EventOptionGroup[] = [];\n\n const marketOrderMarkets = this.createEventOptionGroupByMarketOrderIdWithPeriodDictionary(markets);\n\n marketOrderMarkets.forEach((k) => {\n const periodOptionGroup = this.getPeriodOptionGroup(k);\n if (periodOptionGroup) {\n groupedMarkets.push(periodOptionGroup);\n } else {\n if (k[0]) {\n groupedMarkets.push(k[0]);\n }\n }\n });\n\n return groupedMarkets;\n }\n\n protected singlePeriodOptionGroup(periodGroup: EventOptionGroup): EventOptionGroup {\n // eslint-disable-next-line no-param-reassign\n periodGroup.id = this.getGroupId(periodGroup.detailedGrouping.displayType, periodGroup.id);\n // eslint-disable-next-line no-param-reassign\n periodGroup.name = this.getOptionGroupName(periodGroup);\n\n return periodGroup;\n }\n\n protected createPeriodOptionGroup(root: EventOptionGroup, subGroups: EventOptionGroup[]): EventOptionGroup {\n const periodGroup = clone(root);\n periodGroup.id = this.getGroupId(periodGroup.detailedGrouping.displayType, periodGroup.id);\n periodGroup.options = [];\n // eslint-disable-next-line no-param-reassign\n subGroups.forEach((s) => (s.type = EventOptionGroupType.Period));\n periodGroup.subGroups = subGroups;\n periodGroup.name = this.getOptionGroupName(periodGroup);\n\n if (\n periodGroup.detailedGrouping.displayType === DisplayType.PlayerMilestone ||\n periodGroup.detailedGrouping.displayType === DisplayType.PlayerMilestoneOverUnder\n ) {\n periodGroup.online = subGroups.some((x) => x.online);\n periodGroup.visible = subGroups.some((x) => x.visible);\n }\n\n return periodGroup;\n }\n\n protected groupOptions(markets: EventOptionGroup[]): EventOptionGroup {\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n const firstMarket = markets.shift()!;\n const optionGroup = Object.assign(new EventOptionGroup(), { ...firstMarket, options: [...firstMarket.options] });\n optionGroup.id = this.getGroupId(optionGroup.detailedGrouping.displayType, optionGroup.id, 'group');\n optionGroup.originalId = firstMarket.id;\n\n if (!markets.length) {\n if (this.isPlayer_OptionGroup(optionGroup)) {\n optionGroup.name = this.getOptionGroupName(optionGroup);\n }\n\n return optionGroup;\n }\n\n optionGroup.groupedMarkets = markets.map((m) => m.id);\n\n optionGroup.groupedTemplateIds = markets.map((m) => m.templateId);\n markets.forEach((market) => {\n optionGroup.options.push(...market.options);\n });\n\n optionGroup.online = optionGroup.options.some((group) => group.online);\n optionGroup.name = this.getOptionGroupName(optionGroup);\n\n return optionGroup;\n }\n\n private sortSubGroupMarkets(markets: EventOptionGroup[]): EventOptionGroup[] {\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n return markets.sort((first, second) => first.detailedGrouping.marketGroup! - second.detailedGrouping.marketGroup!);\n }\n\n private sortMarkets(markets: EventOptionGroup[]): EventOptionGroup[] {\n return markets\n .sort((first, second) => +first.id.slice(2) - +second.id.slice(2))\n .sort((first, second) => first.detailedGrouping.marketOrder - second.detailedGrouping.marketOrder);\n }\n\n private getMarketGroupsConfig(grouping: EnhancedFixtureViewGroupingConfiguration, source: OfferSource | undefined): OfferGroup[] {\n for (const factory of this.factories) {\n if (factory.canProcess(source)) {\n return factory.getMarketGroupsConfig(grouping);\n }\n }\n\n throw new Error('No grouping factory found for grouping configuration');\n }\n\n // eslint-disable-next-line complexity\n private getOptionGroupName(optionGroup: EventOptionGroup): string {\n if (optionGroup.detailedGrouping.periodGroup !== undefined && !optionGroup.subGroups) {\n switch (optionGroup.detailedGrouping.displayType) {\n case DisplayType.RaceTo:\n case DisplayType.BTTS:\n case DisplayType.Outrights:\n case DisplayType.TotalScore:\n case DisplayType.YesNo:\n case DisplayType.Spread:\n return optionGroup.detailedGrouping.name || optionGroup.name;\n case DisplayType.Regular:\n case DisplayType.CorrectScore:\n case DisplayType.OverUnder:\n return optionGroup.name;\n case DisplayType.PlayerProps:\n case DisplayType.SixPack:\n return optionGroup.sixPackGridGroupId\n ? optionGroup.periodName || optionGroup.name\n : `${optionGroup.detailedGrouping.name || optionGroup.name} - ${optionGroup.periodName}`;\n default:\n return optionGroup.detailedGrouping.name || optionGroup.name;\n }\n }\n if (optionGroup.detailedGrouping.name === PreCreatedBuildABet.PreCreated) {\n return optionGroup.name || optionGroup.detailedGrouping.name;\n } else {\n return optionGroup.detailedGrouping.name || optionGroup.name;\n }\n }\n\n private getGroupId(displayType: DisplayType | ExtendedDisplayType, groupId: string, groupType = 'period'): string {\n return `${groupType}-${displayType}-${groupId}`;\n }\n\n private isPlayer_OptionGroup(optionGroup: EventOptionGroup): boolean {\n const displayType = optionGroup.detailedGrouping.displayType;\n\n return (\n displayType === DisplayType.PlayerMilestone ||\n displayType === DisplayType.PlayerCombined ||\n displayType === DisplayType.PlayerStats ||\n displayType === DisplayType.PlayerMilestoneOverUnder\n );\n }\n}\n","import { Injectable } from '@angular/core';\n\nimport { OfferSource } from '@cds';\nimport { DetailedGrouping, DisplayType, Game, OptionMarket } from '@cds/betting-offer';\nimport { isEmpty } from 'lodash-es';\n\nexport abstract class MarketDisplayFallbackService {\n abstract shouldForceRegularDisplayTypeTV1(detailedGrouping: DetailedGrouping, game: Game): boolean;\n abstract shouldForceRegularDisplayTypeTV2(detailedGrouping: DetailedGrouping, optionMarket: OptionMarket): boolean;\n}\n\n@Injectable({\n providedIn: 'root',\n})\nexport class SportsMarketGroupingFallbackService extends MarketDisplayFallbackService {\n // For few displaytypes we need mandatory conditions to be met. If those conditions are not met we are converting these display types to regular.\n // To avoid the breakage of UI we are converting the new display types to regular type.\n shouldForceRegularDisplayTypeTV1(detailedGrouping: DetailedGrouping, game: Game): boolean {\n switch (detailedGrouping.displayType) {\n case DisplayType.YesNo:\n return isEmpty(game.player1);\n default:\n return false;\n }\n }\n\n shouldForceRegularDisplayTypeTV2(detailedGrouping: DetailedGrouping, optionMarket: OptionMarket): boolean {\n switch (detailedGrouping.displayType) {\n case DisplayType.YesNo:\n return optionMarket.source === OfferSource.V1 ? isEmpty(optionMarket.player1) : !optionMarket.fixtureParticipantId;\n default:\n return false;\n }\n }\n}\n","import { Injectable } from '@angular/core';\n\nimport { Tag } from '@cds/betting-offer/grouping/fixture-view';\nimport {\n EnhancedFixtureViewGroupingConfiguration,\n EventOptionGroup,\n EventOptionSet,\n EventSitemapSet,\n} from '@frontend/sports/betting-offer/feature/model';\nimport { AngstromTagIdValues } from '@frontend/sports/betting-offer/feature/types';\nimport { groupBy, isEqual, uniqWith } from 'lodash-es';\n\n@Injectable({ providedIn: 'root' })\nexport class EventSitemapGroupingFactory {\n getSitemapSet(\n optionSets: EventOptionSet[],\n grouping: EnhancedFixtureViewGroupingConfiguration,\n optionGroups: EventOptionGroup[],\n ): EventSitemapSet[] {\n const sets: EventSitemapSet[] = this.buildPrimaryLevelItems(optionSets);\n\n sets.forEach((s) => {\n const marketGroupsPerSet = grouping.marketGroups.filter((x) => (x.tag?.key ? x.tag.key === s.id : x.id === s.id));\n const marketGroupItems = marketGroupsPerSet?.map((m) => m.marketGroupItems).flat(1);\n\n if (marketGroupItems.length) {\n const children: EventSitemapSet[] = [];\n const hasTaggedGroupItems = marketGroupItems.some((m) => m.tags.length !== 0);\n\n if (hasTaggedGroupItems) {\n const optTags = optionGroups\n .filter((x) => (s.groups ? s.groups.includes(x.detailedGrouping.marketSet) : x.detailedGrouping.marketSet === s.id))\n .filter((x) => (s.id === AngstromTagIdValues.Sgp ? x.isBetBuilder : true))\n .filter((x) => !!x.detailedGrouping.tags)\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n .map((x) => x.detailedGrouping.tags!);\n const availableTags = uniqWith(optTags, isEqual);\n\n marketGroupItems?.forEach((groupItem) => {\n const tags = [...groupItem.tags];\n if (tags.length > 0 && this.tagsExist(tags, availableTags)) {\n this.buildLowerLevelItems(children, tags, s.groups);\n }\n });\n }\n\n // eslint-disable-next-line no-param-reassign\n s.children = children;\n }\n });\n\n return sets.filter((x) => (x.id === AngstromTagIdValues.Sgp ? !!x.children?.length : true));\n }\n\n private buildPrimaryLevelItems(optionSets: EventOptionSet[]): EventSitemapSet[] {\n const sets: EventSitemapSet[] = [];\n const groupedSets = groupBy(\n optionSets.filter((x) => !!x.marketGroupTag?.key),\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n (os) => os.marketGroupTag!.key,\n );\n\n // eslint-disable-next-line guard-for-in\n for (const tag in groupedSets) {\n const current = groupedSets[tag];\n const set: EventSitemapSet = {\n id: tag,\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n name: current[0].marketGroupTag!.value || current[0].marketGroupTag!.key,\n groups: current.map((c) => c.id),\n children: [],\n };\n\n sets.push(set);\n }\n\n optionSets\n .filter((x) => !x.marketGroupTag || !x.marketGroupTag.key)\n .forEach((o) =>\n // eslint-disable-next-line @typescript-eslint/consistent-type-assertions\n sets.push({\n ...o,\n groups: [o.id],\n children: [],\n } as EventSitemapSet),\n );\n\n return sets;\n }\n\n private buildLowerLevelItems(children: EventSitemapSet[], tags: Tag[], groups?: number[]): void {\n const keyExists = (key: string) => children.find((el) => el.id === key);\n const append = (tag: { key: string; value: string }) => {\n children.push({\n id: tag.key,\n name: tag.value || tag.key.toString(),\n children: [],\n groups,\n });\n };\n\n const currentTag = tags[0];\n if (!keyExists(currentTag.key)) {\n append(currentTag);\n }\n\n tags.shift();\n if (tags.length !== 0) {\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n const nextLevelItem = children.find((el) => el.id === currentTag.key)!;\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n this.buildLowerLevelItems(nextLevelItem.children!, tags, groups);\n }\n }\n\n private tagsExist(tags: Tag[], optionGrouping: Tag[][]): boolean {\n return optionGrouping.some((optTags) => {\n if (optTags.length !== tags.length) {\n return false;\n }\n\n return tags.every((tag, index) => tag.key === optTags[index].key);\n });\n }\n}\n","/* eslint-disable deprecation/deprecation */\n\n/* eslint-disable sonarjs/cognitive-complexity */\nimport { Inject, Injectable } from '@angular/core';\n\nimport { OfferSource } from '@cds';\nimport {\n DetailedGrouping,\n DisplayType,\n Game,\n Option,\n OptionMarket,\n OptionMarketStatus,\n OptionStatus,\n Parameter,\n Participant,\n ParticipantPriceType,\n TeamGrouping,\n TotalsPrefix,\n Translation,\n Visibility,\n} from '@cds/betting-offer';\nimport { OfferGroup, Tag } from '@cds/betting-offer/grouping/fixture-view';\nimport { SixPackGroup } from '@cds/betting-offer/grouping/grid-view';\nimport {\n ComboPrevention,\n ComboPreventionMinimum,\n ComboPreventionType,\n EnhancedFixtureViewGroupingConfiguration,\n EventOption,\n EventOptionGroup,\n EventOptionGrouping,\n EventParticipantImage,\n MapOption,\n ResultSorting,\n SetTab,\n Tv2MarketParameters,\n toResultSorting,\n} from '@frontend/sports/betting-offer/feature/model';\nimport { MarketSubType, MarketType } from '@frontend/sports/betting-offer/feature/types';\nimport { BetBuilderConfig, MarketGroupingConfig, Sitecore } from '@frontend/sports/common/client-config-data-access';\nimport { NonRootToken } from '@frontend/sports/host-app/sports-product/feature/non-root-token';\nimport { NativePrice } from '@frontend/sports/odds/feature/native-price';\nimport { replace } from 'lodash-es';\n\nimport { sortOptions } from '../helpers/option-sorting';\nimport { getOptionImage, getParticipantImageMap } from '../helpers/participant-image.helper';\nimport { CommonFixtureFactory } from './common-fixture.factory';\nimport { CommonMarketFactory } from './common-market.factory';\nimport { MarketDisplayFallbackService } from './sports-market-grouping-fallback.service';\n\nexport type DetailedFixtureMarket = Game | OptionMarket;\nconst optionAttributeRegex = /[-+]?\\d*[.,]?\\d+(?:[eE][-+]?\\d+)?/g;\n\ninterface MarketFactory {\n create(\n market: DetailedFixtureMarket,\n fixture: string,\n fixtureOnline: boolean,\n grouping: EnhancedFixtureViewGroupingConfiguration,\n ): EventOptionGroup[];\n\n canCreate(market: DetailedFixtureMarket): boolean;\n}\n\ninterface RegionalisedGrouping {\n offerGroup: OfferGroup;\n marketOrder: number;\n tags: Tag[];\n playerStatsProps: string[];\n teamGrouping: TeamGrouping;\n}\n\nabstract class MarketFactoryBase extends CommonMarketFactory {\n constructor(\n protected marketGroupingConfig: MarketGroupingConfig,\n betbuilderConfig: BetBuilderConfig,\n ) {\n super(betbuilderConfig);\n }\n\n protected getPeriodValue(\n game: Game | OptionMarket,\n detailedGrouping: DetailedGrouping,\n grouping: EnhancedFixtureViewGroupingConfiguration,\n sixPackGroup: SixPackGroup | null = null,\n ): string {\n if (detailedGrouping.displayType === DisplayType.PlayerMilestone || detailedGrouping.displayType === DisplayType.PlayerMilestoneOverUnder) {\n return this.replaceAttrValue(game as OptionMarket, detailedGrouping.displayType);\n }\n if (detailedGrouping.marketTabId !== undefined && grouping.marketTabs) {\n const marketTab = grouping.marketTabs.find((t) => t.id === detailedGrouping.marketTabId);\n if (marketTab) {\n return marketTab.name;\n }\n }\n if (sixPackGroup) {\n return sixPackGroup.name;\n }\n const period = 'parameters' in game ? (game.parameters.find((param) => param.key === Tv2MarketParameters.Period)?.value ?? '') : '';\n\n return grouping.parameterTranslations?.[`period${period}`] ?? '';\n }\n\n private replaceAttrValue(game: OptionMarket, displayType: DisplayType): string {\n return replace(game.attr, ',', '.') + (displayType === DisplayType.PlayerMilestone ? '+' : '');\n }\n\n protected getTranslatedValue(translation: Translation): string {\n return translation.short || translation.value;\n }\n\n protected mapDetailedGrouping(\n detailedGrouping: DetailedGrouping,\n grouping: EnhancedFixtureViewGroupingConfiguration,\n index: number,\n source: OfferSource,\n attrValue?: string,\n forceRegularDisplayType = false,\n isPriceBoosted = false,\n excludePriceboostedMarketGrouping = false,\n parameters: Parameter[] = [],\n sixPackGroup: { groupId: string; sixPack: SixPackGroup } | null = null,\n ): EventOptionGrouping {\n const regionalisedOfferGroup = this.getRegionalisedOfferGroup(detailedGrouping, grouping);\n // isRegular flagged is enabled for few display types, where the mandatory conditions doesn't meet to display in particular display type.\n // If those conditions are not met we are converting these display types to regular.\n const optionGrouping = {\n marketSet: regionalisedOfferGroup ? regionalisedOfferGroup.offerGroup.id : SetTab.Other,\n name: sixPackGroup ? detailedGrouping.sixPackGroupName : detailedGrouping.name,\n marketOrder: regionalisedOfferGroup ? regionalisedOfferGroup.marketOrder : this.getPrecreatedBABOrderPosition(grouping),\n periodGroup: this.getPeriodGroup(detailedGrouping, grouping, attrValue, parameters, sixPackGroup?.sixPack),\n marketGroup: detailedGrouping.subIndex,\n marketHelpPath: detailedGrouping.marketHelpPath,\n marketTabId: detailedGrouping.marketTabId,\n optionColumnId: detailedGrouping.optionColumnId,\n displayType: detailedGrouping.displayType,\n orderType: detailedGrouping.orderType,\n marketGroupId: detailedGrouping.marketGroupItemId,\n marketGroupItemId: detailedGrouping.marketGroupItemId,\n fixtureParticipantType: detailedGrouping.fixtureParticipantType,\n tags: regionalisedOfferGroup?.tags,\n playerStatsProps: regionalisedOfferGroup?.playerStatsProps,\n teamGrouping: regionalisedOfferGroup?.teamGrouping || undefined,\n sixPackGridId: sixPackGroup?.groupId,\n } satisfies EventOptionGrouping;\n if (forceRegularDisplayType) {\n optionGrouping.marketOrder = isPriceBoosted ? 0 : index;\n optionGrouping.periodGroup = undefined;\n optionGrouping.marketGroup = undefined;\n optionGrouping.displayType = DisplayType.Regular;\n if (isPriceBoosted && !excludePriceboostedMarketGrouping) {\n optionGrouping.marketGroup = Number.MAX_VALUE;\n }\n }\n\n return optionGrouping;\n }\n\n private getPrecreatedBABOrderPosition(grouping: EnhancedFixtureViewGroupingConfiguration): number {\n const tv1MarketGroupItemPosition = grouping.buildABetSettings.tv1MarketGroupItems?.marketGroupItemPosition;\n const tv2MarketGroupItemPosition = grouping.buildABetSettings.tv2MarketGroupItems?.marketGroupItemPosition;\n\n return tv1MarketGroupItemPosition || tv2MarketGroupItemPosition;\n }\n\n private getRegionalisedOfferGroup(\n detailedGrouping: DetailedGrouping,\n grouping: EnhancedFixtureViewGroupingConfiguration,\n ): RegionalisedGrouping | null {\n const regionalisedOfferGroup = grouping.marketGroups.find((mg) => mg.groupId === detailedGrouping.marketGroupId);\n if (!regionalisedOfferGroup) {\n return null;\n }\n const order = regionalisedOfferGroup.marketGroupItems.findIndex((m) => m.id === detailedGrouping.marketGroupItemId);\n if (order === -1) {\n return null;\n }\n\n if (!this.marketGroupingConfig.regionalisedGroupingEnabled || !detailedGrouping.marketGroupId || !detailedGrouping.marketGroupItemId) {\n return {\n offerGroup: {\n id: regionalisedOfferGroup.id,\n groupId: '',\n marketGroupItems: [],\n name: detailedGrouping.name || '',\n tag: { key: '', value: '' },\n },\n marketOrder: Number.MAX_VALUE,\n tags: [],\n playerStatsProps: [],\n teamGrouping: TeamGrouping.None,\n };\n }\n\n return {\n offerGroup: regionalisedOfferGroup,\n marketOrder: order,\n tags: regionalisedOfferGroup.marketGroupItems[order].tags,\n playerStatsProps: regionalisedOfferGroup.marketGroupItems[order].playerStatsProps,\n teamGrouping: regionalisedOfferGroup.marketGroupItems[order].teamGrouping,\n };\n }\n\n private getPeriodGroup(\n detailedGrouping: DetailedGrouping,\n grouping: EnhancedFixtureViewGroupingConfiguration,\n attrValue?: string,\n parameters?: Parameter[],\n sixPackGroup: SixPackGroup | null = null,\n ): number | undefined {\n if (\n !!attrValue &&\n (detailedGrouping.displayType === DisplayType.PlayerMilestone || detailedGrouping.displayType === DisplayType.PlayerMilestoneOverUnder) &&\n (!this.marketGroupingConfig.enablePlayerMilestoneByMarketSubType ||\n parameters?.find((p) => p.key === Tv2MarketParameters.MarketType)?.value !== MarketType.OverAndUnder ||\n parameters.find((p) => p.key === Tv2MarketParameters.MarketSubType)?.value === MarketSubType.YesNo)\n ) {\n if (isNaN(+attrValue)) {\n return +attrValue.replace(',', '.');\n }\n return +attrValue;\n }\n if (detailedGrouping.marketTabId !== undefined && grouping.marketTabs) {\n const marketTab = grouping.marketTabs.find((t) => t.id === detailedGrouping.marketTabId);\n if (marketTab) {\n return marketTab.sortOrder;\n }\n }\n if (sixPackGroup) {\n return grouping.sixPackGridGroups.findIndex((group) => group.id === sixPackGroup.id);\n }\n\n return undefined;\n }\n}\n\nclass GameMarketFactory extends MarketFactoryBase implements MarketFactory {\n constructor(\n private marketFallback: MarketDisplayFallbackService,\n marketGroupingConfig: MarketGroupingConfig,\n betbuilderConfig: BetBuilderConfig,\n ) {\n super(marketGroupingConfig, betbuilderConfig);\n }\n\n canCreate(market: DetailedFixtureMarket): market is Game & boolean {\n return 'visibility' in market;\n }\n\n create(\n market: DetailedFixtureMarket,\n fixture: string,\n fixtureOnline: boolean,\n grouping: EnhancedFixtureViewGroupingConfiguration,\n longIds?: string[],\n isPartOfSgpGroup?: boolean,\n participants?: Participant[],\n ): EventOptionGroup[] {\n return market.grouping.detailed.map((gameGrouping) =>\n this.createOptionGroup(market, fixture, fixtureOnline, gameGrouping, grouping, longIds, isPartOfSgpGroup, participants),\n );\n }\n\n //TODO please refactor\n // eslint-disable-next-line max-lines-per-function\n private createOptionGroup(\n market: DetailedFixtureMarket,\n fixture: string,\n fixtureOnline: boolean,\n gameGrouping: DetailedGrouping,\n grouping: EnhancedFixtureViewGroupingConfiguration,\n longIds?: string[],\n isPartOfSgpGroup?: boolean,\n participants?: Participant[],\n ): EventOptionGroup {\n const game = market as Game;\n const combo = new ComboPrevention();\n combo.minimum = ComboPreventionMinimum[game.combo2];\n combo.restriction = ComboPreventionType[game.combo1];\n const optionGroup = new EventOptionGroup();\n optionGroup.id = game.id.toString();\n optionGroup.originalId = game.id.toString();\n optionGroup.comboPrevention = combo;\n optionGroup.templateId = game.templateId?.toString() ?? '';\n optionGroup.name = this.getTranslatedValue(game.name);\n optionGroup.visible = game.visibility === Visibility.Visible;\n optionGroup.online = fixtureOnline && optionGroup.visible;\n optionGroup.hidden = game.visibility === Visibility.Hidden;\n optionGroup.detailedGrouping = this.mapDetailedGrouping(\n gameGrouping,\n grouping,\n game.id,\n OfferSource.V1,\n undefined,\n this.marketFallback.shouldForceRegularDisplayTypeTV1(gameGrouping, game),\n );\n optionGroup.periodName = this.getPeriodValue(game, gameGrouping, grouping);\n const categoryId = game.templateCategory && game.templateCategory.id;\n optionGroup.gridGroups = this.getGridGroups(game.grouping, categoryId);\n optionGroup.resultSorting = toResultSorting(game.resultOrder);\n const optionConfig: MapOption = {\n game,\n fixtureId: fixture,\n fixtureOnline,\n resultSorting: optionGroup.resultSorting,\n detailedGrouping: gameGrouping,\n grouping,\n order: gameGrouping.subIndex,\n participants,\n };\n optionGroup.options = this.mapOptions(optionConfig);\n optionGroup.categoryAttribute = game.attr;\n optionGroup.parameterTranslations = grouping.parameterTranslations;\n optionGroup.templateCategoryId = game.categoryId;\n optionGroup.templateCategory = game.category;\n optionGroup.player1 = game.player1 && this.getTranslatedValue(game.player1);\n optionGroup.player2 = game.player2 && this.getTranslatedValue(game.player2);\n optionGroup.isPartOfSgpGroup = !!isPartOfSgpGroup;\n optionGroup.source = OfferSource.V1;\n optionGroup.hasBetBuilderMarkets = !!game.isBetBuilder;\n this.patch(optionGroup, market);\n\n return optionGroup;\n }\n\n //TODO please refactor\n // eslint-disable-next-line max-lines-per-function\n private mapOptions(optionConfig: MapOption): EventOption[] {\n const { game, fixtureId, fixtureOnline, resultSorting, detailedGrouping, grouping, order, longIds, participants } = optionConfig;\n let participantImageMap = new Map();\n if (participants) {\n participantImageMap = getParticipantImageMap(participants);\n }\n // eslint-disable-next-line max-lines\n const options = game.results.map((result) => {\n const option = new EventOption();\n option.id = result.id;\n option.name = result.name.short || result.name.value;\n // eslint-disable-next-line deprecation/deprecation\n option.nativePrice = NativePrice.fromNativePrice(result);\n option.visible = result.visibility === Visibility.Visible && result.odds > 1 && game.visibility === Visibility.Visible;\n option.online = fixtureOnline && option.visible;\n option.optionGroupId = game.id;\n option.templateId = game.templateId;\n option.originalEventId = fixtureId;\n option.order = order;\n option.playerId = result.playerId;\n option.player1 = game.player1 && this.getTranslatedValue(game.player1);\n option.player1Id = game.player1Id;\n option.player2 = game.player2 && this.getTranslatedValue(game.player2);\n option.player2Id = game.player2Id;\n option.optionColumnId = detailedGrouping.optionColumnId;\n if (detailedGrouping.optionColumnId !== undefined && grouping.optionColumns) {\n const optionColumn = grouping.optionColumns.find((t) => t.id === detailedGrouping.optionColumnId);\n if (optionColumn) {\n option.optionColumnName = optionColumn.name;\n option.optionColumnSortOrder = optionColumn.sortOrder;\n }\n }\n option.signature = {\n event: '',\n league: '',\n market: this.getSign(game.name),\n option: this.getSign(result.name),\n region: '',\n sport: '',\n };\n option.overridenName = result.sourceName && result.sourceName.value;\n option.marketAttribute = game.attr;\n option.optionAttribute = result.attr;\n option.totalsPrefix = result.totalsPrefix;\n option.optionSpread = game.spread;\n option.isBetBuilder = !!game.isBetBuilder;\n if (longIds?.length) {\n option.longId = longIds.join(';');\n }\n\n if (participants) {\n option.image = getOptionImage(participants, option, participantImageMap);\n }\n return option;\n });\n\n return sortOptions(options, resultSorting);\n }\n\n private getSign(name: Translation): string {\n return (name.short && name.shortSign) || (name.value && name.sign) || '';\n }\n}\n\nclass OptionMarketFactory extends MarketFactoryBase implements MarketFactory {\n constructor(\n private marketFallback: MarketDisplayFallbackService,\n marketGroupingConfig: MarketGroupingConfig,\n private commonFactory: CommonFixtureFactory,\n betbuilderConfig: BetBuilderConfig,\n private sitecore: Sitecore,\n ) {\n super(marketGroupingConfig, betbuilderConfig);\n }\n\n canCreate(market: DetailedFixtureMarket): market is OptionMarket & boolean {\n return 'status' in market;\n }\n\n create(\n market: DetailedFixtureMarket,\n fixture: string,\n fixtureOnline: boolean,\n grouping: EnhancedFixtureViewGroupingConfiguration,\n longIds?: string[],\n isPartOfSgpGroup?: boolean,\n participants?: Participant[],\n isPriceBoosted?: boolean,\n excludePriceboostedMarketGrouping?: boolean,\n forceRegularDisplayType?: boolean,\n ): EventOptionGroup[] {\n return market.grouping.detailed.map((detailedGrouping) =>\n this.createOptionGroup(\n market,\n fixture,\n fixtureOnline,\n detailedGrouping,\n grouping,\n participants,\n isPriceBoosted,\n longIds,\n isPartOfSgpGroup,\n excludePriceboostedMarketGrouping,\n forceRegularDisplayType,\n ),\n );\n }\n\n //TODO PLEASE REFACTOR BELOW FUNCTION WHEN CHANGING IT\n // eslint-disable-next-line max-lines-per-function\n private createOptionGroup(\n market: DetailedFixtureMarket,\n fixture: string,\n fixtureOnline: boolean,\n detailedGrouping: DetailedGrouping,\n grouping: EnhancedFixtureViewGroupingConfiguration,\n participants: Participant[] | undefined,\n isPriceBoosted?: boolean,\n longIds?: string[],\n isPartOfSgpGroup?: boolean,\n excludePriceboostedMarketGrouping?: boolean,\n forceRegularDisplayType?: boolean,\n ): EventOptionGroup {\n const optionMarket = market as OptionMarket;\n const optionGroup = new EventOptionGroup();\n optionGroup.id = optionMarket.id.toString();\n const combo = new ComboPrevention();\n combo.minimum = optionMarket.minCombo ?? 1;\n combo.restriction = ComboPreventionType[optionMarket.comboPrevention];\n optionGroup.comboPrevention = combo;\n optionGroup.isEachWay = !!optionMarket.isEachWay;\n optionGroup.isStartingPriceAvailable = !!optionMarket.isStartingPriceEnabled;\n optionGroup.isWinPriceAvailable = !!optionMarket.isWinPriceEnabled;\n optionGroup.placeTerms = optionMarket.placeTerms;\n optionGroup.originalId = optionMarket.id.toString();\n optionGroup.templateCategory = optionMarket.templateCategory?.category;\n const categoryId = optionMarket.templateCategory?.id;\n optionGroup.gridGroups = this.getGridGroups(optionMarket.grouping, categoryId);\n const matchedSixPacks = this.getMatchedSixPacks(optionGroup, grouping, detailedGrouping);\n optionGroup.sixPackGridGroupId = matchedSixPacks?.sixPack.id;\n optionGroup.detailedGrouping = this.mapDetailedGrouping(\n detailedGrouping,\n grouping,\n optionMarket.id,\n OfferSource.V2,\n optionMarket.attr,\n (forceRegularDisplayType && this.marketFallback.shouldForceRegularDisplayTypeTV2(detailedGrouping, optionMarket)) || isPriceBoosted,\n isPriceBoosted,\n excludePriceboostedMarketGrouping,\n optionMarket.parameters,\n matchedSixPacks,\n );\n optionGroup.periodName = this.getPeriodValue(optionMarket, detailedGrouping, grouping, matchedSixPacks?.sixPack);\n optionGroup.name = this.getTranslatedValue(optionMarket.name);\n optionGroup.visible = optionMarket.status === OptionMarketStatus.Visible;\n optionGroup.online = fixtureOnline && optionGroup.visible;\n optionGroup.hidden = optionMarket.status === OptionMarketStatus.Hidden;\n optionGroup.resultSorting = optionMarket.resultOrder ? toResultSorting(optionMarket.resultOrder) : undefined;\n optionGroup.originalParameters = optionMarket.parameters; // ToDo: To be removed after cds obsolete markettype enums are updated in FE.\n optionGroup.player1 = optionMarket.player1 && this.getTranslatedValue(optionMarket.player1);\n optionGroup.player2 = optionMarket.player2 && this.getTranslatedValue(optionMarket.player2);\n optionGroup.source = optionMarket.source;\n optionGroup.options = this.mapOptions(\n optionMarket,\n fixture,\n fixtureOnline,\n detailedGrouping,\n grouping,\n optionGroup,\n detailedGrouping.subIndex,\n isPriceBoosted,\n longIds,\n participants,\n );\n\n if (optionGroup.resultSorting) {\n optionGroup.options = sortOptions(optionGroup.options, optionGroup.resultSorting);\n }\n\n if (optionMarket.fixtureParticipantId) {\n optionGroup.fixtureParticipantId = optionMarket.fixtureParticipantId;\n\n if (participants) {\n const overrideParticipantNameMarkets = this.commonFactory.shouldOverrideParticipantNames(optionMarket, grouping.sportId);\n const playerName = participants.find((x) => x.id === optionMarket.fixtureParticipantId)?.name;\n // when a new market is added, player is not available in push update.Hence removing the options\n if (!playerName && overrideParticipantNameMarkets) {\n optionGroup.options = [];\n } else {\n optionGroup.player1 = playerName && this.getTranslatedValue(playerName);\n optionGroup.options.forEach((opt: EventOption) => {\n // eslint-disable-next-line no-param-reassign\n opt.player1 = optionGroup.player1;\n if (\n overrideParticipantNameMarkets &&\n optionGroup.player1 &&\n detailedGrouping.displayType !== DisplayType.PlayerMilestone &&\n detailedGrouping.displayType !== DisplayType.PlayerMilestoneOverUnder\n ) {\n // eslint-disable-next-line no-param-reassign\n opt.name = optionGroup.player1;\n }\n });\n }\n }\n }\n optionGroup.parameters = optionMarket.parameters;\n optionGroup.categoryAttribute = optionMarket.attr;\n optionGroup.parameterTranslations = grouping.parameterTranslations;\n optionGroup.isPartOfSgpGroup = !!isPartOfSgpGroup;\n optionGroup.hasBetBuilderMarkets = !!optionMarket.isBetBuilder;\n\n const templateId = optionMarket.parameters.find((f) => f.key === Tv2MarketParameters.TemplateId);\n if (templateId) {\n optionGroup.templateId = templateId.value;\n }\n\n const templateCategoryId = optionMarket.parameters.find((f) => f.key === Tv2MarketParameters.CategoryId);\n if (templateCategoryId) {\n optionGroup.templateCategoryId = Number(templateCategoryId.value);\n }\n\n this.patch(optionGroup, market, participants, excludePriceboostedMarketGrouping);\n this.patchDetailedGrouping(optionGroup, market, grouping, participants, this.marketGroupingConfig.correctScoreOptionsSortOrder);\n\n return optionGroup;\n }\n\n // eslint-disable-next-line max-lines-per-function\n private mapOptions(\n optionMarket: OptionMarket,\n fixture: string,\n fixtureOnline: boolean,\n detailedGrouping: DetailedGrouping,\n grouping: EnhancedFixtureViewGroupingConfiguration,\n optionGroup: EventOptionGroup,\n order?: number,\n isPriceBoosted?: boolean,\n longIds?: string[],\n participants?: Participant[],\n ): EventOption[] {\n // eslint-disable-next-line no-param-reassign\n optionMarket.options = this.filterOptionsByMarketType(optionMarket, grouping.sportId);\n\n if (isPriceBoosted) {\n // eslint-disable-next-line no-param-reassign\n optionMarket.options = this.filterOptionsByBoostedPrice(optionMarket);\n }\n const participantImageMap = participants ? getParticipantImageMap(participants) : new Map();\n const hasHandicapParameter = this.hasHandicapParameter(optionMarket, detailedGrouping);\n\n //TODO please refactor\n // eslint-disable-next-line max-lines-per-function\n return optionMarket.options.map((current, index) => {\n const option = new EventOption();\n option.id = current.id;\n option.name = current.name.short || current.name.value;\n // eslint-disable-next-line deprecation/deprecation\n option.nativePrice = NativePrice.fromNativePrice(current.price);\n option.priceId = current.price.id;\n option.boostedPriceId = current.boostedPrice?.id;\n option.boostedPrice = current.boostedPrice && NativePrice.fromNativePrice(current.boostedPrice);\n option.visible =\n current.status === OptionStatus.Visible &&\n (current.participantPriceType === ParticipantPriceType.StartingPrice || option.nativePrice.greaterThan(1)) &&\n optionMarket.status === OptionMarketStatus.Visible;\n option.online = fixtureOnline && option.visible;\n option.optionGroupId = optionMarket.id;\n option.originalEventId = fixture;\n option.order = order;\n option.marketAttribute = optionMarket.attr;\n option.optionAttribute =\n optionMarket.source === OfferSource.V1\n ? current.attr\n : this.configureOptionAttribute(current, optionMarket, hasHandicapParameter, detailedGrouping, index);\n option.optionColumnId = detailedGrouping.optionColumnId;\n option.optionSpread = optionMarket.spread;\n option.player1 = optionMarket.player1 && this.getTranslatedValue(optionMarket.player1);\n option.player2 = optionMarket.player2 && this.getTranslatedValue(optionMarket.player2);\n option.player1Id = optionMarket.player1Id;\n option.player2Id = optionMarket.player2Id;\n if (detailedGrouping.optionColumnId !== undefined && grouping.optionColumns) {\n const columnConfig = this.getOptionColumnNameAndSorting(detailedGrouping, grouping, optionGroup);\n option.optionColumnName = columnConfig?.columnName;\n option.optionColumnSortOrder = columnConfig?.sorting;\n }\n option.playerId = current.participantId;\n option.participantId = optionMarket.fixtureParticipantId;\n option.optionPlayerId = current.parameters?.fixtureParticipant;\n option.optionTypes = current.parameters?.optionTypes;\n option.totalsPrefix =\n detailedGrouping.displayType === DisplayType.PlayerCombined\n ? (current.parameters?.optionTypes[0] as TotalsPrefix)\n : optionMarket.source === OfferSource.V1\n ? current.totalsPrefix\n : undefined;\n option.signature = {\n event: '',\n league: '',\n market: this.getSign(optionMarket.name),\n option: this.getSign(current.name),\n region: '',\n sport: '',\n };\n if (longIds?.length) {\n option.longId = longIds.join(';');\n }\n option.participantPriceType = current.participantPriceType;\n option.priceHistory = current.price.priceHistory;\n option.status = current.status;\n option.isBetBuilder = !!optionMarket.isBetBuilder;\n\n const templateId = optionMarket.parameters.find((f) => f.key === Tv2MarketParameters.TemplateId);\n if (templateId) {\n option.templateId = Number(templateId.value);\n }\n\n if (isPriceBoosted) {\n option.optionMarketName = this.getTranslatedValue(optionMarket.name);\n }\n\n if (participants) {\n option.image = getOptionImage(participants, option, participantImageMap);\n }\n return option;\n });\n }\n\n private getOptionColumnNameAndSorting(\n detailedGrouping: DetailedGrouping,\n grouping: EnhancedFixtureViewGroupingConfiguration,\n optionGroup: EventOptionGroup,\n ): { columnName: string; sorting: number } | null {\n if (optionGroup.sixPackGridGroupId) {\n const sixPack = grouping.sixPackGridGroups.find((s) => optionGroup.sixPackGridGroupId === s.id);\n if (!sixPack) {\n return null;\n }\n const index = sixPack.marketGroupIds.findIndex((i) => optionGroup.gridGroups?.includes(i));\n if (index !== -1) {\n return { columnName: sixPack.optionNames[index], sorting: index };\n }\n }\n const optionColumn = grouping.optionColumns.find((t) => t.id === detailedGrouping.optionColumnId);\n if (optionColumn) {\n return { columnName: optionColumn.name, sorting: optionColumn.sortOrder };\n }\n\n return null;\n }\n\n private filterOptionsByBoostedPrice(optionMarket: OptionMarket) {\n return optionMarket.options.filter((option) => option.boostedPrice && NativePrice.fromNativePrice(option.boostedPrice).greaterThan(1));\n }\n\n private filterOptionsByMarketType(optionMarket: OptionMarket, sportId: number): Option[] {\n const marketType = optionMarket.parameters.find((x) => x.key === Tv2MarketParameters.MarketType)?.value;\n const optionTypesToFilter = this.marketGroupingConfig.optionTypesToFilterTV2MarketOptions.find(\n (o) => o.sportId === sportId && o.marketType === marketType,\n )?.optionTypes;\n if (!optionTypesToFilter) {\n return optionMarket.options;\n }\n\n return optionMarket.options.filter((option) =>\n optionTypesToFilter.some((optionTypeFilters) =>\n optionTypeFilters.every((currentFilter) => option.parameters.optionTypes.includes(currentFilter)),\n ),\n );\n }\n\n private configureOptionAttribute(\n option: Option,\n optionMarket: OptionMarket,\n hasHandicapParameter: boolean,\n detailedGrouping: DetailedGrouping,\n index: number,\n ): string | undefined {\n switch (detailedGrouping.displayType) {\n case DisplayType.Spread:\n case DisplayType.SixPack: {\n const optionName = option.name.value.split(' ')[0];\n if (optionName === TotalsPrefix.Over || optionName === TotalsPrefix.Under) {\n const prefix =\n optionName === TotalsPrefix.Over ? this.sitecore.eventGrid.SixPack_TotalsOver : this.sitecore.eventGrid.SixPack_TotalsUnder;\n return `${prefix} ${optionMarket.attr}`;\n }\n\n if (hasHandicapParameter) {\n const optionAttribute = this.extractOptionAttribute(option.name.value);\n if (optionAttribute) {\n return optionAttribute;\n }\n }\n\n return detailedGrouping.displayType === DisplayType.SixPack ? ' ' : '0';\n }\n case DisplayType.PlayerMilestone:\n case DisplayType.PlayerMilestoneOverUnder:\n return optionMarket.options[index]?.parameters?.optionTypes[0];\n default:\n return undefined;\n }\n }\n\n private getSign(name: Translation): string {\n return (name.short && name.shortSign) || (name.value && name.sign) || '';\n }\n\n private getMatchedSixPacks(\n optionGroup: EventOptionGroup,\n grouping: EnhancedFixtureViewGroupingConfiguration,\n detailedGrouping: DetailedGrouping,\n ): { groupId: string; sixPack: SixPackGroup } | null {\n if (!detailedGrouping.isSixPackGroup) {\n return null;\n }\n return (\n grouping.sixPackGridGroups\n .map((sixPack) => {\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n const matchedGroup = optionGroup.gridGroups!.find((group) => sixPack.marketGroupIds.includes(group));\n return matchedGroup ? { groupId: matchedGroup, sixPack } : null;\n })\n .find((result) => result != null) || null\n );\n }\n\n private hasHandicapParameter(optionMarket: OptionMarket, detailedGrouping: DetailedGrouping): boolean {\n if (detailedGrouping.displayType === DisplayType.Spread || detailedGrouping.displayType === DisplayType.SixPack) {\n return optionMarket.parameters.some((x) => x.key === Tv2MarketParameters.DecimalHandicap || x.key === Tv2MarketParameters.Handicap);\n }\n\n return false;\n }\n\n private extractOptionAttribute(optionName: string): string | null {\n const match = optionName.match(optionAttributeRegex);\n\n if (!match) {\n return null;\n }\n\n // we care just about the first character - no point in using startsWith\n // eslint-disable-next-line @typescript-eslint/prefer-string-starts-ends-with\n return match[0][0] === '-' || match[0][0] === '+' ? match[0] : `+${match[0]}`;\n }\n}\n\n@Injectable({ providedIn: 'root' })\nexport class DetailedFixtureMarketFactory {\n constructor(\n @Inject(NonRootToken(MarketDisplayFallbackService)) private marketFallback: MarketDisplayFallbackService,\n private marketGroupingConfig: MarketGroupingConfig,\n private commonFactory: CommonFixtureFactory,\n private betbuilderConfig: BetBuilderConfig,\n private sitecore: Sitecore,\n ) {}\n\n private factories = [\n new GameMarketFactory(this.marketFallback, this.marketGroupingConfig, this.betbuilderConfig),\n new OptionMarketFactory(this.marketFallback, this.marketGroupingConfig, this.commonFactory, this.betbuilderConfig, this.sitecore),\n ];\n\n create(\n market: DetailedFixtureMarket,\n fixture: string,\n fixtureOnline: boolean,\n grouping: EnhancedFixtureViewGroupingConfiguration,\n participants?: Participant[],\n isPriceBoosted?: boolean,\n longIds?: string[],\n isPartOfSgpGroup?: boolean,\n excludePriceboostedMarketGrouping?: boolean,\n forceRegularDisplayType?: boolean,\n ): EventOptionGroup[] {\n for (const factory of this.factories) {\n if (factory.canCreate(market)) {\n return factory.create(\n market,\n fixture,\n fixtureOnline,\n grouping,\n longIds,\n isPartOfSgpGroup,\n participants,\n isPriceBoosted,\n excludePriceboostedMarketGrouping,\n forceRegularDisplayType,\n );\n }\n }\n throw new Error('No market factory found for market');\n }\n\n update(optionGroup: EventOptionGroup, update: EventOptionGroup, fixtureOnline: boolean, isPriceBoosted?: boolean): EventOptionGroup {\n const updatedGroup = Object.assign(new EventOptionGroup(), optionGroup);\n updatedGroup.comboPrevention = update.comboPrevention;\n updatedGroup.templateId = update.templateId;\n updatedGroup.hidden = update.hidden && !updatedGroup.groupedMarkets?.length;\n updatedGroup.gridGroups = update.gridGroups;\n updatedGroup.resultSorting = update.resultSorting;\n updatedGroup.detailedGrouping.periodGroup = update.detailedGrouping.periodGroup;\n // Update the market order only for betting offer not grouped explicitly for market grouping configuration.\n if (update.detailedGrouping.marketSet === SetTab.Other) {\n updatedGroup.detailedGrouping.marketOrder = update.detailedGrouping.marketOrder;\n }\n if (isPriceBoosted && update.options.length === 0) {\n updatedGroup.options = [];\n } else {\n this.updateOptions(updatedGroup.options, update.options, fixtureOnline);\n if (isPriceBoosted) {\n updatedGroup.options = this.filterAndUpdatePriceBoostedOptions(updatedGroup.options, updatedGroup.name);\n }\n }\n updatedGroup.options = sortOptions(updatedGroup.options, ResultSorting.Order, optionGroup.resultSorting);\n this.updateGroupMarketIds(updatedGroup, update);\n updatedGroup.online = fixtureOnline && (update.online || updatedGroup.options.some((option) => option.online));\n updatedGroup.player1 = update.player1;\n updatedGroup.player2 = update.player2;\n\n return updatedGroup;\n }\n\n private updateOptions(currentOptions: EventOption[], updateOptions: EventOption[], fixtureOnline: boolean): void {\n if (updateOptions.length === 0) {\n return;\n }\n const marketId = updateOptions[0].optionGroupId;\n const currentOptionsList = [...currentOptions];\n currentOptionsList.forEach((current) => {\n if (current.optionGroupId !== marketId) {\n return;\n }\n const currentOptionIndex = currentOptions.findIndex((option) => option.id === current.id);\n const update = updateOptions.find((updateOption) => updateOption.id === current.id);\n if (update) {\n // eslint-disable-next-line no-param-reassign\n currentOptions[currentOptionIndex] = this.createOption(update, fixtureOnline, current);\n } else {\n currentOptions.splice(currentOptionIndex, 1);\n }\n });\n updateOptions\n .filter((update) => !currentOptions.find((current) => current.optionGroupId === update.optionGroupId && current.id === update.id))\n .forEach((create) => {\n currentOptions.push(this.createOption(create, fixtureOnline, undefined));\n });\n }\n\n private updateGroupMarketIds(optionGroup: EventOptionGroup, update: EventOptionGroup): void {\n if (optionGroup.id !== update.id && optionGroup.groupedMarkets && !optionGroup.groupedMarkets.find((id) => id === update.id.toString())) {\n // eslint-disable-next-line no-param-reassign\n optionGroup.groupedMarkets = optionGroup.groupedMarkets || [];\n optionGroup.groupedMarkets.push(update.id);\n }\n }\n\n private createOption(update: EventOption | undefined, fixtureOnline: boolean, current: EventOption = new EventOption()): EventOption {\n if (!update) {\n return current;\n }\n // eslint-disable-next-line no-param-reassign\n current.id = update.id;\n // eslint-disable-next-line no-param-reassign\n current.name = update.name;\n // eslint-disable-next-line no-param-reassign\n current.nativePrice = update.nativePrice.clone();\n // eslint-disable-next-line no-param-reassign\n current.priceId = update.priceId;\n // eslint-disable-next-line no-param-reassign\n current.visible = !!update.visible;\n // eslint-disable-next-line no-param-reassign\n current.online = fixtureOnline && current.visible;\n // eslint-disable-next-line no-param-reassign\n current.optionGroupId = update.optionGroupId;\n // eslint-disable-next-line no-param-reassign\n current.originalEventId = update.originalEventId;\n // eslint-disable-next-line no-param-reassign\n current.order = update.order;\n // eslint-disable-next-line no-param-reassign\n current.optionColumnId = update.optionColumnId;\n // eslint-disable-next-line no-param-reassign\n current.signature = {\n event: '',\n league: '',\n market: update.signature.market,\n option: update.signature.option,\n region: '',\n sport: '',\n };\n // eslint-disable-next-line no-param-reassign\n current.marketAttribute = update.marketAttribute;\n // eslint-disable-next-line no-param-reassign\n current.optionAttribute = update.optionAttribute;\n // eslint-disable-next-line no-param-reassign\n current.optionSpread = update.optionSpread;\n // eslint-disable-next-line no-param-reassign\n current.player1 = update.player1;\n // eslint-disable-next-line no-param-reassign\n current.playerId = update.playerId;\n // eslint-disable-next-line no-param-reassign\n current.optionColumnName = update.optionColumnName;\n // eslint-disable-next-line no-param-reassign\n current.boostedPrice = update.boostedPrice;\n // eslint-disable-next-line no-param-reassign\n current.boostedPriceId = update.boostedPriceId;\n // eslint-disable-next-line no-param-reassign\n current.totalsPrefix = update.totalsPrefix;\n // eslint-disable-next-line no-param-reassign\n current.participantPriceType = update.participantPriceType;\n // eslint-disable-next-line no-param-reassign\n current.priceHistory = update.priceHistory;\n // eslint-disable-next-line no-param-reassign\n current.status = update.status;\n // eslint-disable-next-line no-param-reassign\n current.optionMarketName = update.optionMarketName;\n\n return current;\n }\n\n private filterAndUpdatePriceBoostedOptions(options: EventOption[], optionMarketName: string): EventOption[] {\n return options\n .filter((option) => option.boostedPrice?.greaterThan(1))\n .map((option) => {\n // eslint-disable-next-line no-param-reassign\n option.showBoostedPrice = true;\n // eslint-disable-next-line no-param-reassign\n option.optionMarketName = optionMarketName;\n\n return option;\n });\n }\n}\n","import { Injectable } from '@angular/core';\n\nimport { Fixture } from '@cds/betting-offer';\nimport { EventModel, EventOptionGroup } from '@frontend/sports/betting-offer/feature/model';\nimport { BettingOfferConfig } from '@frontend/sports/common/client-config-data-access';\n\n/**\n * Processing option groups for which there is a linkeFixture and configured mapping\n * Mapping between templateId and MarketType done in Dynacon\n *\n * @updateLinkedGroups flag OptionGroup if both conditions are true(linkedFixture & mapping),\n * used to show linked golf EW instead of the flagged golf optionGroup\n */\n@Injectable({ providedIn: 'root' })\nexport class LinkedFixtureService {\n constructor(private offerConfig: BettingOfferConfig) {}\n\n updateLinkedGroups(event: EventModel): EventOptionGroup[] {\n if (event.winGameLinkedFixture) {\n const fixtureMarketType = this.getMarketType(event.winGameLinkedFixture);\n\n event.optionGroups.forEach((og) => {\n const templateIdMapping = this.offerConfig.golfSiblingMapping[og.templateId];\n\n if (templateIdMapping === fixtureMarketType) {\n // eslint-disable-next-line no-param-reassign\n og.linkedSibling = true;\n }\n });\n }\n\n return event.optionGroups;\n }\n\n private getMarketType(fixture: Fixture): string {\n const participant = fixture.participants.find((p) => p.options.length);\n\n return participant?.options[0].marketType ?? '';\n }\n}\n","import { Injectable } from '@angular/core';\n\nimport { OfferSource } from '@cds';\nimport {\n DetailedGrouping,\n DisplayType,\n Fixture,\n Game,\n GroupingInfo,\n Option,\n OptionMarket,\n OptionMarketStatus,\n OptionStatus,\n OrderType,\n Parameter,\n ParameterType,\n Participant,\n Translation,\n} from '@cds/betting-offer';\nimport { BettingInsightsModel } from '@cds/betting-offer/betting-insights';\nimport { SplitFixture } from '@cds/betting-offer/domain-specific';\nimport { BetBuilderFixture } from '@cds/betting-offer/domain-specific/bet-builder';\nimport { ScoreboardSlim } from '@cds/scoreboards/v1';\nimport { PlayerStats } from '@cds/statistics/player';\nimport {\n EnhancedFixtureViewGroupingConfiguration,\n EventDetailsTags,\n EventModel,\n EventOptionGroup,\n ExtendedDisplayType,\n PRECREATED_BAB_EMPTY_THEME_ID,\n Tv2MarketParameters,\n getSourceEventId,\n} from '@frontend/sports/betting-offer/feature/model';\nimport { BetBuilderConfig, EventDetailsConfig, PriceBoostConfig, Sitecore } from '@frontend/sports/common/client-config-data-access';\nimport { SportConstant } from '@frontend/sports/common/core/data-access/constants';\nimport { NativePrice } from '@frontend/sports/odds/feature/native-price';\nimport { NativeAppService } from '@frontend/vanilla/core';\nimport { cloneDeep, flatten } from 'lodash-es';\n\nimport { DetailedGroupingFactory } from '../detailed-grouping.factory';\nimport { CommonFixtureFactory } from './common-fixture.factory';\nimport { DetailedFixtureMarket, DetailedFixtureMarketFactory } from './detailed-fixture-market.factory';\nimport { EventSitemapGroupingFactory } from './event-sitemap-grouping.factory';\nimport { LinkedFixtureService } from './linked-fixture.service';\n\nexport interface EventDetailsParameters {\n fixture: Fixture;\n splitFixtures: SplitFixture[];\n grouping: EnhancedFixtureViewGroupingConfiguration;\n precreated?: BetBuilderFixture;\n isPriceBoosted?: boolean;\n loadEventSitemap?: boolean;\n bettingInsights?: BettingInsightsModel;\n excludePriceboostedMarketGrouping?: boolean;\n}\n\nexport interface MarketGroupDetails {\n groupId: string;\n groupItemId: string | undefined;\n}\n\n@Injectable({ providedIn: 'root' })\nexport class DetailedCreateFactory {\n constructor(\n private fixtureFactory: CommonFixtureFactory,\n private marketFactory: DetailedFixtureMarketFactory,\n private groupingFactory: DetailedGroupingFactory,\n private eventSitemapGroupingFactory: EventSitemapGroupingFactory,\n private eventDetailsConfig: EventDetailsConfig,\n public sitecore: Sitecore,\n public betBuilderConfig: BetBuilderConfig,\n public linkedFixtureService: LinkedFixtureService,\n private nativeAppService: NativeAppService,\n private priceBoostConfig: PriceBoostConfig,\n ) {}\n\n create(eventParameters: EventDetailsParameters): EventModel {\n const event = this.fixtureFactory.create(eventParameters.fixture);\n const optionGroups = this.createOptionGroups(\n eventParameters.fixture,\n eventParameters.splitFixtures,\n eventParameters.grouping,\n eventParameters.precreated,\n eventParameters.isPriceBoosted,\n eventParameters.excludePriceboostedMarketGrouping,\n );\n const betBuilderId = event.getBetBuilderIdByStage();\n const optionSets = this.groupingFactory.getOptionSets(optionGroups, eventParameters.grouping, eventParameters.fixture.source, betBuilderId);\n\n this.setParticipantsShirtColor(event, eventParameters.fixture);\n\n event.marketsDictionary = this.createMarketsDictionary(optionGroups);\n event.splitFixtureIds = (eventParameters.splitFixtures && eventParameters.splitFixtures.map((splitFixture) => splitFixture.id)) || [];\n event.fixtureScoreboard = eventParameters.fixture.scoreboard as ScoreboardSlim;\n event.optionGroups = optionGroups;\n event.optionSets = optionSets;\n event.gamesCount = optionGroups.length;\n event.isPriceBoosted = eventParameters.isPriceBoosted;\n event.bettingInsights = eventParameters.bettingInsights;\n event.playerStats = this.getPlayerStats(eventParameters.fixture, eventParameters.splitFixtures);\n\n if (eventParameters.fixture.tv2LinkedFixture) {\n event.winGameLinkedFixture = eventParameters.fixture.tv2LinkedFixture;\n event.optionGroups = this.linkedFixtureService.updateLinkedGroups(event);\n }\n\n if (!!eventParameters.loadEventSitemap && this.eventDetailsConfig.enableSitemapNavigation) {\n event.sitemapSets = this.eventSitemapGroupingFactory.getSitemapSet(optionSets, eventParameters.grouping, optionGroups);\n }\n\n if (eventParameters.precreated?.precreatedGroupedInfo) {\n event.precreatedOptionGroups = eventParameters.precreated?.precreatedGroupedInfo;\n }\n\n return event;\n }\n\n //TODO please refactor this function if you are going to modify it, it's very complicated and must be refactored\n // eslint-disable-next-line max-lines-per-function, sonarjs/cognitive-complexity\n createOptionGroups(\n fixture: Fixture,\n splitFixtures: SplitFixture[],\n grouping: EnhancedFixtureViewGroupingConfiguration,\n precreated?: BetBuilderFixture,\n isPriceBoosted?: boolean,\n excludePriceboostedMarketGrouping?: boolean,\n forceRegularDisplayType = true,\n ): EventOptionGroup[] {\n const result: EventOptionGroup[] = [];\n const mapper = (id: string, source?: DetailedFixtureMarket[], participants?: Participant[], isPartOfSgpGroup?: boolean) => {\n if (!source) {\n return [];\n }\n\n return flatten(\n source.map((optionGroup) => {\n const longIds = precreated?.precreatedMarketsInfo.find((marketInfo) => marketInfo.id === optionGroup.id)?.selections;\n\n return this.marketFactory.create(\n optionGroup,\n id,\n fixture.isOpenForBetting,\n grouping,\n participants,\n isPriceBoosted,\n longIds,\n isPartOfSgpGroup,\n excludePriceboostedMarketGrouping,\n forceRegularDisplayType,\n );\n }),\n );\n };\n\n result.push(...mapper(fixture.id, fixture.games, fixture.participants));\n result.push(...mapper(fixture.id, fixture.optionMarkets, fixture.participants));\n\n if ((this.priceBoostConfig.isEnabled || this.priceBoostConfig.isEnabledAPB) && grouping.apbGroups && fixture.priceBoostCount > 0) {\n const priceBoostMarketGroup = this.priceBoostConfig.showAsTab\n ? grouping.apbGroups.filter((p) => p.tag?.key === EventDetailsTags.PriceBoost)\n : grouping.apbGroups.filter(\n (group) =>\n group.tag?.key !== EventDetailsTags.PriceBoost &&\n group.marketGroupItems.some((item) => item.tags.some((tag) => tag.key === EventDetailsTags.PriceBoost)),\n );\n\n if (priceBoostMarketGroup.length > 0) {\n const filteredData = priceBoostMarketGroup.map((data) => {\n const mappedData = {\n groupId: data.groupId,\n groupItemId: data.marketGroupItems.find((marketGroupItem) =>\n marketGroupItem.tags.some((tag) => tag.key === EventDetailsTags.PriceBoost),\n )?.id,\n };\n\n return mappedData;\n });\n\n if (filteredData.length > 0) {\n const automatedPriceBoostMarkets = this.getAutomatedPriceBoostedMarkets(filteredData, fixture.optionMarkets);\n result.push(...mapper(fixture.id, automatedPriceBoostMarkets));\n }\n }\n }\n\n if (\n this.betBuilderConfig.isBetBuilderEnabledForSport?.includes(fixture.sport.id) &&\n (this.nativeAppService.isTerminal || this.betBuilderConfig.themedTabs[fixture.sport.id])\n ) {\n const markets =\n precreated?.precreatedMarketsInfo.length && grouping.preCreatedGroups\n ? this.getBetBuilderMarketsToBeAppended(fixture, grouping, precreated)\n : fixture.optionMarkets;\n\n if (precreated?.sourceFixture?.id) {\n const precreatedFixtureId = getSourceEventId(precreated?.sourceFixture.id);\n result.push(...mapper(precreatedFixtureId, markets));\n }\n\n if (precreated?.precreatedGroupedInfo && grouping.preCreatedGroups) {\n const precreatedOptionGroups = this.getBetBuilderOptionGroupMarketsToBeAppended(fixture, grouping, precreated);\n\n result.push(...mapper(fixture.id, markets, undefined));\n result.push(...mapper(fixture.id, precreatedOptionGroups, undefined, true));\n }\n }\n\n splitFixtures.forEach((split) => {\n result.push(...mapper(split.id, split.games, fixture.participants));\n });\n\n return result;\n }\n\n private getAutomatedPriceBoostedMarkets(marketGroupItemsCollection: MarketGroupDetails[], optionMarkets: OptionMarket[]): OptionMarket[] {\n const boostedOptionMarkets: OptionMarket[] = [];\n\n marketGroupItemsCollection.map((data) => {\n if (data.groupItemId) {\n const parameters: Parameter[] = [\n {\n key: Tv2MarketParameters.MarketType,\n type: ParameterType.String,\n value: ExtendedDisplayType.AutomatedPriceBoost,\n },\n ];\n const detailsGroups: DetailedGrouping = {\n displayType: DisplayType.Regular,\n orderType: OrderType.None,\n marketHelpPath: '',\n marketGroupId: data.groupId,\n marketGroupItemId: data.groupItemId,\n contextInfo: '',\n };\n optionMarkets.reduce((res: OptionMarket[], optionMarket) => {\n const boostedOptions = optionMarket.options.filter(\n // eslint-disable-next-line deprecation/deprecation\n (option) => option.boostedPrice && NativePrice.fromNativePrice(option.boostedPrice).greaterThan(1),\n );\n\n if (boostedOptions.length) {\n const boostedOptionMarket = cloneDeep(optionMarket);\n boostedOptionMarket.options = cloneDeep(boostedOptions);\n boostedOptionMarket.grouping = {\n detailed: [detailsGroups],\n gridGroups: [],\n };\n boostedOptionMarket.parameters = parameters;\n res.push(boostedOptionMarket);\n }\n\n return res;\n }, boostedOptionMarkets);\n }\n });\n\n return boostedOptionMarkets;\n }\n\n private getPlayerStats(fixture: Fixture, splitFixtures: SplitFixture[]): PlayerStats[] {\n if (fixture.playerStats?.some((x) => x.source === OfferSource.V1)) {\n return fixture.playerStats;\n }\n\n const playerStats = fixture.playerStats ?? [];\n const splitWithStats = splitFixtures.find((split) => !!split.playerStats);\n\n if (splitWithStats) {\n playerStats.push(splitWithStats.playerStats);\n }\n\n return playerStats;\n }\n\n //TODO please refactor below function if you are going to modify anything within it.\n // eslint-disable-next-line max-lines-per-function\n private getBetBuilderMarketsToBeAppended(\n fixture: Fixture,\n grouping: EnhancedFixtureViewGroupingConfiguration,\n precreated?: BetBuilderFixture,\n ): OptionMarket[] {\n const precreatedModel = cloneDeep(precreated);\n if (precreatedModel?.precreatedMarketsInfo.length) {\n let optionMarkets: OptionMarket[] | undefined = [];\n optionMarkets = precreatedModel?.sourceFixture\n ? precreatedModel?.sourceFixture.optionMarkets.filter((market) => {\n return precreatedModel.precreatedMarketsInfo.some((f) => {\n return f.id === market.id;\n });\n })\n : [];\n const emptyMarketTabs = grouping.marketTabs.filter((x) => x.name === '');\n optionMarkets?.forEach((precreatedBAB) => {\n const themedName = precreatedModel?.precreatedMarketsInfo.find((x) => x.id === precreatedBAB.id);\n let marketTabId;\n if (!this.nativeAppService.isTerminal) {\n marketTabId = grouping.marketTabs.find((x) => x.name?.toUpperCase() === themedName?.themedTab?.toUpperCase());\n }\n if (marketTabId === undefined || marketTabId.name === '') {\n marketTabId = emptyMarketTabs[0];\n marketTabId.id = PRECREATED_BAB_EMPTY_THEME_ID;\n }\n const parameters: Parameter[] = [];\n const parameter: Parameter = {\n key: Tv2MarketParameters.MarketType,\n type: ParameterType.String,\n value: ExtendedDisplayType.BetBuilder,\n };\n parameters.push(parameter);\n const detailedGrouping: DetailedGrouping[] = [];\n const preCreatedMarketGroups = !this.betBuilderConfig.enablePrecreatedBetBuilderTab\n ? grouping.preCreatedGroups.filter((g) => g.tag?.key !== EventDetailsTags.PreCreatedBab)\n : grouping.preCreatedGroups;\n\n for (const group of preCreatedMarketGroups) {\n for (const marketGroupItem of group.marketGroupItems) {\n detailedGrouping.push({\n displayType: DisplayType.Regular,\n orderType: OrderType.None,\n marketTabId: marketTabId?.id,\n name: this.sitecore.betBuilder.messages?.PrecreatedBuildABetTitle,\n marketHelpPath: this.sitecore.betBuilder.messages?.PrecreatedBuildABetTitleHelpText ?? '',\n marketGroupId: group.groupId,\n contextInfo: '',\n marketGroupItemId: marketGroupItem.id,\n });\n }\n }\n const groupingInfo: GroupingInfo = {\n detailed: detailedGrouping,\n gridGroups: [],\n // eslint-disable-next-line max-lines\n };\n // eslint-disable-next-line no-param-reassign\n precreatedBAB.grouping = groupingInfo;\n const market = precreatedModel.precreatedMarketsInfo.find((x) => x.id === precreatedBAB.id);\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n const marketName: Translation = { sign: market!.themedTab, value: market!.themedTab };\n // eslint-disable-next-line no-param-reassign\n precreatedBAB.name = marketName;\n // eslint-disable-next-line no-param-reassign, @typescript-eslint/no-non-null-assertion\n precreatedBAB.id = market!.id;\n // eslint-disable-next-line no-param-reassign\n precreatedBAB.parameters = parameters;\n // eslint-disable-next-line no-param-reassign\n precreatedBAB.status = OptionMarketStatus.Visible;\n // eslint-disable-next-line no-param-reassign\n precreatedBAB.options = this.mapOptions(precreatedBAB, precreated);\n fixture.optionMarkets.push(precreatedBAB);\n });\n }\n\n return fixture.optionMarkets;\n }\n\n //TODO please refactor below function\n // eslint-disable-next-line max-lines-per-function\n private getBetBuilderOptionGroupMarketsToBeAppended(\n fixture: Fixture,\n grouping: EnhancedFixtureViewGroupingConfiguration,\n precreated?: BetBuilderFixture,\n ): DetailedFixtureMarket[] {\n const precreatedModel = cloneDeep(precreated);\n const emptyMarketTab = grouping.marketTabs.find((x) => x.name === '');\n const markets: DetailedFixtureMarket[] = [];\n\n if (precreatedModel?.precreatedGroupedInfo.length) {\n const preCreatedMarketGroups = !this.betBuilderConfig.enablePrecreatedBetBuilderTab\n ? grouping.preCreatedGroups.filter((g) => g.tag?.key !== EventDetailsTags.PreCreatedBab)\n : grouping.preCreatedGroups;\n // eslint-disable-next-line max-lines-per-function\n precreatedModel?.precreatedGroupedInfo.forEach((group) =>\n // eslint-disable-next-line max-lines-per-function, @typescript-eslint/no-confusing-void-expression, sonarjs/cognitive-complexity\n group.options.forEach((option) => {\n const precreatedBAB = fixture.games.length\n ? fixture.games.find((market) => market.id === option.marketId)\n : fixture.optionMarkets.find((market) => market.id === option.marketId);\n if (!precreatedBAB) {\n return;\n }\n const precreatedBABModel = cloneDeep(precreatedBAB);\n const detailedGrouping: DetailedGrouping[] = [];\n for (const preCreatedGroup of preCreatedMarketGroups) {\n for (const marketGroupItem of preCreatedGroup.marketGroupItems) {\n detailedGrouping.push({\n displayType: DisplayType.Regular,\n orderType: OrderType.None,\n marketTabId: emptyMarketTab?.id,\n name: this.sitecore.betBuilder.messages?.PrecreatedBuildABetTitle,\n marketHelpPath: this.sitecore.betBuilder.messages?.PrecreatedBuildABetTitleHelpText ?? '',\n marketGroupId: preCreatedGroup.groupId,\n contextInfo: '',\n marketGroupItemId: marketGroupItem.id,\n });\n }\n }\n const groupingInfo: GroupingInfo = {\n detailed: detailedGrouping,\n\n gridGroups: [],\n };\n precreatedBABModel.grouping = groupingInfo;\n // precreatedBABModel is TV1 Game\n if ('visibility' in precreatedBABModel) {\n const foundOption = precreatedBABModel.results.find((o) => o.id === option.id);\n\n if (foundOption) {\n const foundMarket = markets.find((market) => market.id === precreatedBABModel.id) as Game | undefined;\n if (foundMarket) {\n foundMarket.results.push(foundOption);\n } else {\n precreatedBABModel.results = [foundOption];\n markets.push(precreatedBABModel);\n }\n }\n }\n // precreatedBABModel is TV2 OptionMarket\n if ('status' in precreatedBABModel) {\n const parameters: Parameter[] = [\n {\n key: Tv2MarketParameters.MarketType,\n type: ParameterType.String,\n value: ExtendedDisplayType.BetBuilder,\n },\n ];\n precreatedBABModel.parameters = parameters;\n const foundOption = precreatedBABModel.options.find((o) => o.id === option.id);\n\n if (foundOption) {\n const foundMarket = markets.find((market) => market.id === precreatedBABModel.id) as OptionMarket | undefined;\n if (foundMarket) {\n foundMarket.options.push(foundOption);\n } else {\n precreatedBABModel.options = [foundOption];\n markets.push(precreatedBABModel);\n }\n }\n }\n }),\n );\n }\n\n return markets;\n }\n\n private mapOptions(precreatedBAB: OptionMarket, precreated: BetBuilderFixture | undefined): Option[] {\n return precreatedBAB.options.map((option) => {\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n const currentMarketName = precreated?.sourceFixture.optionMarkets.find((x) => x.id === precreatedBAB.id)!.name;\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n const optionMarketName: Translation = { sign: currentMarketName!.sign, value: currentMarketName!.value };\n\n return {\n ...option,\n price: option.price,\n name: optionMarketName,\n status: OptionStatus.Visible,\n sourceName: option.sourceName,\n id: option.id,\n };\n });\n }\n\n private createMarketsDictionary(optionGroups: EventOptionGroup[]): Record {\n return optionGroups.reduce>((marketsDictionary, group) => {\n const ids = [group.id, ...(group.groupedMarkets || [])];\n\n ids.forEach((id) => {\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n if (!marketsDictionary[id]) {\n // eslint-disable-next-line no-param-reassign\n marketsDictionary[id] = [];\n }\n\n marketsDictionary[id].push(group);\n });\n\n return marketsDictionary;\n }, {});\n }\n\n private setParticipantsShirtColor(eventModel: EventModel, fixture: Fixture): void {\n const animationSports = [SportConstant.Basketball, SportConstant.Tennis, SportConstant.Icehockey];\n const fallbackColors: string[] = this.eventDetailsConfig.teamIndicatorFallbackColors.slice(0, 2);\n //TODO any index issue, probably a defect, please consider adding player info to cds scoreboard type\n const hasPlayerInfo = fixture.scoreboard && fixture.scoreboard['playerInfo' as keyof typeof fixture.scoreboard];\n // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment\n const playerInfo = hasPlayerInfo && (fixture.scoreboard['playerInfo' as keyof typeof fixture.scoreboard] as any);\n // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access\n const isColorsAvailable = hasPlayerInfo && playerInfo.colorsAvailable;\n\n // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access\n const areTeamColorsSame = isColorsAvailable && playerInfo.players[`01`].shirtColor === playerInfo.players[`02`].shirtColor;\n\n eventModel.participants.forEach((participant, index) => {\n // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, no-param-reassign\n participant.shirtColor =\n isColorsAvailable && !areTeamColorsSame\n ? // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access\n playerInfo.players[`0${index + 1}`].shirtColor\n : animationSports.indexOf(eventModel.sport.id) > -1 || areTeamColorsSame\n ? fallbackColors[index]\n : null;\n });\n }\n}\n","/* eslint-disable @typescript-eslint/no-unused-expressions */\n\n/* eslint-disable no-param-reassign */\nimport { Injectable } from '@angular/core';\n\nimport { EventModel, EventOptionGroup } from '@frontend/sports/betting-offer/feature/model';\n\n@Injectable({ providedIn: 'root' })\nexport class DetailedDeleteFactory {\n deleteOptionGroup(event: EventModel, optionGroupId: number): void {\n const groupsWithSubgroup =\n event.marketsDictionary &&\n event.marketsDictionary[optionGroupId] &&\n event.marketsDictionary[optionGroupId].filter((group) => group.detailedGrouping && group.detailedGrouping.marketGroup !== undefined);\n\n groupsWithSubgroup &&\n groupsWithSubgroup.forEach((group) => {\n this.deleteOptions(group, optionGroupId);\n if (group.groupedMarkets && !group.groupedMarkets.length) {\n delete group.groupedMarkets;\n }\n });\n\n // eslint-disable-next-line @typescript-eslint/no-dynamic-delete\n delete event.marketsDictionary[optionGroupId];\n event.optionGroups = event.optionGroups.filter((m) => m.id !== optionGroupId.toString() && m.id !== '');\n }\n\n private deleteOptions(optionGroup: EventOptionGroup, deleteId: number): void {\n optionGroup.options = optionGroup.options.filter((option) => option.optionGroupId !== deleteId);\n\n if (optionGroup.id === deleteId.toString() && optionGroup.groupedMarkets && optionGroup.groupedMarkets.length && optionGroup.options.length) {\n optionGroup.id = optionGroup.groupedMarkets.shift() || '';\n } else {\n optionGroup.groupedMarkets =\n optionGroup && optionGroup.groupedMarkets && optionGroup.groupedMarkets.filter((id) => id !== deleteId.toString());\n }\n }\n}\n","import { Injectable } from '@angular/core';\n\nimport { OfferSource } from '@cds';\nimport { FixtureStage, HybridFixtureStatus, OptionMarket, Participant, ParticipantType } from '@cds/betting-offer';\nimport { ScoreboardSlim } from '@cds/scoreboards/v1';\nimport {\n EnhancedFixtureViewGroupingConfiguration,\n EventModel,\n EventOptionGroup,\n EventParticipantType,\n ExtendedDisplayType,\n SetTab,\n} from '@frontend/sports/betting-offer/feature/model';\nimport { clone, includes } from 'lodash-es';\n\nimport { CommonFixtureFactory } from './common-fixture.factory';\nimport { DetailedFixtureMarket, DetailedFixtureMarketFactory } from './detailed-fixture-market.factory';\nimport { ScoreboardFactory } from './scoreboard-factory.service';\n\n@Injectable({ providedIn: 'root' })\nexport class DetailedUpdateFactory {\n constructor(\n private marketFactory: DetailedFixtureMarketFactory,\n private commonFactory: CommonFixtureFactory,\n private scoreboardFactory: ScoreboardFactory,\n ) {}\n\n update(model: EventModel, openForBetting: boolean, stage: FixtureStage): void {\n this.commonFactory.update(model, openForBetting, stage);\n }\n\n updateScoreboard(fixtureScoreboard: ScoreboardSlim, model: EventModel): void {\n // eslint-disable-next-line no-param-reassign\n model.scoreboard = this.scoreboardFactory.createScoreboard(fixtureScoreboard, model);\n }\n\n updateOptionGroup(\n event: EventModel,\n fixtureMarket: DetailedFixtureMarket,\n fixtureId: string,\n grouping: EnhancedFixtureViewGroupingConfiguration,\n ): void {\n const overrideParticipantNameMarkets =\n (event.offerSource === OfferSource.V2 || event.hybridFixtureData?.status === HybridFixtureStatus.Ok) &&\n this.commonFactory.shouldOverrideParticipantNames(fixtureMarket as OptionMarket, grouping.sportId);\n const participants = this.mapParticipants(event, overrideParticipantNameMarkets);\n const updates = this.marketFactory.create(fixtureMarket, fixtureId, event.online, grouping, participants, event.isPriceBoosted);\n const id = fixtureMarket.id.toString();\n\n // eslint-disable-next-line no-param-reassign\n event.optionGroups = this.updateMarkets(event.optionGroups, updates, event.online, event.isPriceBoosted);\n\n if (overrideParticipantNameMarkets || event.isPriceBoosted) {\n // eslint-disable-next-line no-param-reassign\n event.optionGroups = event.optionGroups.filter((opt) => opt.options.length);\n }\n // eslint-disable-next-line no-param-reassign\n event.marketsDictionary[id] = event.optionGroups.filter((optionGroup) => optionGroup.id === id || includes(optionGroup.groupedMarkets, id));\n }\n\n private mapParticipants(event: EventModel, overrideParticipantNameMarkets: boolean): Participant[] | undefined {\n if (!overrideParticipantNameMarkets) {\n return;\n }\n\n const players = event.players.map(\n (p) =>\n // eslint-disable-next-line @typescript-eslint/consistent-type-assertions\n ({\n id: p.fixtureParticipantId,\n image: clone(p.image),\n name: { short: p.shortName, value: p.name },\n participantId: p.id,\n properties: { team: p.teamId },\n }) as Participant,\n );\n\n if (event.hybridFixtureData) {\n const participants = event.participants\n .filter((x) => x.type === EventParticipantType.Away || x.type === EventParticipantType.Home)\n .map(\n (p) =>\n // eslint-disable-next-line @typescript-eslint/consistent-type-assertions\n ({\n id: p.id,\n image: clone(p.image),\n name: { short: p.shortName, value: p.name },\n participantId: p.id,\n properties: {\n team: p.teamId,\n type: p.type === EventParticipantType.Home ? ParticipantType.HomeTeam : ParticipantType.AwayTeam,\n },\n }) as Participant,\n );\n\n return [...players, ...participants];\n }\n\n return players;\n }\n\n private updateMarkets(\n currentOptionGroups: EventOptionGroup[],\n updateOptionGroups: EventOptionGroup[],\n fixtureOnline: boolean,\n isPriceBoosted?: boolean,\n ): EventOptionGroup[] {\n updateOptionGroups.forEach((update) => {\n const indexes = currentOptionGroups.reduce(\n (acc: number[], curr: EventOptionGroup, i: number) =>\n curr.id === update.id &&\n curr.detailedGrouping.marketSet === update.detailedGrouping.marketSet &&\n (curr.detailedGrouping.marketSet !== SetTab.Other\n ? curr.detailedGrouping.marketOrder === update.detailedGrouping.marketOrder\n : true)\n ? [...acc, i]\n : acc,\n [],\n );\n\n if (!indexes.length) {\n currentOptionGroups.push(update);\n } else {\n indexes.forEach((index) => {\n // eslint-disable-next-line no-param-reassign\n currentOptionGroups[index] = this.marketFactory.update(currentOptionGroups[index], update, fixtureOnline, isPriceBoosted);\n\n if (!currentOptionGroups[index].options.length) {\n currentOptionGroups.splice(index, 1);\n }\n });\n\n this.updateAutomatedPriceBoostedMarket(currentOptionGroups, update, fixtureOnline);\n }\n });\n\n return [...currentOptionGroups];\n }\n\n private updateAutomatedPriceBoostedMarket(\n currentOptionGroups: EventOptionGroup[],\n updateOptionGroup: EventOptionGroup,\n fixtureOnline: boolean,\n ): void {\n const automatedPriceBoostIndex = currentOptionGroups.findIndex(\n (og) => og.id === updateOptionGroup.id && og.detailedGrouping.displayType === ExtendedDisplayType.AutomatedPriceBoost,\n );\n\n if (automatedPriceBoostIndex > -1) {\n const currentDetailedGrouping = clone(currentOptionGroups[automatedPriceBoostIndex].detailedGrouping);\n // eslint-disable-next-line no-param-reassign\n currentOptionGroups[automatedPriceBoostIndex] = this.marketFactory.update(\n currentOptionGroups[automatedPriceBoostIndex],\n updateOptionGroup,\n fixtureOnline,\n true,\n );\n // eslint-disable-next-line no-param-reassign\n currentOptionGroups[automatedPriceBoostIndex].detailedGrouping = currentDetailedGrouping;\n }\n }\n}\n","import { Injectable } from '@angular/core';\n\nimport { FixtureStage } from '@cds/betting-offer';\nimport { ScoreboardSlim } from '@cds/scoreboards/v1';\nimport { EnhancedFixtureViewGroupingConfiguration, EventModel } from '@frontend/sports/betting-offer/feature/model';\nimport { EventDetailsConfig } from '@frontend/sports/common/client-config-data-access';\n\nimport { DetailedGroupingFactory } from '../detailed-grouping.factory';\nimport { DetailedCreateFactory, EventDetailsParameters } from './detailed-create.factory';\nimport { DetailedDeleteFactory } from './detailed-delete.factory';\nimport { DetailedFixtureMarket } from './detailed-fixture-market.factory';\nimport { DetailedUpdateFactory } from './detailed-update.factory';\nimport { EventSitemapGroupingFactory } from './event-sitemap-grouping.factory';\n\n@Injectable({ providedIn: 'root' })\nexport class DetailedFixtureFactory {\n constructor(\n private groupingFactory: DetailedGroupingFactory,\n private createFactory: DetailedCreateFactory,\n private updateFactory: DetailedUpdateFactory,\n private deleteFactory: DetailedDeleteFactory,\n private eventDetailsConfig: EventDetailsConfig,\n private eventSitemapGroupingFactory: EventSitemapGroupingFactory,\n ) {}\n\n create(eventParameters: EventDetailsParameters): EventModel {\n return this.createFactory.create(eventParameters);\n }\n\n update(model: EventModel, openForBetting: boolean, stage: FixtureStage): void {\n this.updateFactory.update(model, openForBetting, stage);\n }\n\n updateScoreboard(fixtureScoreboard: ScoreboardSlim, model: EventModel): void {\n this.updateFactory.updateScoreboard(fixtureScoreboard, model);\n }\n\n updateOptionGroup(\n event: EventModel,\n fixtureMarket: DetailedFixtureMarket,\n fixtureId: string,\n grouping: EnhancedFixtureViewGroupingConfiguration,\n ): void {\n this.updateFactory.updateOptionGroup(event, fixtureMarket, fixtureId, grouping);\n this.refreshModel(event, grouping);\n }\n\n deleteOptionGroup(event: EventModel, optionGroupId: number, grouping: EnhancedFixtureViewGroupingConfiguration): void {\n this.deleteFactory.deleteOptionGroup(event, optionGroupId);\n this.refreshModel(event, grouping);\n }\n\n private refreshModel(event: EventModel, grouping: EnhancedFixtureViewGroupingConfiguration): void {\n const betBuilderId = event.getBetBuilderIdByStage();\n\n // eslint-disable-next-line no-param-reassign\n event.optionSets = this.groupingFactory.getOptionSets(event.optionGroups, grouping, event.offerSource, betBuilderId);\n // eslint-disable-next-line no-param-reassign\n event.gamesCount = event.optionGroups.length;\n\n if (this.eventDetailsConfig.enableSitemapNavigation) {\n // eslint-disable-next-line no-param-reassign\n event.sitemapSets = this.eventSitemapGroupingFactory.getSitemapSet(event.optionSets, grouping, event.optionGroups);\n }\n }\n}\n","import { Injectable } from '@angular/core';\nimport { takeUntilDestroyed } from '@angular/core/rxjs-interop';\n\nimport { OfferSource } from '@cds';\nimport { FavouriteType, FavouritesService, FavouritesViewModel, isFixtureFavourite } from '@frontend/sports/favourites/core/feature';\nimport { GridEvent } from '@frontend/sports/grid/core/feature/model';\nimport { GridActions, IGridRootState, IGridState, gridStateSelector } from '@frontend/sports/grid/core/feature/store';\nimport { filterSportsEmitLast } from '@frontend/sports/host-app/sports-product/feature/utils';\nimport { UserService } from '@frontend/sports/user/feature';\nimport { Store } from '@ngrx/store';\nimport { difference, flattenDeep, values } from 'lodash-es';\nimport { filter, map, pairwise, withLatestFrom } from 'rxjs/operators';\n\n@Injectable({ providedIn: 'root' })\nexport class GridFavouriteService {\n constructor(\n private favouriteService: FavouritesService,\n private store: Store,\n private user: UserService,\n ) {\n this.favouriteService.change\n .pipe(\n filterSportsEmitLast(),\n map((change) => change.favourites.filter(isFixtureFavourite)),\n pairwise(),\n map(([previousChange, currentChange]) => [\n ...difference(previousChange, currentChange), // added stuff\n ...difference(currentChange, previousChange), // removed stuff\n ]),\n filter((change) => change.length > 0),\n withLatestFrom(this.store.select(gridStateSelector)),\n takeUntilDestroyed(),\n )\n .subscribe(([change, state]) => {\n this.updateFavourited(change, state);\n });\n }\n\n isFavourited(event: GridEvent): boolean | undefined {\n if (!this.user.isAuthenticated) {\n return;\n }\n\n return !!this.favouriteService.get(event.id, event.sport.id, this.getType(event));\n }\n\n private updateFavourited(diff: FavouritesViewModel[], state: IGridState): void {\n if (diff.length === 0) {\n return;\n }\n\n const grids = values(state);\n\n for (const grid of grids) {\n const events = flattenDeep(grid.groups.map((group) => group.events));\n const changed = events.filter((event) => diff.some((c) => c.itemId === event.id));\n\n for (const change of changed) {\n change.favourited = this.isFavourited(change);\n this.store.dispatch(\n GridActions.updateEvents({\n payload: {\n id: grid.id,\n event: change,\n },\n }),\n );\n }\n }\n }\n\n private getType(event: GridEvent): FavouriteType {\n if (!event.offerSource || event.offerSource === OfferSource.V1) {\n return FavouriteType.Fixture;\n }\n\n return FavouriteType.FixtureV2;\n }\n}\n","import { GridEvent } from '@frontend/sports/grid/core/feature/model';\nimport { Observable, of } from 'rxjs';\n\nimport { BaseFactory, GridSourceEvent } from './event.factory';\n\nexport class GridBaseFactory implements BaseFactory {\n canCreate(events: GridSourceEvent[]): boolean {\n return 'optionGroups' in events[0];\n }\n\n create(events: GridSourceEvent[]): Observable {\n return of(events as GridEvent[]);\n }\n}\n","import { Fixture } from '@cds/betting-offer';\nimport { DetailedFixtureFactory, FixtureFactory } from '@frontend/sports/betting-offer/feature/fixture-factories';\nimport { OfferGroupingService } from '@frontend/sports/betting-offer/feature/offer-grouping';\nimport { GridEvent } from '@frontend/sports/grid/core/feature/model';\nimport { Observable, of } from 'rxjs';\nimport { map } from 'rxjs/operators';\n\nimport { BaseFactory, EventOption, GridSourceEvent } from './event.factory';\n\nexport class GridFixtureFactory implements BaseFactory {\n constructor(\n private fixtureFactory: FixtureFactory,\n private detailedFactory: DetailedFixtureFactory,\n private grouping: OfferGroupingService,\n ) {}\n\n canCreate(events: GridSourceEvent[]): boolean {\n return 'games' in events[0] || 'optionMarkets' in events[0];\n }\n\n create(events: GridSourceEvent[], options?: EventOption): Observable {\n const fixtures = events as Fixture[];\n\n if (!options?.marketGrouping) {\n return of(fixtures.map((fixture) => this.fixtureFactory.create(fixture)));\n }\n\n return this.grouping.getFixtureGrouping(fixtures[0].sport.id, 'any').pipe(\n map((groupingConfiguration) =>\n fixtures.map((fixture) => {\n if (!groupingConfiguration) {\n throw new Error('Could not map events without grouping');\n }\n\n return this.detailedFactory.create({\n fixture,\n splitFixtures: [],\n grouping: groupingConfiguration,\n isPriceBoosted: options.isPriceBoosted,\n excludePriceboostedMarketGrouping: options.excludePriceboostedMarketGrouping,\n });\n }),\n ),\n );\n }\n}\n","import { Injectable } from '@angular/core';\n\nimport { Fixture } from '@cds/betting-offer';\nimport { DetailedFixtureFactory, FixtureFactory } from '@frontend/sports/betting-offer/feature/fixture-factories';\nimport { EventModel } from '@frontend/sports/betting-offer/feature/model';\nimport { OfferGroupingService } from '@frontend/sports/betting-offer/feature/offer-grouping';\nimport { GridFavouriteService } from '@frontend/sports/grid/core/feature/favourites';\nimport { GridEvent } from '@frontend/sports/grid/core/feature/model';\nimport { Observable, of } from 'rxjs';\nimport { tap } from 'rxjs/operators';\n\nimport { GridBaseFactory } from './grid-base.factory';\nimport { GridFixtureFactory } from './grid-fixture.factory';\n\nexport type GridSourceEvent = EventModel | Fixture;\n\nexport interface EventOption {\n marketGrouping?: boolean;\n isPriceBoosted?: boolean;\n excludePriceboostedMarketGrouping?: boolean;\n}\n\nexport interface BaseFactory {\n canCreate(events: GridSourceEvent[]): boolean;\n create(events: GridSourceEvent[], options?: EventOption): Observable;\n}\n\n@Injectable({ providedIn: 'root' })\nexport class EventFactory {\n private factories: BaseFactory[];\n constructor(\n private favouriteService: GridFavouriteService,\n private fixtureFactory: FixtureFactory,\n private detailedFactory: DetailedFixtureFactory,\n private grouping: OfferGroupingService,\n ) {\n this.factories = [new GridFixtureFactory(this.fixtureFactory, this.detailedFactory, this.grouping), new GridBaseFactory()];\n }\n\n create(events: GridSourceEvent[] | undefined, options?: EventOption): Observable {\n if (!events?.length) {\n return of([]);\n }\n\n for (const factory of this.factories) {\n if (factory.canCreate(events)) {\n return factory.create(events, options).pipe(\n tap((mapped) => {\n this.setFavourited(mapped);\n }),\n );\n }\n }\n\n throw new Error('No market factory found for market');\n }\n\n private setFavourited(events: GridEvent[]): void {\n // eslint-disable-next-line no-param-reassign\n events.forEach((current) => (current.favourited = this.favouriteService.isFavourited(current)));\n }\n}\n","import { OperatorFunction, ignoreElements, pipe } from 'rxjs';\n\nexport function ignoreElementsTyped(): OperatorFunction {\n return pipe(ignoreElements());\n}\n","import { of, EMPTY } from 'rxjs';\nimport { concatMap, withLatestFrom, map, catchError, tap, finalize } from 'rxjs/operators';\n\n/**\n * `concatLatestFrom` combines the source value\n * and the last available value from a lazily evaluated Observable\n * in a new array\n *\n * @usageNotes\n *\n * Select the active customer from the NgRx Store\n *\n * ```ts\n * import { concatLatestFrom } from '@ngrx/operators';\n * import * as fromCustomers from '../customers';\n *\n * this.actions$.pipe(\n * concatLatestFrom(() => this.store.select(fromCustomers.selectActiveCustomer))\n * )\n * ```\n *\n * Select a customer from the NgRx Store by its id that is available on the action\n *\n * ```ts\n * import { concatLatestFrom } from '@ngrx/operators';\n * import * fromCustomers from '../customers';\n *\n * this.actions$.pipe(\n * concatLatestFrom((action) => this.store.select(fromCustomers.selectCustomer(action.customerId)))\n * )\n * ```\n */\nfunction concatLatestFrom(observablesFactory) {\n return concatMap(value => {\n const observables = observablesFactory(value);\n const observablesAsArray = Array.isArray(observables) ? observables : [observables];\n return of(value).pipe(withLatestFrom(...observablesAsArray));\n });\n}\n\n/**\n * `mapResponse` is a map operator with included error handling.\n * It is similar to `tapResponse`, but allows to map the response as well.\n *\n * The main use case is for NgRx Effects which requires an action to be dispatched.\n *\n * @usageNotes\n * ```ts\n * export const loadAllUsers = createEffect((\n * actions$ = inject(Actions),\n * usersService = inject(UsersService)\n * ) => {\n * return actions$.pipe(\n * ofType(UsersPageActions.opened),\n * exhaustMap(() => {\n * return usersService.getAll().pipe(\n * mapResponse({\n * next: (users) => UsersApiActions.usersLoadedSuccess({ users }),\n * error: (error) => UsersApiActions.usersLoadedFailure({ error }),\n * })\n * );\n * })\n * );\n * });\n * ```\n */\nfunction mapResponse(observer) {\n return source$ => source$.pipe(map(value => observer.next(value)), catchError(error => of(observer.error(error))));\n}\n\n/**\n * Handles the response in ComponentStore effects in a safe way, without\n * additional boilerplate. It enforces that the error case is handled and\n * that the effect would still be running should an error occur.\n *\n * Takes optional callbacks for `complete` and `finalize`.\n *\n * @usageNotes\n *\n * ```ts\n * readonly dismissAlert = this.effect((alert$) => {\n * return alert$.pipe(\n * concatMap(\n * (alert) => this.alertsService.dismissAlert(alert).pipe(\n * tapResponse(\n * (dismissedAlert) => this.alertDismissed(dismissedAlert),\n * (error: { message: string }) => this.logError(error.message)\n * )\n * )\n * )\n * );\n * });\n *\n * readonly loadUsers = this.effect((trigger$) => {\n * return trigger$.pipe(\n * tap(() => this.patchState({ loading: true })),\n * exhaustMap(() =>\n * this.usersService.getAll().pipe(\n * tapResponse({\n * next: (users) => this.patchState({ users }),\n * error: (error: HttpErrorResponse) => this.logError(error.message),\n * finalize: () => this.patchState({ loading: false }),\n * })\n * )\n * )\n * );\n * });\n * ```\n */\nfunction tapResponse(observerOrNext, error, complete) {\n const observer = typeof observerOrNext === 'function' ? {\n next: observerOrNext,\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n error: error,\n complete\n } : observerOrNext;\n return source => source.pipe(tap({\n next: observer.next,\n complete: observer.complete\n }), catchError(error => {\n observer.error(error);\n return EMPTY;\n }), observer.finalize ? finalize(observer.finalize) : source$ => source$);\n}\n\n/**\n * DO NOT EDIT\n *\n * This file is automatically generated at build\n */\n\n/**\n * Generated bundle index. Do not edit.\n */\n\nexport { concatLatestFrom, mapResponse, tapResponse };\n","import { EventDetailsColumnType, EventModel } from '@frontend/sports/betting-offer/feature/model';\nimport { EventFactory, EventOption, GridSourceEvent } from '@frontend/sports/grid/core/feature/factories';\nimport { GridEvent, GridGrouping, GridMedia, GridModel } from '@frontend/sports/grid/core/feature/model';\nimport { GridActions, IGridState, gridStateSelector } from '@frontend/sports/grid/core/feature/store';\nimport { MediaState, mediaStateSelector } from '@frontend/sports/grid/media/feature/state';\nimport { concatLatestFrom } from '@ngrx/operators';\nimport { Store } from '@ngrx/store';\nimport { isArray, isEqual, keys } from 'lodash-es';\nimport { Observable, ReplaySubject, Subscription } from 'rxjs';\nimport { distinctUntilChanged, filter, map, startWith, switchMap, take, tap } from 'rxjs/operators';\n\nexport interface GridEventMapper {\n canMap(grid: GridModel, events: GridSourceEvent[]): boolean;\n\n map(grid: GridModel, events: GridSourceEvent[]): Observable;\n}\n\nexport interface GridEventToReplace {\n preMatchGridEvent: GridEvent;\n liveGridSourceEvent: GridSourceEvent;\n}\n\nexport class ObservableGrid extends Observable {\n private id: string;\n private initialized = false;\n\n private subject = new ReplaySubject(1);\n private subscription: Subscription;\n\n constructor(\n private grid: GridModel,\n private store: Store,\n private factory: EventFactory,\n source: Observable,\n ) {\n super();\n\n this.id = grid.id;\n // eslint-disable-next-line import/no-deprecated\n this.source = this.subject.asObservable();\n\n this.subscription = source\n .pipe(\n tap((current) => {\n this.init(current);\n }),\n switchMap((initial) =>\n store.select(gridStateSelector).pipe(\n //Start with the initial config emitted via the source to avoid waiting for the next \"store\" emission which is dispatched via the tap 3 lines above\n //shouldn't really be needed in the first place, but it's a hack for the tests to work, and will avoid a redundant task schedule\n startWith({ [this.id]: initial }),\n //usually it's not advised to combine selectors in this way, made an exception here to avoid coupling the two states together\n concatLatestFrom(() => store.select(mediaStateSelector)),\n map(([gridState, mediaState]) => this.getModel(gridState, mediaState)),\n distinctUntilChanged((current, previous) => this.compareModel(current, previous)),\n filter((value) => value !== undefined),\n ),\n ),\n tap((current) => {\n this.grid = current;\n this.subject.next(current);\n }),\n )\n .subscribe();\n }\n\n getState(): GridModel {\n return this.grid;\n }\n\n destroy(): void {\n this.subject.complete();\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n this.subscription?.unsubscribe();\n this.store.dispatch(GridActions.destroy({ gridId: this.id }));\n }\n\n addEvents(events: GridSourceEvent | GridSourceEvent[], options?: EventOption, grouping?: GridGrouping): void {\n const eventsList = isArray(events) ? events : [events];\n\n this.factory.create(eventsList, options).subscribe((eventsMapped) => {\n this.store.dispatch(GridActions.addEvents({ payload: { id: this.id, events: eventsMapped, grouping } }));\n });\n }\n\n removeEvents(eventIds: string[]): void {\n this.store.dispatch(GridActions.removeEvents({ payload: { id: this.id, eventIds } }));\n }\n\n replaceEvent(targetFixtureId: string, newFixture: GridSourceEvent, options?: EventOption): void {\n this.factory\n .create([newFixture], options)\n .pipe(take(1))\n .subscribe((gridEvents) => {\n if (gridEvents.length === 1) {\n this.store.dispatch(GridActions.replaceEvent({ payload: { id: this.id, targetFixtureId, newFixture: gridEvents[0] } }));\n }\n });\n }\n\n replaceEvents(events: GridEventToReplace[], options?: EventOption): void {\n // eslint-disable-next-line unicorn/prefer-object-from-entries\n const eventDictionary = events.reduce>(\n (previous, current) => ({\n ...previous,\n [current.liveGridSourceEvent.id]: current.preMatchGridEvent,\n }),\n {},\n );\n this.factory\n .create(\n events.map((event) => event.liveGridSourceEvent),\n options,\n )\n .pipe(\n take(1),\n map((gridEvents) =>\n gridEvents.map((gridEvent) => ({\n currentEvent: eventDictionary[gridEvent.id],\n newEvent: gridEvent,\n })),\n ),\n )\n .subscribe((gridEvents) => {\n this.store.dispatch(GridActions.replaceEvents({ payload: { id: this.id, events: gridEvents } }));\n });\n }\n\n private init(grid: GridModel): void {\n if (this.initialized) {\n return;\n }\n\n this.initialized = true;\n this.store.dispatch(GridActions.init({ payload: grid }));\n }\n\n private getModel = (gridState: IGridState, mediaState: MediaState) => {\n const grid = gridState[this.id];\n if (!grid) {\n return;\n }\n\n if (!mediaState) {\n return grid;\n }\n\n const mediaTab = mediaState.activeTab;\n const media: GridMedia = {\n enabled: mediaState.active,\n videoEvent: (mediaTab === EventDetailsColumnType.Video && mediaState.video.eventId) || '',\n animationEvent: (mediaTab === EventDetailsColumnType.Animation && mediaState.animation.eventId) || '',\n statsEvent: (mediaTab === EventDetailsColumnType.Stats && mediaState.statistics.eventId) || '',\n };\n\n if (isEqual(this.grid.media, media)) {\n return { ...grid, media: this.grid.media };\n }\n\n return { ...grid, media };\n };\n\n private compareModel = (current: GridModel | undefined, previous: GridModel | undefined) => {\n if (current === undefined && previous === undefined) {\n return true;\n }\n if (current === undefined || previous === undefined) {\n return false;\n }\n const currentKeys = keys(current) as (keyof GridModel)[];\n const previousKeys = keys(previous) as (keyof GridModel)[];\n\n if (currentKeys.length !== previousKeys.length) {\n return false;\n }\n\n for (const key of previousKeys) {\n if (current[key] !== previous[key]) {\n return false;\n }\n }\n\n return true;\n };\n}\n","import { Injectable, Optional } from '@angular/core';\n\nimport { Group as DefaultGroup, GridViewGroupingConfiguration, SixPackGroup } from '@cds/betting-offer/grouping/grid-view';\nimport { EpcotConfig, GridConfig, Sitecore } from '@frontend/sports/common/client-config-data-access';\nimport { Column, GridGrouping, GridLayout, GridSorting, Group, GroupBadge, SubscriptionTopic } from '@frontend/sports/grid/core/feature/model';\nimport { isEmpty, now, reverse, sortBy, times } from 'lodash-es';\n\nexport interface GridOption {\n activeGrouping?: number | string;\n marketGrouping?: boolean;\n moreGrouping?: boolean;\n grouping?: GridGrouping;\n disableGroupSorting?: boolean;\n sorting?: GridSorting;\n collapsedThreshold?: number;\n subscriptionTopic?: SubscriptionTopic;\n groupOrder?: string[];\n badges?: Record;\n includeRetailGroups?: boolean;\n excludeDefaultMainMarket?: boolean;\n}\n\n@Injectable({ providedIn: 'root' })\nexport class ObservableGridProvider {\n constructor(\n protected sitecore: Sitecore,\n @Optional() protected epcotConfig?: EpcotConfig,\n @Optional() protected gridConfig?: GridConfig,\n ) {}\n\n getColumns(config?: GridViewGroupingConfiguration, layout?: GridLayout, options?: GridOption): Column[] {\n let source: (DefaultGroup | SixPackGroup)[] = [];\n\n let { groups, sixPackGroups } = config || {\n groups: new Array(),\n sixPackGroups: new Array(),\n };\n\n if (layout === GridLayout.SixPack) {\n sixPackGroups = this.groupSixPackConfigs(sixPackGroups);\n source = this.applyGroupOrder(sixPackGroups, options);\n } else {\n groups = this.hideSixPackMarkets(groups, sixPackGroups);\n source = this.applyGroupOrder(groups, options);\n }\n\n const maxColumns = this.epcotConfig?.isEnabled && this.gridConfig?.is2way3wayEnabled ? 2 : 4;\n\n const count = Math.min(maxColumns, source.length);\n\n if (count === 0) {\n return options?.excludeDefaultMainMarket ? [] : [this.getBaseColumn()];\n }\n\n return times(count).map((current, index) => this.getColumn(source, groups, index, options));\n }\n\n protected getColumn(source: (DefaultGroup | SixPackGroup)[], groups: DefaultGroup[], active: number, options?: GridOption): Column {\n let mapped: Group[];\n\n mapped =\n source.length === 0\n ? [this.getBaseGroup()]\n : source.filter((s) => !s.isFallbackGroup).map((group, index) => this.getGroup(group, groups, index === active, options?.badges));\n\n return {\n id: (now() + active).toString(32),\n groups: mapped,\n enabled: true,\n more: !!options?.moreGrouping,\n };\n }\n\n private getGroup(group: DefaultGroup | SixPackGroup, groups: DefaultGroup[], active: boolean, badges?: Record): Group {\n return {\n id: group.id,\n name: group.name,\n options: group.optionNames,\n balancedMarket: group.balancedMarketsEnabled,\n marketAttribute: group.showAttribute,\n childGroups: 'marketGroupIds' in group ? group.marketGroupIds : [],\n active,\n extended: false,\n visible: true,\n optionsToShow: group.optionsToShow,\n badge: badges?.[group.id],\n isFallbackGroup: group.isFallbackGroup,\n fallbackGridGroupIds: group.fallbackGridGroupIds,\n };\n }\n\n protected getBaseColumn(): Column {\n const group = this.getBaseGroup();\n\n return {\n id: 'main',\n groups: [group],\n enabled: true,\n more: false,\n };\n }\n\n protected getBaseGroup(): Group {\n return {\n id: 'main',\n name: this.sitecore.eventGrid.MarketSwitcher_MainMarket,\n active: true,\n extended: false,\n visible: true,\n isFallbackGroup: false,\n fallbackGridGroupIds: [],\n };\n }\n\n protected applyGroupOrder(source: (DefaultGroup | SixPackGroup)[], options?: GridOption): (DefaultGroup | SixPackGroup)[] {\n if (!options?.groupOrder?.length) {\n return source;\n }\n\n reverse([...options.groupOrder]).forEach((grid) => {\n // eslint-disable-next-line no-param-reassign\n source = sortBy(source, [(group: DefaultGroup | SixPackGroup) => grid !== group.id]);\n });\n\n return source;\n }\n\n protected groupSixPackConfigs(sixPackGroups: SixPackGroup[]): SixPackGroup[] {\n const sixPackParentToChildMap = this.gridConfig?.sixPackParentToChildMap;\n if (isEmpty(sixPackParentToChildMap)) {\n return sixPackGroups;\n }\n\n const [parents, children] = sixPackGroups.reduce<[SixPackGroup[], SixPackGroup[]]>(\n ([p, c], sixPack) => {\n (sixPack.id in sixPackParentToChildMap ? p : c).push(sixPack);\n return [p, c];\n },\n [[], []],\n );\n\n const orphans = new Set();\n const parentMarketGroupIds = new Map>();\n for (const child of children) {\n let hasParent = false;\n for (const parent of parents) {\n if (sixPackParentToChildMap[parent.id].includes(child.id)) {\n if (!parentMarketGroupIds.has(parent.id)) {\n parentMarketGroupIds.set(parent.id, new Set(parent.marketGroupIds ?? []));\n }\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n child.marketGroupIds.forEach((id) => parentMarketGroupIds.get(parent.id)!.add(id));\n hasParent = true;\n }\n }\n if (!hasParent) {\n orphans.add(child);\n }\n }\n\n const updatedParents = parents.map((parent) => ({\n ...parent,\n marketGroupIds: [...(parentMarketGroupIds.get(parent.id) ?? parent.marketGroupIds)],\n }));\n\n return [...updatedParents, ...orphans];\n }\n\n protected hideSixPackMarkets(groups: DefaultGroup[], sixPackGroups: SixPackGroup[]): DefaultGroup[] {\n const sixPackParentToChildMap = this.gridConfig?.sixPackParentToChildMap;\n if (isEmpty(sixPackParentToChildMap) || sixPackGroups.length === 0) {\n return groups;\n }\n\n const sixPackValues = new Set(Object.values(sixPackParentToChildMap).flat());\n const sixPackMarketGroupIds = new Set(sixPackGroups.flatMap((sixpack) => (sixPackValues.has(sixpack.id) ? sixpack.marketGroupIds : [])));\n\n return groups.filter((group) => !sixPackMarketGroupIds.has(group.id));\n }\n}\n","import { Injectable, OnDestroy, inject } from '@angular/core';\n\nimport { Fixture } from '@cds/betting-offer';\nimport { GridViewGroupingConfiguration } from '@cds/betting-offer/grouping/grid-view';\nimport { EventModel, SportModel } from '@frontend/sports/betting-offer/feature/model';\nimport { OfferGroupingService } from '@frontend/sports/betting-offer/feature/offer-grouping';\nimport { GridConfig, Sitecore } from '@frontend/sports/common/client-config-data-access';\nimport { isDefined } from '@frontend/sports/common/core/utils/extended-types';\nimport { EventFactory } from '@frontend/sports/grid/core/feature/factories';\nimport { Column, GridGrouping, GridLayout, GridModel, Group, SubscriptionTopic } from '@frontend/sports/grid/core/feature/model';\nimport { IGridGroup, addEvents, setActiveGroupState, setGroupCollapseThreshold, setStateCollapse } from '@frontend/sports/grid/core/feature/store';\nimport { ColumnActiveState, GridStoreService, GridStoreState } from '@frontend/sports/grid/core/feature/store';\nimport { Store } from '@ngrx/store';\nimport { isNumber, mapValues } from 'lodash-es';\nimport { Observable, forkJoin, of } from 'rxjs';\nimport { map } from 'rxjs/operators';\n\nimport { ObservableGrid } from './observable-grid';\nimport { GridOption, ObservableGridProvider } from './observable-grid.provider';\n\n@Injectable({ providedIn: 'root' })\nexport class ObservableGridFactory implements OnDestroy {\n protected grids: ObservableGrid[] = [];\n\n protected eventFactory = inject(EventFactory);\n protected gridConfig = inject(GridConfig);\n protected sitecore = inject(Sitecore);\n // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment\n protected store: Store = inject(Store);\n protected gridStore = inject(GridStoreService);\n protected observableGridProvider = inject(ObservableGridProvider);\n protected grouping = inject(OfferGroupingService);\n\n ngOnDestroy(): void {\n this.destroy();\n }\n\n destroy(): void {\n this.grids.forEach((grid) => {\n grid.destroy();\n });\n this.grids = [];\n }\n\n /**\n * Creates a grid model with the provided options and events\n *\n * @param id - is used to identify the grid, so we can wire up the storing of active groups on each market column and the collapse state of several items on the grid\n * @param events - list of the events, can not be empty\n * @param [options={}] - options which are used to build the grid\n * @param [options.activeGrouping] - number specifying which non gridable market group is selected, is being used aso for market template\n * @param [options.marketGrouping] - forwarded to the event mapper, used for non gridable events to apply the event details market grouping\n * @param [options.moreGrouping] - indicates weather the grid should render the more in the market groups dropdown\n * @param [options.grouping] - specifies the grouping approach that will be used for the events\n * @param [options.sorting] - specifies what is the initial sorting applied\n * @param [options.collapsedThreshold] - a number which is being used to collapse the groups after the event count surpasses the threshold\n * @param [options.subscriptionTopic] - specifies what topic should the grid subscribe the events to\n * @returns\n * @memberof ObservableGridFactory\n */\n from(id: string, events: EventModel[], options: GridOption = {}): ObservableGrid {\n if (events.length === 0) {\n throw new Error('Can not map undefined events');\n }\n\n const grid = this.getBase(id, options, events[0].sport);\n\n return this.getGrid(grid, options, of(events));\n }\n\n /**\n * Creates a grid model with the provided options and fixtures\n *\n * @param id - is used to identify the grid, so we can wire up the storing of active groups on each market column and the collapse state of several items on the grid\n * @param fixtures - list of the fixtures, can not be empty\n * @param [options={}] - options which are used to build the grid\n * @param [options.activeGrouping] - number specifying which non gridable market group is selected, is being used aso for market template\n * @param [options.marketGrouping] - forwarded to the event mapper, used for non gridable events to apply the event details market grouping\n * @param [options.moreGrouping] - indicates weather the grid should render the more in the market groups dropdown\n * @param [options.grouping] - specifies the grouping approach that will be used for the events\n * @param [options.sorting] - specifies what is the initial sorting applied\n * @param [options.collapsedThreshold] - a number which is being used to collapse the groups after the event count surpasses the threshold\n * @param [options.subscriptionTopic] - specifies what topic should the grid subscribe the events to\n * @returns\n * @memberof ObservableGridFactory\n */\n fromResponse(\n id: string,\n fixtures?: Fixture[],\n options: GridOption = {},\n isPriceBoosted = false,\n excludePriceboostedMarketGrouping = false,\n ): ObservableGrid {\n if (!fixtures?.length) {\n throw new Error('Can not map undefined fixtures');\n }\n\n const sport = fixtures[0].sport;\n const events = this.eventFactory.create(fixtures, {\n marketGrouping: options.marketGrouping,\n isPriceBoosted,\n excludePriceboostedMarketGrouping,\n });\n const grid = this.getBase(id, options, {\n id: sport.id,\n name: sport.name.value,\n virtual: fixtures[0].isVirtual,\n isEsport: sport.isEsport,\n });\n\n return this.getGrid(grid, options, events);\n }\n\n protected getGrid(grid: GridModel, options: GridOption, events: Observable): ObservableGrid {\n // eslint-disable-next-line no-param-reassign\n grid = this.setConfig(grid, options);\n\n const source = this.getSource(grid, options, events);\n\n const result = new ObservableGrid(grid, this.store, this.eventFactory, source);\n this.grids.push(result);\n\n return result;\n }\n\n private getSource(grid: GridModel, options: GridOption, events: Observable): Observable {\n return forkJoin([this.grouping.getGridGrouping(grid.sport.id), events]).pipe(\n map(([config, mapped]) => this.setConfig(grid, options, config, mapped)),\n );\n }\n\n protected setConfig(grid: GridModel, options: GridOption, config?: GridViewGroupingConfiguration, events?: EventModel[]): GridModel {\n const state = this.gridStore.get(grid.id) || { columns: [], groups: [], events: {} };\n\n const layout = this.getLayout(config);\n const columns = this.observableGridProvider.getColumns(config, layout, options);\n\n this.stripEventData(events);\n this.setPlaceHolder(options, columns);\n // eslint-disable-next-line no-param-reassign\n grid = this.updateGridState(grid, layout, options, columns, state, events);\n\n if (config) {\n const active = this.getActiveColumns(columns, state.columns);\n return setActiveGroupState(grid, active);\n }\n\n return grid;\n }\n\n protected stripEventData(events?: EventModel[]) {\n if (events?.length) {\n // for state sanity we clean up unneeded properties\n // mainly for event data\n events.forEach((event) => {\n //no\n //@ts-expect-error The operand of a 'delete' operator must be option\n // eslint-disable-next-line no-param-reassign\n delete event.fixtureScoreboard;\n // eslint-disable-next-line no-param-reassign\n delete event.splitFixtureIds;\n });\n }\n }\n\n protected setPlaceHolder(options: GridOption, columns: Column[]) {\n // eslint-disable-next-line @typescript-eslint/prefer-optional-chain\n if (options && options.activeGrouping != null) {\n const placeholder = this.getPlaceholderGroup(options.activeGrouping);\n\n columns.forEach((column, index) => {\n // eslint-disable-next-line no-param-reassign\n column.enabled = index === 0;\n\n if (column.enabled) {\n column.groups.push(placeholder);\n // eslint-disable-next-line no-param-reassign\n column.groups.forEach((group) => (group.active = group === placeholder));\n }\n });\n }\n }\n\n protected updateGridState(\n grid: GridModel,\n layout: GridLayout,\n options: GridOption,\n columns: Column[],\n state: GridStoreState,\n events?: EventModel[],\n ): GridModel {\n const eventState = mapValues(state.events, (event) => ({\n collapsed: event.collapsed,\n collapsedChildren: event.collapsedChildren,\n }));\n\n // eslint-disable-next-line no-param-reassign\n grid = setStateCollapse({ ...grid, layout, columns }, state.groups, eventState);\n\n if (events?.length) {\n // eslint-disable-next-line no-param-reassign\n grid = addEvents(grid, events);\n if (options.collapsedThreshold) {\n // eslint-disable-next-line no-param-reassign\n grid = setGroupCollapseThreshold(grid, options.collapsedThreshold);\n }\n }\n return grid;\n }\n\n protected getBase(id: string, options: GridOption, sport: SportModel, isCustomGrid?: boolean): GridModel {\n let topic = options.subscriptionTopic === undefined ? SubscriptionTopic.Grid : options.subscriptionTopic;\n\n if (isNumber(options.activeGrouping) || isCustomGrid) {\n // when it is a number we expect to show non gridable markets\n topic = SubscriptionTopic.NonGridable;\n }\n\n return {\n id,\n grouping: options.grouping === undefined ? GridGrouping.League : options.grouping,\n sorting: options.sorting,\n columns: [],\n sport,\n groups: [],\n groupingThreshold: options.collapsedThreshold,\n layout: GridLayout.Default,\n media: { enabled: false, videoEvent: undefined, animationEvent: undefined, statsEvent: undefined },\n collapsed: {\n groups: [],\n events: {},\n },\n topic,\n disableGroupSorting: options.disableGroupSorting,\n };\n }\n\n protected getLayout(config?: GridViewGroupingConfiguration): GridLayout {\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n if (config && this.gridConfig.isSixPackLayoutEnabled && config.sixPackGroups?.some((group) => group.marketGroupIds.length > 0)) {\n return GridLayout.SixPack;\n }\n\n return GridLayout.Default;\n }\n\n protected getActiveColumns(columns: Column[], state: ColumnActiveState[]): IGridGroup[] {\n return columns\n .map((column, index) => {\n const columnState = state.find((current) => current.index === index);\n\n if (columnState) {\n return { columnId: column.id, groupId: columnState.group };\n }\n //removing below return will break build\n // eslint-disable-next-line sonarjs/no-redundant-jump\n return;\n })\n .filter(isDefined);\n }\n\n private getPlaceholderGroup(id: number | string): Group {\n return {\n id: id.toString(),\n name: this.sitecore.eventGrid.MarketSwitcher_More,\n active: false,\n extended: true,\n visible: false,\n isFallbackGroup: false,\n fallbackGridGroupIds: [],\n };\n }\n}\n","import { Injectable } from '@angular/core';\n\nimport { BettingOfferApi } from '@frontend/sports/betting-offer/feature/offer-service';\nimport { TournamentsDataConfig } from '@frontend/sports/common/client-config-data-access';\nimport { CountItem, sportModelMethods, takeTop } from '@frontend/sports/common/core/data-access/sport-model';\nimport { Tournament } from '@frontend/sports/types/components/tournaments';\nimport { Observable, combineLatest, of } from 'rxjs';\nimport { map, switchMap } from 'rxjs/operators';\n\nimport { SportsCacheService } from '../../client-caching/sports-cache.service';\n\nexport interface CompetitionOverview {\n tournaments?: Tournament[];\n sport?: CountItem;\n}\n\nexport interface ReorderItem {\n id: number;\n location: number;\n}\n\n// TODO:REVIEW-USAGE\n@Injectable({ providedIn: 'root' })\nexport class CompetitionListService {\n constructor(\n private bettingContent: BettingOfferApi,\n private bettingCache: SportsCacheService,\n private tournamentsData: TournamentsDataConfig,\n ) {}\n\n getSportTree(\n sportId: number,\n start?: string,\n end?: string,\n withTournaments?: boolean,\n competitionId?: number,\n conferenceId?: number,\n ): Observable {\n const cached = !start && !end;\n const source = cached\n ? this.bettingCache.getSport(sportId, competitionId, conferenceId)\n : this.bettingContent.getSport(sportId, false, { from: start, to: end }).pipe(\n switchMap((sport) =>\n combineLatest([of(sport), this.bettingCache.getConference(conferenceId, competitionId, { from: start, to: end })]),\n ),\n map(([sports, conference]) => sportModelMethods.fromTag((sports || []).concat(conference || [])).pop()),\n );\n\n return source.pipe(\n map((sport) => {\n if (!sport) {\n return;\n }\n\n return {\n sport,\n tournaments: withTournaments ? this.getValidTournaments(sportId) : undefined,\n };\n }),\n );\n }\n\n getSportList(top: number, ...reorderings: ReorderItem[]): Observable {\n return this.bettingCache.getSportList().pipe(map((result) => this.getReorderedSportList(result, top, reorderings)));\n }\n\n getReorderedSportList(sports: CountItem[], top: number, reorderings: ReorderItem[]): CountItem[] {\n const topSports = takeTop(sports);\n let locationShiftUp = 0;\n\n for (const { id, location } of reorderings) {\n if (!location) {\n continue;\n }\n\n const existing = sports.find((current) => current.id === id);\n\n if (existing) {\n const existingLocation = topSports.indexOf(existing);\n\n if (existingLocation >= 0) {\n topSports.splice(existingLocation, 1);\n }\n\n topSports.splice(location - locationShiftUp - 1, 0, existing);\n } else {\n locationShiftUp++;\n }\n }\n\n return topSports.slice(0, top);\n }\n\n getValidTournaments(sportId: number): Tournament[] {\n return this.tournamentsData.tournaments.filter((tournament) => this.filterTournament(tournament, sportId));\n }\n\n private filterTournament(tournament: Tournament, sportId: number): boolean {\n return Boolean(\n tournament.navigation &&\n tournament.navigation.filter &&\n tournament.navigation.filter.visible &&\n tournament.leaguesSection &&\n tournament.leaguesSection.sport &&\n tournament.leaguesSection.sport.id.toString() === sportId.toString(),\n );\n }\n}\n","import { Inject, Injectable } from '@angular/core';\n\nimport { MarqueeIndexedFixtureView } from '@cds/betting-offer/domain-specific';\nimport { MarqueeRequest } from '@cds/query-objects';\nimport { BaseCdsApi, BaseCdsApiFactory, CDS_API_FACTORY } from '@frontend/sports/content-distribution/feature';\nimport { Observable } from 'rxjs';\n\n@Injectable({ providedIn: 'root' })\nexport class MarqueeApi {\n private cdsApi: BaseCdsApi;\n\n constructor(@Inject(CDS_API_FACTORY) cdsApiServiceFactory: BaseCdsApiFactory) {\n this.cdsApi = cdsApiServiceFactory({ endpoint: '/marquee' });\n }\n\n getMarquees(request: MarqueeRequest): Observable {\n return this.cdsApi.post(`/fixtures`, request);\n }\n}\n","import { Injectable } from '@angular/core';\n\nimport { MarqueeData, MarqueeRequest } from '@cds/query-objects';\nimport { MarqueeConfig } from '@frontend/sports/common/client-config-data-access';\nimport { MarqueeTile } from '@frontend/sports/types/components/content';\nimport { Observable, map } from 'rxjs';\n\nimport { CompetitionPageType } from './marquee-tile.model';\nimport { MarqueeTileService } from './marquee-tile.service';\n\nexport interface IMarqueeRequestInfo {\n request: MarqueeRequest;\n tiles: MarqueeTile[];\n}\n\n@Injectable({ providedIn: 'root' })\nexport class MarqueeRequestBuilderService {\n constructor(\n private marqueeConfig: MarqueeConfig,\n private marqueeTileService: MarqueeTileService,\n ) {}\n\n buildForFixture(fixtureId: string): Observable {\n return this.marqueeTileService.getForFixture(fixtureId).pipe(map((sitecoreTiles) => this.buildMarqueeRequest(sitecoreTiles)));\n }\n\n buildForTeamPagesFixture(fixtureIds: string[]): IMarqueeRequestInfo | undefined {\n const sitecoreTiles = this.marqueeTileService.getForTeamPagesFixture(fixtureIds);\n if (sitecoreTiles.length) {\n return this.buildMarqueeRequest(sitecoreTiles);\n }\n\n return;\n }\n\n buildForCompetition(ids: number[], sportId: number, type: CompetitionPageType): Observable {\n return this.marqueeTileService.getForCompetition(ids, sportId, type).pipe(map((tiles) => this.buildMarqueeRequest(tiles)));\n }\n\n buildMarqueeRequest(sitecoreTiles: MarqueeTile[]): IMarqueeRequestInfo {\n const marqueeRequestData: MarqueeData[] = sitecoreTiles.map((tile, index) => {\n return {\n fixtureId: tile.fixtureId,\n minimumOdds: tile.previousOdds,\n requestIndex: index,\n gameTemplateIds: tile.gameTemplateIds,\n gridGroupId: tile.gridGroupId,\n gridGroupIds: tile.gridGroupIds?.join(','),\n ...(tile.gameId && { gameId: tile.gameId }),\n ...(tile.resultId && { resultId: tile.resultId }),\n ...(tile.optionMarketId && { optionMarketId: tile.optionMarketId }),\n ...(tile.optionId && { optionId: tile.optionId }),\n happenings: tile.optionMarketParameters?.happening,\n marketTypes: tile.optionMarketParameters?.marketTypes,\n periods: tile.optionMarketParameters?.period,\n positions: tile.optionMarketParameters?.position,\n optionGroupId: tile.optionGroupId,\n };\n });\n // filter out only tiles with event after the requestIndex has been set to all tiles\n const marqueeData = marqueeRequestData.filter((md) => md.fixtureId);\n\n return {\n request: {\n marqueeData,\n take: this.marqueeConfig.maxTiles,\n },\n tiles: sitecoreTiles,\n };\n }\n}\n","import { Injectable } from '@angular/core';\n\nimport { MarqueeTilesConfig } from '@frontend/sports/common/client-config-data-access';\nimport { Observable, combineLatest, of } from 'rxjs';\nimport { first, map, mergeMap } from 'rxjs/operators';\n\nimport { MarqueeApi } from '../cds/marquee-api.service';\nimport { IMarqueeRequestInfo, MarqueeRequestBuilderService } from './marquee-request-builder.service';\nimport { MarqueeResponseMapperService } from './marquee-response-mapper.service';\nimport { CompetitonTileRequestModel, Tile } from './marquee-tile.model';\n\n@Injectable({ providedIn: 'root' })\nexport class MarqueeService {\n private configReady: Observable;\n\n constructor(\n private marqueeRequestBuilderService: MarqueeRequestBuilderService,\n private marqueeResponseMapperService: MarqueeResponseMapperService,\n private marqueeApi: MarqueeApi,\n marqueeTilesConfig: MarqueeTilesConfig,\n ) {\n this.configReady = marqueeTilesConfig.whenReady.pipe(first());\n }\n\n loadMarquees(fixtureId: string): Observable {\n return combineLatest([this.marqueeRequestBuilderService.buildForFixture(fixtureId), this.configReady]).pipe(\n mergeMap(([marqueeRequest]) => this.callApi(marqueeRequest)),\n );\n }\n\n loadMarqueesFor(competitonTileRequestModel: CompetitonTileRequestModel): Observable {\n const marqueeRequest$ = this.marqueeRequestBuilderService.buildForCompetition(\n competitonTileRequestModel.competitionIds,\n competitonTileRequestModel.sportId,\n competitonTileRequestModel.type,\n );\n\n return combineLatest([marqueeRequest$, this.configReady]).pipe(\n mergeMap(([marqueeRequest]) => {\n const dateInterval = competitonTileRequestModel.interval || {};\n\n marqueeRequest.request.from = dateInterval.from;\n marqueeRequest.request.to = dateInterval.to;\n\n return this.callApi(marqueeRequest);\n }),\n );\n }\n\n loadTeamPagesMarquees(fixtureIds: string[]): Observable {\n return this.configReady.pipe(\n mergeMap(() => {\n const marqueeRequest = this.marqueeRequestBuilderService.buildForTeamPagesFixture(fixtureIds);\n if (marqueeRequest) {\n return this.callApi(marqueeRequest);\n }\n\n return of([]);\n }),\n );\n }\n\n private callApi(marqueeRequest: IMarqueeRequestInfo): Observable {\n return this.marqueeApi\n .getMarquees(marqueeRequest.request)\n .pipe(map((responseMarquee) => this.marqueeResponseMapperService.mapFixtures(responseMarquee || [], marqueeRequest.tiles)));\n }\n}\n","import { Inject, Injectable } from '@angular/core';\n\nimport { CompetitionBatchRequest } from '@cds/query-objects';\nimport { CompetitionStatistics } from '@cds/statistics/competition';\nimport { FixtureStatistics } from '@cds/statistics/fixture';\nimport { BaseCdsApi, BaseCdsApiFactory, CDS_API_FACTORY } from '@frontend/sports/content-distribution/feature';\nimport { Observable, firstValueFrom } from 'rxjs';\n\n@Injectable({ providedIn: 'root' })\nexport class StatisticsApi {\n private cdsApi: BaseCdsApi;\n\n constructor(@Inject(CDS_API_FACTORY) cdsApiServiceFactory: BaseCdsApiFactory) {\n this.cdsApi = cdsApiServiceFactory({ endpoint: '/statistics' });\n }\n\n getCompetitionStatistics(\n competitionId: number,\n sportId: number,\n stageId?: number,\n groupId?: number,\n ): Observable {\n return this.cdsApi.get('/competition', {\n competitionId,\n sportId,\n stageId,\n groupId,\n });\n }\n\n getCompetitionStatisticsBatch(competitionIds: string, sportId: number): Observable {\n return this.cdsApi.get('/competition-batch', {\n competitionIds,\n sportId,\n });\n }\n\n getCompetitionStatisticsBatchV2(competitionBatchRequest: CompetitionBatchRequest): Observable {\n return this.cdsApi.post('/competition-batch/v2', competitionBatchRequest);\n }\n\n getFixtureStatistics(fixtureId: string, sportId: number): Promise {\n return firstValueFrom(\n this.cdsApi.get('/fixture', {\n fixtureId,\n sportId,\n }),\n );\n }\n}\n","import { Injectable } from '@angular/core';\n\nimport { CompetitionBatchRequest } from '@cds/query-objects';\nimport { CompetitionStatistics } from '@cds/statistics/competition';\nimport { LoggerFactory, SportsRemoteLogger } from '@frontend/sports/common/core/feature/logging';\nimport { Observable, forkJoin, of } from 'rxjs';\nimport { catchError, map } from 'rxjs/operators';\n\nimport { StatisticsApi } from '../cds/statistics-api.service';\nimport { StatisticsData } from './statistics.models';\n\n@Injectable({\n providedIn: 'root',\n})\nexport class StatisticsApiService {\n private readonly logger: SportsRemoteLogger;\n\n constructor(\n loggerFactory: LoggerFactory,\n private cdsStatsService: StatisticsApi,\n ) {\n this.logger = loggerFactory.getLogger('StatisticsApiService');\n }\n\n getStats(eventId: string, leagueId: number, sportId: number, stageId?: number, groupId?: number): Observable {\n const leagueStatistics$ = this.cdsStatsService.getCompetitionStatistics(leagueId, sportId, stageId, groupId);\n const eventStatistics$ = this.cdsStatsService.getFixtureStatistics(eventId, sportId);\n\n return forkJoin([leagueStatistics$, eventStatistics$]).pipe(\n map(([leagueStatistics, eventStatistics]) => {\n return { eventStats: eventStatistics, leagueStats: leagueStatistics!.rows };\n }),\n catchError((error: any) => {\n this.logError('getStats', error);\n\n return of({ eventStats: undefined, leagueStats: [] });\n }),\n );\n }\n\n getCompetitionsStatistics(competitionIds: number[], sportId: number): Observable {\n return this.cdsStatsService.getCompetitionStatisticsBatch(competitionIds.join(','), sportId).pipe(\n map((value) => value ?? []),\n catchError((error: any) => {\n this.logError('getCompetitionsStatistics', error);\n\n return of([]);\n }),\n );\n }\n\n getCompetitionsBatchStatistics(competitionBatchRequest: CompetitionBatchRequest): Observable {\n return this.cdsStatsService.getCompetitionStatisticsBatchV2(competitionBatchRequest).pipe(\n map((value) => value ?? []),\n catchError((error: any) => {\n this.logError('competition-batch/v2', error);\n\n return of([]);\n }),\n );\n }\n\n private logError(methodName: string, error: any): void {\n this.logger.error(error, `Failed to load statistics from ${methodName}`);\n }\n}\n","import { DomainEvent } from '@frontend/sports/common/core/utils/dispatcher';\nimport { PayoutAcceptanceMode } from '@frontend/sports/types/models/user-settings';\n\nimport { EarlyPayoutAcceptance } from './early-payout';\n\nexport enum TaxCalculation {\n Default = 'Default',\n TurnoverTax = 'TurnoverTax',\n}\n\nexport class UserSettingsResponse {\n oddsFormatLimitReached!: boolean;\n succeeded?: boolean;\n}\n\nexport interface IUserBettingSettings {\n defaultStake?: number;\n defaultStakeProposalReviewDate?: string;\n oddsAcceptanceMode?: string;\n betslipEmailNotification?: boolean;\n betslipAppNotification?: boolean;\n earlyPayoutAcceptanceMode?: EarlyPayoutAcceptance | PayoutAcceptanceMode;\n keypadIncrementalStakes?: number[];\n betslipSkipConfirmation?: boolean;\n freeBetsActivated?: boolean;\n earlyPayoutDeactivated?: boolean;\n quickBetEnabled?: boolean;\n isDefaultStakeUserDefined?: boolean;\n stakeChangeConsent?: number;\n}\n\nexport interface IConfigSettings {\n minimumStake: number;\n minimumStakeSystem: number;\n minimumStakeUnit: number;\n minimumStakeSystemUnit: number;\n winningsTaxRate: number;\n allowedV2OfferSports: number[];\n oddsBoostForbiddenSportIds: number[];\n oddsBoostForbiddenGameTemplateIds: number[];\n oddsBoostForbiddenMarketTypes: string[];\n riskFreeForbiddenSportIds: number[];\n riskFreeForbiddenGameTemplateIds: number[];\n riskFreeForbiddenMarketTypes: string[];\n customerDnaProfileId?: string;\n gamblingControlMaxStake: number;\n winningsTaxThreshold: number;\n stakeTaxRate: number;\n minimumGrossStakeUnit: number;\n minimumGrossStakeSystemUnit: number;\n taxCalculation?: string;\n}\n\nexport const UserEvents = {\n /* eslint-disable @typescript-eslint/naming-convention,no-underscore-dangle,id-blacklist,id-match */\n ClaimsChanged: 'CLAIMS_CHANGED',\n M2UserClaimsChanged: 'm2-user-claimsChanged',\n UserSettingsChanged: new DomainEvent('USER_SETTINGS_CHANGED'),\n ClientConfigRefreshed: new DomainEvent('CLIENT_CONFIG_REFRESHED'),\n};\n","import { Injectable, Injector } from '@angular/core';\nimport { NavigationEnd, Router } from '@angular/router';\n\nimport { MarqueeTilesConfig, SportsUserConfig } from '@frontend/sports/common/client-config-data-access';\nimport { DispatcherService } from '@frontend/sports/common/core/utils/dispatcher';\nimport { GeoLocationStatus, MarqueeTile } from '@frontend/sports/types/components/content';\nimport { ClientConfigService, GeolocationService, OnAppInit } from '@frontend/vanilla/core';\nimport { ReplaySubject, combineLatest, from } from 'rxjs';\nimport { concatMap, distinctUntilChanged, first, map, skip, startWith, switchMap, tap } from 'rxjs/operators';\n\nimport { UserEvents } from '../model/user.models';\n\n@Injectable({\n providedIn: 'root',\n})\nexport class SportsClientConfigRefreshService implements OnAppInit {\n private readonly BET_PLACE_SUCCESS = 'BET_PLACE_SUCCESS';\n private _locationDependantDataNeeded$ = new ReplaySubject(1);\n\n readonly locationDependantDataNeeded$ = this._locationDependantDataNeeded$.asObservable();\n\n constructor(\n private geolocationService: GeolocationService,\n private injector: Injector,\n private dispatcher: DispatcherService,\n private sportsUserConfig: SportsUserConfig,\n private marqueeTilesConfig: MarqueeTilesConfig,\n private router: Router,\n ) {}\n\n onAppInit(): void {\n const clientConfig = this.injector.get(ClientConfigService);\n\n const firstNavigationFinished$ = this.router.events.pipe(first((e) => e instanceof NavigationEnd));\n\n this.geolocationService.whenReady\n .pipe(\n first(),\n concatMap(() =>\n combineLatest([this.geolocationService.positionChanges, firstNavigationFinished$, this.marqueeTilesConfig.whenReady]).pipe(\n map(([location]) => location.mappedLocation?.locationId),\n distinctUntilChanged(),\n switchMap(() => from(clientConfig.reload([MarqueeTilesConfig]))),\n tap(() => {\n this.dispatcher.dispatch(UserEvents.ClientConfigRefreshed);\n }),\n map(() => this.marqueeTilesConfig.tiles.some((t: MarqueeTile) => t.geoLocationStatus === GeoLocationStatus.Restricted)),\n startWith(false),\n distinctUntilChanged(),\n skip(1), // we don't care about the first value which is emitted from startWith - it's used just for distinctUntilChanged\n ),\n ),\n )\n .subscribe(() => {\n this._locationDependantDataNeeded$.next();\n });\n\n this.dispatcher.on(this.BET_PLACE_SUCCESS).subscribe(() => {\n if (this.sportsUserConfig.isEligibleForNewCustomerOffer) {\n clientConfig.reload([SportsUserConfig]);\n }\n });\n }\n}\n","import { Observable } from 'rxjs';\n\nexport enum FreshDataType {\n DataAndPush,\n Data,\n}\n\nexport abstract class FreshDataProvider {\n abstract isRefreshNeeded(): Observable;\n}\n","export class EllapsedTimer {\n private startedAt: number = Date.now();\n\n private constructor() {}\n\n get ellapsedMilliseconds(): number {\n return Date.now() - this.startedAt;\n }\n\n static start(): EllapsedTimer {\n return new EllapsedTimer();\n }\n}\n","import { Injectable, inject } from '@angular/core';\n\nimport { filterSports } from '@frontend/sports/host-app/sports-product/feature/utils';\nimport { WINDOW } from '@frontend/vanilla/core';\nimport { BehaviorSubject, Observable, fromEvent, merge } from 'rxjs';\n\nimport { EllapsedTimer } from './ellapsed-timer';\n\n@Injectable({ providedIn: 'root' })\nexport class OnlineDetectionService {\n readonly #window = inject(WINDOW);\n private offlineTimer?: EllapsedTimer;\n private online$: BehaviorSubject<{ isOnline: boolean; offlineDuration?: number }>;\n\n constructor() {\n this.online$ = new BehaviorSubject({ isOnline: this.#window.navigator.onLine });\n\n merge(fromEvent(this.#window, 'online'), fromEvent(this.#window, 'offline'))\n .pipe(filterSports())\n .subscribe(() => this.updateOnlineStatus());\n }\n\n onlineChange(): Observable<{ isOnline: boolean; offlineDuration?: number }> {\n return this.online$.asObservable();\n }\n\n private updateOnlineStatus = () => {\n const isOnline = this.#window.navigator.onLine;\n\n const offlineDuration = isOnline && this.offlineTimer ? this.offlineTimer.ellapsedMilliseconds : undefined;\n this.offlineTimer = isOnline ? undefined : EllapsedTimer.start();\n\n this.online$.next({ isOnline, offlineDuration });\n };\n}\n","import { Inject, Injectable } from '@angular/core';\n\nimport { AppConfig } from '@frontend/sports/common/client-config-data-access';\nimport { CdsPushProvider, CdsPushService } from '@frontend/sports/content-distribution/feature';\nimport { NonRootToken } from '@frontend/sports/host-app/sports-product/feature/non-root-token';\nimport { filterSportsEmitLast } from '@frontend/sports/host-app/sports-product/feature/utils';\nimport { TrackingService, trackingConstants } from '@frontend/sports/tracking/feature';\nimport { SportsClientConfigRefreshService, UserService } from '@frontend/sports/user/feature';\nimport { DeviceService, NativeAppService } from '@frontend/vanilla/core';\nimport { PageVisibilityService } from '@frontend/vanilla/features/page-visibility';\nimport { Subject, merge } from 'rxjs';\nimport { filter, throttleTime } from 'rxjs/operators';\n\nimport { FreshDataProvider, FreshDataType } from './fresh-data-provider.service';\nimport { OnlineDetectionService } from './online-detection.service';\n\n@Injectable({ providedIn: 'root' })\nexport class FreshDataService {\n private reloadNeededStream = new Subject();\n private isOnline = false;\n private isVisible = true;\n\n constructor(\n pageVisibilityService: PageVisibilityService,\n onlineDetection: OnlineDetectionService,\n private pushProvider: CdsPushProvider,\n private appConfig: AppConfig,\n private ms2Tracking: TrackingService,\n deviceService: DeviceService,\n nativeApp: NativeAppService,\n userService: UserService,\n @Inject(NonRootToken(FreshDataProvider)) freshDataProviders: FreshDataProvider[],\n clientConfigRefresh: SportsClientConfigRefreshService,\n private cdsPushService: CdsPushService,\n ) {\n pageVisibilityService\n .visibilityChange()\n .pipe(\n filter(() => deviceService.isMobile),\n filterSportsEmitLast(),\n )\n .subscribe(this.visibilityChange);\n\n onlineDetection.onlineChange().pipe(filterSportsEmitLast()).subscribe(this.onlineChange);\n\n nativeApp.eventsFromNative\n .pipe(\n filter((e) => e.eventName === 'APP_FOREGRND'),\n filterSportsEmitLast(),\n )\n .subscribe(() => this.requestReload(FreshDataType.DataAndPush));\n\n merge(...freshDataProviders.map((_) => _.isRefreshNeeded()))\n .pipe(filterSportsEmitLast())\n .subscribe((type) => this.requestReload(type));\n\n merge(userService.onAuthenticationChange$, clientConfigRefresh.locationDependantDataNeeded$)\n .pipe(filterSportsEmitLast())\n .subscribe(() => {\n this.requestReload(FreshDataType.Data);\n });\n }\n\n readonly reloadNeeded = this.reloadNeededStream.pipe(throttleTime(150));\n\n private visibilityChange = ({ isVisible, pageHiddenDuration }: { isVisible: boolean; pageHiddenDuration?: number }) => {\n this.isVisible = isVisible;\n if (!this.isVisible) {\n return;\n }\n\n const pageHiddenForTooLong =\n (pageHiddenDuration || 0) > this.appConfig.pageHiddenReloadThreshold && this.appConfig.pageHiddenReloadThreshold > 0;\n\n if (this.isOnline && (this.pushProvider.isDisconnected || pageHiddenForTooLong)) {\n if (pageHiddenForTooLong) {\n this.ms2Tracking.track(trackingConstants.EVENT_RELOAD_HIDDEN, {\n [trackingConstants.PAGE_RELOAD_AFTER_HIDDEN]: pageHiddenDuration,\n });\n }\n this.requestReload(FreshDataType.DataAndPush);\n }\n };\n\n private onlineChange = ({ isOnline, offlineDuration }: { isOnline: boolean; offlineDuration?: number }) => {\n this.isOnline = isOnline;\n\n if (!isOnline) {\n return;\n }\n\n const offlineForTooLong =\n (offlineDuration || 0) > this.appConfig.offlineDurationReloadThreshold && this.appConfig.offlineDurationReloadThreshold > 0;\n\n if (this.isVisible && (offlineForTooLong || this.pushProvider.isDisconnected)) {\n if (offlineForTooLong) {\n this.ms2Tracking.track(trackingConstants.EVENT_RELOAD_OFFLINE, {\n [trackingConstants.PAGE_RELOAD_AFTER_OFFLINE]: offlineDuration,\n });\n }\n this.requestReload(FreshDataType.DataAndPush);\n }\n };\n\n private async requestReload(type: FreshDataType): Promise {\n if (type === FreshDataType.DataAndPush) {\n await this.cdsPushService.restart();\n }\n\n this.reloadNeededStream.next(type);\n }\n}\n","import { Injectable } from '@angular/core';\n\nimport { StatisticsMode } from '@cds/query-objects';\nimport { StatisticsConfig } from '@frontend/sports/common/client-config-data-access';\nimport { SportConstant } from '@frontend/sports/common/core/data-access/constants';\nimport { NumberDictionary } from '@frontend/sports/common/core/utils/extended-types';\nimport { CompetitionRecordsFormat } from '@frontend/sports/types/components/statistics';\nimport { isEmpty, keyBy } from 'lodash-es';\n\n@Injectable({\n providedIn: 'root',\n})\nexport class StatisticsConfigService {\n private readonly teamRankEnabledSportsList: NumberDictionary;\n private readonly pitcherEnabledSportsList: NumberDictionary;\n\n constructor(private statisticsConfig: StatisticsConfig) {\n this.teamRankEnabledSportsList = keyBy(this.statisticsConfig.teamRankEnabledSports, (sport) => sport);\n this.pitcherEnabledSportsList = keyBy(this.statisticsConfig.pitcherEnabledSports, (sport) => sport);\n }\n\n get teamRankEnabledSports(): NumberDictionary {\n return this.teamRankEnabledSportsList;\n }\n\n get pitcherEnabledSports(): NumberDictionary {\n return this.pitcherEnabledSportsList;\n }\n\n get teamRecordsFormat(): { [sport: number]: CompetitionRecordsFormat } {\n return this.statisticsConfig.teamRecordsFormat;\n }\n\n showStats(sportId: SportConstant): boolean {\n return !!(this.teamRankEnabledSports[sportId] || this.pitcherEnabledSports[sportId] || !isEmpty(this.teamRecordsFormat[sportId]));\n }\n\n getStatisticsModesBySportId(sportId?: number): string {\n const statModes: string[] = [];\n let showRank;\n let showPitcher;\n let showRecords;\n if (sportId) {\n showRank = !!this.teamRankEnabledSportsList[sportId];\n showPitcher = !!this.pitcherEnabledSportsList[sportId];\n showRecords = !isEmpty(this.statisticsConfig.teamRecordsFormat[sportId]);\n } else {\n showRank = this.statisticsConfig.teamRankEnabledSports?.length !== 0;\n showPitcher = this.statisticsConfig.pitcherEnabledSports?.length !== 0;\n showRecords = !isEmpty(this.statisticsConfig.teamRecordsFormat);\n }\n\n if (!showRank && !showPitcher && !showRecords) {\n return StatisticsMode.None.toString();\n }\n\n if (showRank && showPitcher && showRecords) {\n return StatisticsMode.All.toString();\n }\n\n if (showRank) {\n statModes.push(StatisticsMode.Rank.toString());\n }\n if (showPitcher) {\n statModes.push(StatisticsMode.Pitchers.toString());\n }\n if (showRecords) {\n statModes.push(StatisticsMode.SeasonStandings.toString());\n }\n\n return statModes.toString();\n }\n}\n","import { MessageEnvelope, MessageType } from '@cds/push';\nimport { BetBuilderOfferUpdateCommand } from '@cds/push/bet-builder-commands';\nimport {\n FixtureUpdateCommand,\n GameDeleteCommand,\n GameUpdateCommand,\n OptionMarketDeleteCommand,\n OptionMarketUpdateCommand,\n ParticipantUpdateCommand,\n PatchScoreboardCommand,\n PlayerStatsCommand,\n SlimScoreboardCommand,\n} from '@cds/push/fixture-commands';\nimport { BaseEventModel } from '@frontend/sports/betting-offer/feature/model';\nimport { CdsPushService, CdsSubscription } from '@frontend/sports/content-distribution/feature';\nimport { flatten } from 'lodash-es';\nimport { EMPTY, Observable, isObservable } from 'rxjs';\nimport { catchError, first } from 'rxjs/operators';\n\nexport type EventCallback = (event: T, message: MessageEnvelope) => void;\nexport type MessageCallback = (message: MessageEnvelope) => void;\n//TODO consider better typing for MessageHandler where event param dictate the type\n// export type ConreteMessageHandler extends BaseMessageHandler {event: 'TypeStringAsType', payload: ThisTypePayload}\nexport type MessageHandler = (\n event: T,\n payload:\n | FixtureUpdateCommand\n | BetBuilderOfferUpdateCommand\n | ParticipantUpdateCommand\n | (GameDeleteCommand & OptionMarketDeleteCommand)\n | (GameUpdateCommand & OptionMarketUpdateCommand)\n | SlimScoreboardCommand\n | PlayerStatsCommand\n | PatchScoreboardCommand,\n) => T | Promise | Observable;\nexport interface EventSubscriptionRequest {\n event: T;\n callback?: EventCallback;\n}\n\nexport class EventSubscription {\n constructor(\n private subscriptions: Map,\n private teardown: (subscriptions: CdsSubscription[]) => void,\n ) {}\n\n unsubscribe(): void {\n if (this.subscriptions.size > 0) {\n this.teardown(flatten([...this.subscriptions.values()]));\n }\n }\n\n add(other: EventSubscription): EventSubscription {\n return new EventSubscription(this.merge(this.subscriptions, other.subscriptions), this.teardown);\n }\n\n remove(events: (T | string)[]): void {\n const subscriptions: CdsSubscription[] = [];\n\n for (const event of events) {\n const id = typeof event === 'string' ? event : event.id;\n const current = this.subscriptions.get(id);\n\n if (current) {\n this.subscriptions.delete(id);\n subscriptions.push(...current);\n }\n }\n\n this.teardown(subscriptions);\n }\n\n private merge(actual: Map, other: Map): Map {\n const merged = new Map();\n\n for (const map of [actual, other]) {\n for (const [id, subscriptions] of map) {\n const current = merged.get(id);\n\n if (current) {\n current.push(...subscriptions);\n } else {\n merged.set(id, subscriptions);\n }\n }\n }\n\n return merged;\n }\n}\n\nexport abstract class BaseSubscriptionService {\n private handlers = new Map>();\n\n protected constructor(private push: CdsPushService) {\n this.handlers = this.getHandlers();\n\n if (!this.handlers || this.handlers.size === 0) {\n throw new Error('No handler registered');\n }\n }\n\n subscribe(request: EventSubscriptionRequest[]): EventSubscription {\n const subscriptions = new Map();\n\n for (const current of request) {\n let source = subscriptions.get(current.event.id);\n\n if (!source) {\n source = [];\n subscriptions.set(current.event.id, source);\n }\n\n source.push(...this.getEventSubscriptions(current));\n }\n\n if (subscriptions.size > 0) {\n this.push.subscribe(flatten([...subscriptions.values()]));\n }\n\n return new EventSubscription(subscriptions, this.push.unsubscribe.bind(this.push));\n }\n\n protected abstract getHandlers(): Map>;\n protected abstract getContextSubscriptions(event: T, callback: MessageCallback): CdsSubscription[];\n\n private getEventSubscriptions({ event, callback }: EventSubscriptionRequest): CdsSubscription[] {\n const subscriptionHandler = (message: MessageEnvelope) => {\n const handler = this.handlers.get(message.messageType);\n\n if (handler) {\n try {\n // eslint-disable-next-line @typescript-eslint/no-unsafe-argument\n const result = handler(event, message.payload);\n const apply = (updated: T) => callback?.(updated, message);\n const error = (reason: unknown) => {\n console.error('Can not apply update to eventModel', reason);\n };\n\n if (isObservable(result)) {\n result\n .pipe(\n first(),\n catchError((reason) => {\n error(reason);\n\n return EMPTY;\n }),\n )\n .subscribe(apply);\n } else if (result instanceof Promise) {\n Promise.resolve(result).then(apply).catch(error);\n } else {\n apply(result);\n }\n } catch (error) {\n console.group('Can not apply update to eventModel');\n console.error(message);\n console.error(error);\n console.groupEnd();\n }\n } else {\n console.warn('No handler registered for the command', message);\n }\n };\n\n return this.getContextSubscriptions(event, subscriptionHandler);\n }\n}\n","import { BasePrice } from '@cds/betting-offer';\nimport { LegInfo, SuspensionState } from '@cds/betting-offer/domain-specific/bet-builder';\nimport { EventModel } from '@frontend/sports/betting-offer/feature/model';\n\nexport function updateBetbuilderPrecreatedGroup(\n event: EventModel,\n sgpId: string,\n odds: BasePrice,\n suspensionState: SuspensionState,\n legs: LegInfo[],\n): void {\n if (event.precreatedOptionGroups) {\n const group = event.precreatedOptionGroups.find((g) => g.builderOptionPricing.sgpId === sgpId);\n if (group) {\n const updatedGroup = { ...group };\n const updatedPricing = {\n ...group.builderOptionPricing,\n odds,\n suspensionState,\n legInformation: legs,\n };\n updatedGroup.builderOptionPricing = updatedPricing;\n\n const index = event.precreatedOptionGroups.indexOf(group, 0);\n if (index > -1) {\n const updatedArray = [...event.precreatedOptionGroups];\n updatedArray.splice(index, 1);\n updatedArray.push(updatedGroup);\n // eslint-disable-next-line no-param-reassign\n event.precreatedOptionGroups = updatedArray;\n }\n }\n }\n}\n","import { Injectable } from '@angular/core';\n\nimport { OfferSource } from '@cds';\nimport { MessageType } from '@cds/push';\nimport { BetBuilderOfferUpdateCommand } from '@cds/push/bet-builder-commands';\nimport {\n FixtureUpdateCommand,\n GameDeleteCommand,\n GameUpdateCommand,\n OptionMarketDeleteCommand,\n OptionMarketUpdateCommand,\n SlimScoreboardCommand,\n} from '@cds/push/fixture-commands';\nimport { ScoreboardSlim } from '@cds/scoreboards/v1';\nimport { FixtureFactory } from '@frontend/sports/betting-offer/feature/fixture-factories';\nimport { EventModel } from '@frontend/sports/betting-offer/feature/model';\nimport { ConnectionConfig } from '@frontend/sports/common/client-config-data-access';\nimport {\n BetBuilderSubscription,\n CdsPushService,\n CdsSubscription,\n FixtureSlimSubscription,\n GameSubscription,\n OptionMarketSubscription,\n ScoreboardSlimSubscription,\n} from '@frontend/sports/content-distribution/feature';\nimport {\n BaseSubscriptionService,\n EventSubscription,\n EventSubscriptionRequest,\n MessageCallback,\n MessageHandler,\n} from '@frontend/sports/event-subscription/feature/base-subscription';\nimport { updateBetbuilderPrecreatedGroup } from '@frontend/sports/event-subscription/feature/precreated-group-commands';\n\n/**\n * This service should be used for subscribing event for push. Should not be used for\n * detail event. It automatically subscribes to scoreboard slim and all markets that\n * are part of the response. Should be typically used for marquee, live navigation etc.\n *\n * @export\n * @class EventSubscriptionService\n * @extends {BaseSubscriptionService}\n */\n@Injectable({ providedIn: 'root' })\nexport class EventSubscriptionService extends BaseSubscriptionService {\n constructor(\n private fixtureFactory: FixtureFactory,\n push: CdsPushService,\n protected connection: ConnectionConfig,\n ) {\n super(push);\n }\n\n override subscribe(request: EventSubscriptionRequest[]): EventSubscription {\n if (request.some((current) => !this.isNormalEvent(current.event))) {\n console.warn('No support for detailed event');\n }\n\n return super.subscribe(request.filter((current) => this.isNormalEvent(current.event)));\n }\n\n protected getContextSubscriptions(event: EventModel, callback: MessageCallback): CdsSubscription[] {\n const subscriptions: CdsSubscription[] = [];\n\n subscriptions.push(new FixtureSlimSubscription(event.offerContext, event.id, this.connection.culture, callback));\n subscriptions.push(new ScoreboardSlimSubscription(event.offerContext, event.id, this.connection.culture, callback));\n\n event.optionGroups.forEach((optionGroup) => {\n if (event.offerSource === OfferSource.V2 || event.hybridFixtureData) {\n subscriptions.push(new OptionMarketSubscription(event.offerContext, event.id, optionGroup.id, this.connection.culture, callback));\n } else {\n subscriptions.push(new GameSubscription(event.offerContext, event.id, optionGroup.id, this.connection.culture, callback));\n }\n });\n\n if (event.precreatedOptionGroups?.length) {\n const betBuilderSubscriptions = event.precreatedOptionGroups\n .filter((g) => g.builderOptionPricing && g.builderOptionPricing.groupId)\n .map((g) => new BetBuilderSubscription([g.builderOptionPricing.groupId], callback));\n subscriptions.push(...betBuilderSubscriptions);\n }\n\n return subscriptions;\n }\n\n protected getHandlers(): Map> {\n const handlers = new Map>();\n\n handlers.set(MessageType.GameDelete, (evt, cmd) => this.deleteOptionGroup(evt, cmd as GameDeleteCommand & OptionMarketDeleteCommand));\n handlers.set(MessageType.GameUpdate, (evt, cmd) => this.updateOptionGroup(evt, cmd as GameUpdateCommand & OptionMarketUpdateCommand));\n handlers.set(MessageType.OptionMarketDelete, (evt, cmd) => this.deleteOptionGroup(evt, cmd as GameDeleteCommand & OptionMarketDeleteCommand));\n handlers.set(MessageType.OptionMarketUpdate, (evt, cmd) => this.updateOptionGroup(evt, cmd as GameUpdateCommand & OptionMarketUpdateCommand));\n handlers.set(MessageType.ScoreboardSlim, (evt, cmd) => this.updateScoreboard(evt, cmd as SlimScoreboardCommand));\n handlers.set(MessageType.FixtureUpdate, (evt, cmd) => this.updateFixture(evt, cmd as FixtureUpdateCommand));\n handlers.set(MessageType.BetBuilderOfferUpdate, (evt, cmd) => this.updateBetbuilderPrecreatedOffer(evt, cmd as BetBuilderOfferUpdateCommand));\n\n return handlers;\n }\n\n private isNormalEvent(event: EventModel): boolean {\n return !event.optionSets || event.optionSets.length === 0;\n }\n\n protected deleteOptionGroup(event: EventModel, command: GameDeleteCommand & OptionMarketDeleteCommand): EventModel {\n const optionGroupId = command.gameId || command.marketId;\n this.fixtureFactory.deleteOptionGroup(event, optionGroupId.toString());\n\n return event;\n }\n\n protected updateOptionGroup(event: EventModel, command: GameUpdateCommand & OptionMarketUpdateCommand): EventModel {\n const optionGroup = command.game || command.optionMarket;\n this.fixtureFactory.updateOptionGroup(event, optionGroup);\n\n return event;\n }\n\n protected updateScoreboard(event: EventModel, command: SlimScoreboardCommand): EventModel {\n const scoreboard = command.scoreboard as ScoreboardSlim;\n this.fixtureFactory.updateScoreboard(event, scoreboard);\n\n return event;\n }\n\n protected updateFixture(event: EventModel, command: FixtureUpdateCommand): EventModel {\n this.fixtureFactory.update(event, command.isOpenForBetting, command.stage);\n\n return event;\n }\n\n private updateBetbuilderPrecreatedOffer(event: EventModel, command: BetBuilderOfferUpdateCommand): EventModel {\n updateBetbuilderPrecreatedGroup(event, command.sgpId, command.odds, command.suspensionState, command.legs);\n\n return event;\n }\n}\n","import { Observable, Operator, OperatorFunction, Subscriber } from 'rxjs';\n\nclass BufferWithSizeSubscriber extends Subscriber {\n private buffer: T[] = [];\n\n constructor(\n public override destination: Subscriber,\n private bufferSize: number,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n closingNotifier: Observable,\n ) {\n super(destination);\n closingNotifier.subscribe(() => {\n this.emitAndClearBuffer(this.buffer);\n });\n }\n\n private emitAndClearBuffer(buffer: T[]): void {\n this.destination.next(buffer);\n this.buffer = [];\n }\n\n protected override _next(value: T): void {\n const buffer = this.buffer;\n buffer.push(value);\n\n if (buffer.length === this.bufferSize) {\n this.emitAndClearBuffer(buffer);\n }\n }\n\n protected override _complete(): void {\n const buffer = this.buffer;\n if (buffer.length > 0) {\n this.destination.next(buffer);\n }\n super._complete();\n }\n}\n\n// eslint-disable-next-line deprecation/deprecation\nclass BufferWithSizeOperator implements Operator {\n constructor(\n private bufferSize: number,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n private closingNotifier: Observable,\n ) {}\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n call(subscriber: Subscriber, source: any): any {\n // eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access\n return source.subscribe(new BufferWithSizeSubscriber(subscriber, this.bufferSize, this.closingNotifier));\n }\n}\n\n/**\n * Combination of buffer() and bufferCount()\n *\n * @param bufferSize Argument of bufferCount()\n * @param closingNotifier Argument of buffer()\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport function bufferWithSize(bufferSize: number, closingNotifier: Observable): OperatorFunction {\n return function bufferWithSizeOperatorFunction(source: Observable): Observable {\n // eslint-disable-next-line deprecation/deprecation\n return source.lift(new BufferWithSizeOperator(bufferSize, closingNotifier));\n };\n}\n","import { EventModel } from '@frontend/sports/betting-offer/feature/model';\n\nexport function getLeagueId(event: EventModel): number {\n return event.league.parentLeagueId || event.league.id;\n}\n","import { Fixture, ImageProfile } from '@cds/betting-offer';\nimport { BetBuilderFixture } from '@cds/betting-offer/domain-specific/bet-builder';\nimport { EventModel, SetTab } from '@frontend/sports/betting-offer/feature/model';\nimport { NumberDictionary } from '@frontend/sports/common/core/utils/extended-types';\nimport { Widget, WidgetLayoutResponse, WidgetLayoutTemplate, WidgetPage } from '@frontend/sports/types/components/widget';\n\nimport { BetBuilderPrecreatedModel } from '../betbuilder/model/bet-builder.model';\nimport { Tile } from '../highlights-marquee/marquee-tile.model';\nimport { getLeagueId } from './event-details-common.utils';\n\nexport interface DetailedEventViewModel {\n model?: EventModel;\n promotedTemplate: number[];\n preCreated?: BetBuilderPrecreatedModel;\n hasMarqueeAvailable: boolean;\n virtualCompetitionDetails?: VirtualCompetitionDetails;\n tiles: Tile[];\n fixture?: Fixture;\n betBuilderFixture?: BetBuilderFixture;\n widgetLayoutResponse?: WidgetLayoutResponse;\n}\n\nexport interface ModularEventViewModel {\n widgetPage?: WidgetPage;\n layoutTemplate?: WidgetLayoutTemplate;\n groupedWidgets: NumberDictionary[]>;\n widgetPositions: number[];\n}\n\nexport interface ResultEventModel {\n model?: EventModel;\n preCreated?: BetBuilderPrecreatedModel;\n virtualCompetitionDetails: VirtualCompetitionDetails | undefined;\n tiles?: Tile[];\n fixture?: Fixture;\n betBuilderFixture?: BetBuilderFixture;\n}\n\nexport enum HeaderContentDensity {\n Tiny,\n Small,\n Medium,\n Large,\n Fullscreen,\n Auto,\n}\n\nexport enum TreeItemType {\n Sport,\n Region,\n League,\n Event,\n Favourite,\n FavouriteLeague,\n}\n\nexport enum LeftNavType {\n Live,\n Prematch,\n Competition,\n}\n\nexport interface NavEventModel {\n data: EventModel;\n selected: boolean;\n showMarkets?: boolean;\n hidden?: boolean; // to hide favourited items from left nav\n favourited?: boolean;\n}\n\nexport interface BaseTreeItem {\n children: TreeItem[];\n}\n\nexport interface TreeItem extends BaseTreeItem {\n id: number;\n name: string;\n type: TreeItemType;\n selected: boolean;\n expanded: boolean;\n loading: boolean;\n icon: string;\n events: NavEventModel[];\n count?: number;\n class?: string;\n toggleMarkets?: boolean;\n collapsible: boolean;\n leftNavType: LeftNavType;\n sportId?: number;\n hidden?: boolean; // to hide favourited items from left nav\n hideToggleMarkets?: boolean;\n hideFavorites?: boolean;\n isVirtual?: boolean;\n virtualCompetitionId?: number;\n imageProfile?: ImageProfile;\n}\n\nexport interface GroupedEvents {\n [leagueId: number]: EventModel[];\n}\n\nexport class SelectionInfo {\n sportId: number;\n leagueId: number;\n eventId: string;\n\n constructor(event: EventModel) {\n return {\n sportId: event.sport.id,\n leagueId: getLeagueId(event),\n eventId: event.id,\n };\n }\n}\n\nexport enum FeatureTab {\n Scoreboard = 1,\n Video = 2,\n Simulation = 3,\n Statistics = 4,\n}\n\nexport enum MediaTab {\n Scoreboard = 'scoreboard',\n Video = 'video',\n Simulation = 'simulation',\n Statistics = 'statistics',\n}\n\nexport enum FeatureUrl {\n Scoreboard = 'score',\n Video = 'video',\n Simulation = 'animation',\n Statistics = 'stats',\n}\n\nexport interface VirtualCompetitionDetails {\n id: number;\n name: string;\n group?: { id: number; name: string };\n}\n\nexport interface PrepareOptionGroupsParams extends OptionMarketSetData {\n event: EventModel;\n}\n\nexport interface OptionMarketSetData {\n activeTab?: string | SetTab;\n groupedSetsIds?: number[];\n tag?: string;\n setTags?: string[];\n isSgp?: boolean;\n isPrecreatedBABTab?: boolean;\n}\n\nexport const DispatcherEvent = {\n EventDetails: {\n Expanded: 'EVENT_DETAILS_EXPANDED',\n SetTab: 'EVENT_DETAILS_SET_TAB',\n },\n};\n","import { Injectable } from '@angular/core';\n\nimport { EventModel } from '@frontend/sports/betting-offer/feature/model';\nimport { bufferWithSize } from '@frontend/sports/common/core/utils/rxjs';\nimport { EventCallback, EventSubscription } from '@frontend/sports/event-subscription/feature/base-subscription';\nimport { EventSubscriptionService } from '@frontend/sports/event-subscription/feature/event-subscription';\nimport { filterSportsEmitLast } from '@frontend/sports/host-app/sports-product/feature/utils';\nimport { Observable, Subject, asyncScheduler, noop } from 'rxjs';\nimport { debounceTime, distinct, filter, map, throttleTime } from 'rxjs/operators';\n\nimport { FreshDataService } from '../data-refresh/fresh-data.service';\n\n@Injectable({ providedIn: 'root' })\nexport class LiveEventsSubscriptionService {\n private subscriptions = new Map>();\n private eventsStream$ = new Subject();\n readonly updatedEvents$: Observable;\n\n constructor(\n private freshdataService: FreshDataService,\n private eventSubscriptionService: EventSubscriptionService,\n ) {\n const updateQueueInMilliseconds = 400;\n const maxUpdateQueueCount = 10;\n\n // buffer updates and notify at once for performance reasons\n const flush$ = this.eventsStream$.pipe(map(noop), throttleTime(updateQueueInMilliseconds, asyncScheduler));\n const debounceTimer = this.eventsStream$.pipe(debounceTime(updateQueueInMilliseconds));\n\n this.updatedEvents$ = this.eventsStream$.pipe(\n distinct((event) => event.id, flush$),\n bufferWithSize(maxUpdateQueueCount, debounceTimer),\n filter((events) => events && events.length > 0),\n );\n\n this.freshdataService.reloadNeeded.pipe(filterSportsEmitLast()).subscribe(() => this.unsubscribeAll());\n }\n\n isSubscribed(eventId: string): boolean {\n return this.subscriptions.has(eventId);\n }\n\n subscribe(model: EventModel): void {\n if (!this.subscriptions.get(model.id)) {\n const subscription = this.subscribeEvent(model);\n if (subscription) {\n this.subscriptions.set(model.id, subscription);\n }\n }\n }\n\n unsubscribe(fixtureId: string | string[]): void {\n const ids = Array.isArray(fixtureId) ? fixtureId : [fixtureId];\n this.unsubscribeEvent(ids);\n }\n\n unsubscribeAll(): void {\n this.subscriptions.forEach((s) => s.unsubscribe());\n this.subscriptions.clear();\n }\n\n private unsubscribeEvent(ids: string[]): void {\n ids.forEach((id) => {\n const subscription = this.subscriptions.get(id);\n if (subscription) {\n subscription.unsubscribe();\n this.subscriptions.delete(id);\n }\n });\n }\n\n private subscribeEvent(event: EventModel): EventSubscription {\n const callback: EventCallback = (data) => {\n const subscription = this.subscriptions.get(data.id);\n if (subscription) {\n this.eventsStream$.next(data);\n }\n };\n\n return this.eventSubscriptionService.subscribe([{ event, callback }]);\n }\n}\n","import { Injectable } from '@angular/core';\n\nimport { Fixture, FixtureStage, FixtureType, OfferCategory } from '@cds/betting-offer';\nimport { BatchRequest, FixtureRequest, FixtureState, OfferMapping, ScoreboardMode } from '@cds/query-objects';\nimport { ScoreboardSlim } from '@cds/scoreboards/v1';\nimport { FixtureFactory } from '@frontend/sports/betting-offer/feature/fixture-factories';\nimport { BatchResponse, BettingOfferApi } from '@frontend/sports/betting-offer/feature/offer-service';\nimport { FavouritesConfig } from '@frontend/sports/common/client-config-data-access';\nimport { isDefined } from '@frontend/sports/common/core/utils/extended-types';\nimport { FavouritesService, isFixtureFavourite } from '@frontend/sports/favourites/core/feature';\nimport { filterSportsEmitLast } from '@frontend/sports/host-app/sports-product/feature/utils';\nimport { UserService } from '@frontend/sports/user/feature';\nimport { MediaQueryService } from '@frontend/vanilla/core';\nimport { entries, groupBy, sortBy } from 'lodash-es';\nimport { BehaviorSubject, Observable } from 'rxjs';\n\nimport { FixtureList } from '../event-list-shared/sport/competitions/competition.models';\nimport { StatisticsConfigService } from '../statistics/statistics-config.service';\nimport { LiveEventsSubscriptionService } from './live-events-subscription.service';\n\n@Injectable({ providedIn: 'root' })\nexport class LiveFavouriteFixturesService {\n private fixtures$ = new BehaviorSubject([]);\n\n get change(): Observable {\n return this.fixtures$;\n }\n\n get fixtures(): Fixture[] {\n return this.fixtures$.getValue();\n }\n\n get liveFixtures(): Fixture[] {\n return this.fixtures.filter((fixture) => fixture.stage === FixtureStage.Live);\n }\n\n constructor(\n private userService: UserService,\n private favouritesConfig: FavouritesConfig,\n private bettingOfferApi: BettingOfferApi,\n private favouriteService: FavouritesService,\n private subscriptionsService: LiveEventsSubscriptionService,\n private fixtureFactory: FixtureFactory,\n private media: MediaQueryService,\n private statisticsConfigService: StatisticsConfigService,\n ) {\n const get = () => this.refresh(OfferMapping.None);\n\n this.userService.onUserLogin$.pipe(filterSportsEmitLast()).subscribe(get);\n\n this.subscriptionsService.updatedEvents$.pipe(filterSportsEmitLast()).subscribe((updatedEvents) => {\n const finishedFavourites = updatedEvents\n .filter((event) => event.scoreboard.isFinished)\n .map((event) =>\n this.favouriteService.favouritesList.find((favourite) => isFixtureFavourite(favourite) && favourite.itemId === event.id),\n )\n .filter(isDefined);\n\n this.subscriptionsService.unsubscribe(finishedFavourites.map((f) => f.itemId));\n this.favouriteService.remove(finishedFavourites).subscribe();\n });\n\n get();\n }\n\n isFavourited(id: string): boolean {\n return this.liveFixtures.some((f) => f.id === id);\n }\n\n isSwipeFavouriteEnabled(): boolean {\n return this.favouritesConfig.isFavouriteSwipeEnabled && this.media.isActive('lt-md');\n }\n\n isLiveFavouriteEnabled(): boolean {\n return this.favouritesConfig.isLiveFavouritesEnabled && !this.media.isActive('lt-md');\n }\n\n refresh(offerMapping?: OfferMapping, offerCategories?: OfferCategory): void {\n const fixtureFavsIds = this.favouritedListToRefresh();\n\n if (!this.favouritesConfig.isLiveFavouritesEnabled || !fixtureFavsIds.length) {\n this.fixtures$.next([]);\n\n return;\n }\n\n this.getFixturesById(fixtureFavsIds, offerMapping, offerCategories).subscribe((batchFixtures: BatchResponse | undefined) => {\n if (!batchFixtures) {\n this.fixtures$.next([]);\n\n return;\n }\n\n const expiredFixtureIds = this.getExpiredFixtures(batchFixtures);\n const fixtureList = this.getFixtures(batchFixtures);\n\n this.removeExpired(expiredFixtureIds);\n this.subscribeToPush(fixtureList);\n\n this.fixtures$.next(fixtureList);\n });\n }\n\n getFixtureList(groupByCompetition: boolean = false): FixtureList[] {\n const groupedFixtures = groupBy(this.fixtures, (favourite) =>\n groupByCompetition ? favourite.competition.parentLeagueId || favourite.competition.id : favourite.sport.id,\n );\n\n const result = entries(groupedFixtures).map(([_, value]) => ({\n fixtures: value.sort((a, b) => (a.startDate > b.startDate ? 1 : -1)),\n totalCount: value.length,\n }));\n\n return sortBy(result, (current) => current.fixtures[0].sport.id);\n }\n\n updateFixtureList(): void {\n const current = this.favouritedList();\n const filtered = this.fixtures.filter((fixture) => current.some((id) => fixture.id === id));\n\n this.fixtures$.next(filtered);\n }\n\n private subscribeToPush(list: Fixture[]): void {\n list.filter((fixture) => !this.subscriptionsService.isSubscribed(fixture.id))\n .map((fixture) => this.fixtureFactory.create(fixture))\n .forEach((event) => this.subscriptionsService.subscribe(event));\n }\n\n private removeExpired(expiredFixtures: string[]): void {\n const expiredFavourites = this.favouriteService.favouritesList.filter((favourite) =>\n expiredFixtures.some((exId) => favourite.itemId === exId),\n );\n\n this.favouriteService.remove(expiredFavourites).subscribe();\n }\n\n private favouritedList(): string[] {\n return this.favouriteService.favouritesList.filter((f) => isFixtureFavourite(f) && f.count > 0).map(({ itemId }) => itemId);\n }\n\n private favouritedListToRefresh(): string[] {\n return this.favouriteService.favouritesList.filter(isFixtureFavourite).map(({ itemId }) => itemId);\n }\n\n private getExpiredFixtures(batchFixtures: BatchResponse): string[] {\n const expiredFixtures: string[] = [];\n\n entries(batchFixtures).forEach(([key, value]) => {\n if (!value.fixtures.length) {\n expiredFixtures.push(key);\n } else {\n const fixture = value.fixtures[0];\n\n if (fixture.scoreboard) {\n const scoreboardResponse = fixture.scoreboard as ScoreboardSlim;\n const isFinished = scoreboardResponse.periodId === 255;\n if (isFinished) {\n expiredFixtures.push(key);\n }\n }\n }\n });\n\n return expiredFixtures;\n }\n\n private getFixtures(batchFixtures: BatchResponse): Fixture[] {\n return entries(batchFixtures)\n .filter(([_, value]) => value.fixtures.length)\n .map(([_, value]) => value.fixtures[0]);\n }\n\n private getFixturesById(\n fixtureIds: string[],\n offerMapping?: OfferMapping,\n offerCategories?: OfferCategory,\n ): Observable {\n const request: BatchRequest[] = fixtureIds.map((fixtureId) => ({\n batchId: fixtureId,\n request: {\n fixtureIds: fixtureId,\n fixtureTypes: FixtureType.Standard,\n state: FixtureState.Latest,\n offerMapping,\n offerCategories,\n scoreboardMode: ScoreboardMode.Slim,\n excludeLocationFilter: true,\n statisticsModes: this.statisticsConfigService.getStatisticsModesBySportId(undefined),\n },\n }));\n\n return this.bettingOfferApi.getBatchFixtureList(request);\n }\n}\n","export enum TrackNavArea {\n NavTop = 'TopNav',\n NavBottom = 'BottomNav',\n NavSub = 'SubNav',\n NavLive = 'LiveNav',\n NavVirtual = 'VirtualNav',\n NavBreadcrumb = 'Breadcrumb',\n}\n","import { ActivatedRouteSnapshot, Params } from '@angular/router';\n\nexport function getAllParams(route: ActivatedRouteSnapshot | undefined): Params {\n let params = { ...route?.params };\n let parentRoute = route?.parent;\n\n while (parentRoute) {\n params = Object.assign(params, parentRoute.params);\n parentRoute = parentRoute.parent;\n }\n\n return params;\n}\n","import { Injectable } from '@angular/core';\nimport { takeUntilDestroyed } from '@angular/core/rxjs-interop';\nimport { ActivationEnd } from '@angular/router';\n\nimport { BetslipBarConfig, LayoutNavigationConfig } from '@frontend/sports/common/client-config-data-access';\nimport { RouteTag } from '@frontend/sports/common/core/data-access/route';\nimport { getAllParams } from '@frontend/sports/common/core/utils/route-params';\nimport { RouterEventsService } from '@frontend/sports/common/core/utils/router-events';\nimport { HeaderService, MenuItemsService, MenuSection } from '@frontend/vanilla/core';\nimport { toInteger } from 'lodash-es';\nimport { Observable, Subject, combineLatest } from 'rxjs';\nimport { filter } from 'rxjs/operators';\n\nimport { PopupManager } from '../popup/popup-manager.service';\nimport { TrackNavArea } from './navigation-tracking.model';\n\n@Injectable({ providedIn: 'root' })\nexport class NavigationRouterService {\n private lastActiveItemName: string | null;\n private navigationState$ = new Subject();\n private lastSelectedSport$ = new Subject();\n private _activeNavigation?: string;\n private selectedEventId?: string;\n\n constructor(\n private eventRouter: RouterEventsService,\n private menuService: MenuItemsService,\n private popupManager: PopupManager,\n private headerService: HeaderService,\n private layoutNavigationConfig: LayoutNavigationConfig,\n public betslipBarConfig: BetslipBarConfig,\n ) {\n this.eventRouter.currentActivationEnd\n .pipe(\n filter((e) => !!e),\n takeUntilDestroyed(),\n )\n .subscribe((event) => this.handleRouterEvent(event));\n this.popupManager.setLastActiveItem.pipe(takeUntilDestroyed()).subscribe(() => this.restoreLastActiveItem());\n\n combineLatest([this.navigationState$, this.headerService.whenReady])\n .pipe(takeUntilDestroyed())\n .subscribe(([navigationState, _]) => this.headerService.highlightProduct(navigationState));\n }\n\n setNavigation(active: string | null): void {\n const activeValue = toInteger(active);\n\n if (active && activeValue) {\n this.lastSelectedSport$.next(activeValue);\n }\n\n if (active !== 'all') {\n this.lastActiveItemName = active;\n }\n\n this.setActiveItem(active);\n\n if (active === 'live' || active === 'virtual') {\n this.navigationState$.next(active);\n } else {\n this.navigationState$.next(null);\n }\n }\n\n restoreLastActiveItem(): void {\n this.setActiveItem(this.lastActiveItemName);\n }\n\n resetNavigation(): void {\n this.setActiveItem(null);\n }\n\n get activeNavigation(): string | undefined {\n return this._activeNavigation;\n }\n\n get selectedSport$(): Observable {\n return this.lastSelectedSport$.asObservable();\n }\n\n get getSelectedEventId(): string | undefined {\n return this.selectedEventId;\n }\n\n private setActiveItem(itemName: string | null): void {\n this.menuService.setActive(TrackNavArea.NavTop, itemName);\n let bottomNavItem: string | null = null;\n\n if (itemName) {\n bottomNavItem =\n this.layoutNavigationConfig.bottomNavItemsMapping[itemName] ||\n this.layoutNavigationConfig.bottomNavItemsMapping['fallback'] ||\n itemName;\n }\n\n this.menuService.setActive(MenuSection.BottomNav, bottomNavItem);\n this._activeNavigation = itemName ?? undefined;\n }\n\n private handleRouterEvent(event: ActivationEnd | undefined): void {\n const params = getAllParams(event?.snapshot);\n\n let eventId: string = params.sportId || params.sport;\n\n if (event?.snapshot?.data?.tag === RouteTag.EsportsLobby) {\n eventId = event?.snapshot.data?.tag;\n }\n\n const popupQueryParams = event?.snapshot.queryParams?.['popup'];\n const isAzMenuOpened = popupQueryParams === 'sports';\n if (!isAzMenuOpened && eventId) {\n this.setNavigation(eventId);\n }\n this.selectedEventId = params.event;\n }\n}\n","/* eslint-disable @typescript-eslint/prefer-ts-expect-error */\n\n/* eslint-disable @typescript-eslint/ban-ts-comment */\nimport { NumberDictionary, StringDictionary } from '@frontend/sports/common/core/utils/extended-types';\n\nexport const isObjectEqualShadowCopy = (prev: T, next: T): boolean => {\n // eslint-disable-next-line eqeqeq\n if (prev === null && next === null) {\n // When both null then objects are equal\n return true;\n }\n\n // eslint-disable-next-line eqeqeq\n if (prev === null || next === null) {\n // When one is null then objects are unequal\n return false;\n }\n\n if (prev === undefined && next === undefined) {\n // When both undefined then objects are equal\n return true;\n }\n\n if (prev === undefined || next === undefined) {\n // When one is undefined then objects are unequal\n return false;\n }\n\n const prevKeys = Object.keys(prev);\n const nextKeys = Object.keys(next);\n if (prevKeys.length !== nextKeys.length) {\n // If number of keys is different: unequal\n return false;\n }\n for (const key of nextKeys) {\n //below ignore was added to get rid of suppressImplicitAnyIndexErrors in our tsconfig, at least it's limited to this usage now.\n //@ts-ignore\n if (prev[key] !== next[key]) {\n // One key is different: unequal\n return false;\n }\n }\n\n return true; // Keys are the same: equal\n};\n\n/**\n * @deprecated This class is deprecated and will be removed in future versions.\n * Use `values` from lodash instead.\n */\nexport const objectValues = (obj: { [key: number]: TValue } | { [key: string]: TValue }): TValue[] => {\n // @ts-ignore ES2017 check\n if (Object.values) {\n // @ts-ignore ES2017 check\n return Object.values(obj);\n } else {\n const keys = Object.keys(obj);\n const res: TValue[] = [];\n for (const key of keys) {\n //below ignore was added to get rid of suppressImplicitAnyIndexErrors in our tsconfig, at least it's limited to this usage now.\n //@ts-ignore\n // eslint-disable-next-line @typescript-eslint/no-unsafe-argument\n res.push(obj[key]);\n }\n\n return res;\n }\n};\n\n/**\n * @deprecated This class is deprecated and will be removed in future versions.\n * Use `entries` from lodash instead.\n * @example\n * _.map(_.entries(obj), ([key, value]) => ({ key, value }))\n */\nexport const objectEntries = (obj: StringDictionary | NumberDictionary): { key: string; value: TValue }[] => {\n // @ts-ignore ES2017 check\n if (Object.entries) {\n // @ts-ignore ES2017 check\n return Object.entries(obj).map((e: [string, TValue]) => ({ key: e[0], value: e[1] }));\n } else {\n //below ignore was added to get rid of suppressImplicitAnyIndexErrors in our tsconfig, at least it's limited to this usage now.\n //@ts-ignore\n // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment\n return Object.keys(obj).map((k) => ({ key: k, value: obj[k] }));\n }\n};\n\n/**\n * @deprecated This class is deprecated and will be removed in future versions.\n * Use `omit` and `omitBy` from lodash instead.\n */\n// eslint-disable-next-line @typescript-eslint/ban-types\nexport const removeObjectKey = (\n // @ts-ignore key\n obj: { [key: TKey]: TValue },\n key: TKey,\n // @ts-ignore key\n): { [key: TKey]: TValue } => {\n const newObj = { ...obj };\n // @ts-ignore key\n //eslint-disable-next-line @typescript-eslint/no-dynamic-delete\n delete newObj[key];\n\n return newObj;\n};\n\nexport const mapMirror = function (obj: Map): Map {\n const entries = obj.entries();\n const result = new Map();\n for (const entry of entries) {\n result.set(entry[1], entry[0]);\n }\n\n return result;\n};\n\nexport const calcDiff = function (prev: T[], next: T[]): { added: T[]; removed: T[] } {\n if ((!prev && !next) || (prev.length === 0 && next.length === 0)) {\n return { removed: [], added: [] };\n }\n if (!prev || prev.length === 0) {\n return { removed: [], added: [...next] };\n }\n if (!next || next.length === 0) {\n return { removed: [...prev], added: [] };\n }\n\n const removed = new Map(prev.map((item) => [item, true]));\n const added = new Map(next.map((item) => [item, true]));\n for (const [item] of removed) {\n if (added.has(item)) {\n removed.set(item, false);\n }\n }\n for (const [item] of added) {\n if (removed.has(item)) {\n added.set(item, false);\n }\n }\n\n return {\n removed: [...removed.entries()].filter((e) => !!e[1]).map((e) => e[0]),\n added: [...added.entries()].filter((e) => !!e[1]).map((e) => e[0]),\n };\n};\n\nexport const toDictionary = function (\n list: T[] | undefined,\n keySelector: (item: T) => TKey,\n valueSelector: (item: T, index: number) => TValue,\n): StringDictionary {\n const result: StringDictionary = {};\n\n if (!list) {\n return {};\n }\n\n for (let i = 0; i < list.length; i++) {\n const item = list[i];\n const key = keySelector(item);\n\n if (key in result) {\n console.warn('Item with the same key has already been added');\n continue;\n }\n\n result[key] = valueSelector(item, i);\n }\n\n return result;\n};\n","import { FixturePage } from '@cds/betting-offer';\nimport { CountItem, LeagueItem } from '@frontend/sports/common/core/data-access/sport-model';\nimport { getReverseEnum } from '@frontend/sports/common/core/utils/extended-types';\nimport { GridSorting } from '@frontend/sports/grid/core/feature/model';\n\nimport { IntervalLink } from '../../../calendar/count-interval.service';\nimport { Tile } from '../../../highlights-marquee/marquee-tile.model';\nimport { CompetitionRoute } from '../../../navigation/navigation.models';\nimport type { ShowcaseModuleData } from '../../../showcase/showcase.model';\nimport { TabBarItem } from '../../../tab-bar/tab-bar.models';\nimport { TeamPagesViewModel } from '../../../teampages-core/team-pages.model';\n\nexport interface FixtureList extends FixturePage {\n sport?: CountItem;\n params?: CompetitionRoute;\n tabs?: TabBarItem[];\n sorting?: GridSorting;\n virtualCompetition?: LeagueItem;\n showcaseData?: ShowcaseModuleData;\n competitionTitle?: string;\n marqueeData?: Tile[];\n teampagesViewModel?: TeamPagesViewModel;\n calendarIntervals?: IntervalLink[];\n calendarPills?: TabBarItem[];\n sportsOverview?: CountItem;\n calendarLiveCounts?: CountItem;\n}\n\nexport enum FixtureTab {\n Matches = 1,\n Outrights = 2,\n Standings = 3,\n Specials = 4,\n Teams = 5,\n Priceboost = 6,\n}\n\nexport const ReverseFixtureTab = getReverseEnum(FixtureTab);\n","import { OfferSource } from '@cds';\nimport { Competition, Region } from '@cds/betting-offer/tags';\nimport { EventParticipant, EventParticipantImage } from '@frontend/sports/betting-offer/feature/model';\nimport { CountItem } from '@frontend/sports/common/core/data-access/sport-model';\nimport { FavouritesSport } from '@frontend/sports/favourites/core/feature';\nimport { LeagueGroup } from '@frontend/sports/grid/core/feature/model';\nimport { ObservableGrid } from '@frontend/sports/grid/core/feature/observables';\nimport { ParticipantDetail } from '@frontend/sports/types/components/team-pages';\n\nimport { Tile } from '../highlights-marquee/marquee-tile.model';\nimport { TabBarItem } from '../tab-bar/tab-bar.models';\n\nexport interface TeamPagesDetailsModel {\n teampagesGridViewModel: TeamPagesGridViewModel;\n bannerViewModel: BannerViewModel;\n marqueeTiles: Tile[];\n sportCounts: CountItem;\n}\nexport interface TeamPagesNavigationViewModel {\n teams: Team[];\n competitionTabs: TabBarItem[];\n currentSport: FavouritesSport;\n selectedTeam: Team;\n hasFavouritedTeams: boolean;\n popularTeams: ParticipantDetail[];\n}\nexport interface TeamPagesViewModel {\n teamPagesDetailsModel: TeamPagesDetailsModel;\n teamPagesNavigationViewModel: TeamPagesNavigationViewModel;\n}\n\nexport interface Team {\n name: string;\n id: number;\n participantImage?: EventParticipantImage;\n sportIcon?: string;\n isFavourited: boolean;\n isSelected: boolean;\n sport?: FavouritesSport;\n favouriteParticipant?: EventParticipant;\n offerSource?: OfferSource;\n competitionId?: string;\n teamType?: FavouriteType;\n}\n\nexport enum FavouriteType {\n League = 'L',\n Participant = 'P',\n ParticipantV2 = 'P2',\n Fixture = 'F',\n FixtureV2 = 'F2',\n}\n\nexport enum FixtureView {\n Grid,\n GridList,\n Standings,\n}\n\nexport enum FixtureTab {\n Matches = 1,\n Outrights = 2,\n Standings = 3,\n Specials = 4,\n}\n\nexport interface BannerViewModel {\n team: Team;\n backgroundImage: string;\n}\n\nexport enum TeamMenuItem {\n Showmore = '9999',\n MyTeams = '0',\n}\nexport interface TeamPageCompetition {\n id: string;\n name: string;\n sportId: number;\n}\n\nexport interface TeamPagesGridModel {\n tabs: TabBarItem[];\n gridModel?: ObservableGrid;\n view?: FixtureView;\n standings?: CountItem;\n currentStandingsGroups?: LeagueGroup[];\n currentStandingsSport?: CountItem;\n currentStandings?: CountItem;\n groups?: LeagueGroup[];\n noMarketsForTeam?: boolean;\n loading?: boolean;\n fixtureIds?: string[];\n}\n\nexport interface TeamPagesGridViewModel {\n teamPagesGridModel: TeamPagesGridModel;\n selectedTeam: Team;\n}\n\nexport interface MasterDataResponse {\n competition: Competition;\n region: Region;\n}\n","import { Inject, Injectable } from '@angular/core';\n\nimport { OfferSource } from '@cds';\nimport { Participant, ParticipantType } from '@cds/betting-offer';\nimport { Competition } from '@cds/betting-offer/tags';\nimport { TeamPagesConfig } from '@frontend/sports/common/client-config-data-access';\nimport { SportConstant } from '@frontend/sports/common/core/data-access/constants';\nimport { ExpiringCache } from '@frontend/sports/common/core/utils/expiring-cache';\nimport { BaseCdsApi, BaseCdsApiFactory, CDS_API_FACTORY } from '@frontend/sports/content-distribution/feature';\nimport { CompetitionDetail } from '@frontend/sports/types/components/team-pages';\nimport { LocalStoreService } from '@frontend/vanilla/core';\nimport { Observable, forkJoin, of } from 'rxjs';\nimport { map } from 'rxjs/operators';\n\nimport { MasterdataApiService } from '../cds/cds-masterdata-api.service';\nimport { FavouriteType, Team, TeamPageCompetition } from './team-pages.model';\n\n@Injectable({ providedIn: 'root' })\nexport class TeamPagesApiService {\n private cdsApi: BaseCdsApi;\n private teamsByComepetitionCache: ExpiringCache = new ExpiringCache(600);\n private teamsCountByCompetitionsCache: ExpiringCache = new ExpiringCache(600);\n private seeMoreStorageKey = 'seeMoreCompetition';\n constructor(\n @Inject(CDS_API_FACTORY) cdsApiServiceFactory: BaseCdsApiFactory,\n private teamPagesConfig: TeamPagesConfig,\n private m2LocalStore: LocalStoreService,\n private cdsMasterApi: MasterdataApiService,\n ) {\n this.cdsApi = cdsApiServiceFactory({ endpoint: '/masterdata' });\n }\n\n getTeamsByCompetitionId(id: string, sportId: number): Observable {\n if (!id) {\n return of([]);\n }\n const source = this.cdsApi.get('/competitions/participants/v2', { competitionId: id, sportId }).pipe(\n map((response) => {\n if (!response) {\n return [];\n }\n\n return response\n .filter(\n (item) =>\n item && item.participantId && (item.type && sportId === SportConstant.Soccer ? item.type === ParticipantType.Team : true),\n )\n .map((item) => ({\n id: item.participantId,\n name: item.name.value || '',\n competitionId: id,\n isFavourited: false,\n isSelected: false,\n teamType: this.getFavouriteType(item.source),\n offerSource: item.source,\n participantImage: item.image,\n }));\n }),\n );\n\n return this.teamsByComepetitionCache.getOrCreate(`teamPages-comp-${id}`, source);\n }\n\n mapTeam(participant: Participant, competitionId: string | undefined): Team {\n return {\n id: participant.participantId,\n name: participant.name.value || '',\n competitionId,\n isFavourited: false,\n isSelected: false,\n teamType: this.getFavouriteType(participant.source),\n offerSource: participant.source,\n participantImage: participant.image,\n };\n }\n\n getTeamsByQueryText(sportId: number, query: string): Observable {\n return this.cdsApi.get('/participants/by-text', { sportId, query }).pipe(\n map((response) => {\n if (!response) {\n return [];\n }\n\n return response.map((participant) => ({\n id: participant.participantId,\n name: participant.name.value,\n isSelected: false,\n isFavourited: false,\n offerSource: participant.source,\n teamType: this.getFavouriteType(participant.source),\n }));\n }),\n );\n }\n\n getCompetitionsAndTeamsCount(competitions: CompetitionDetail[], sportId?: number | 0): Observable {\n const seeMoreComp = this.seeMoreCompetition;\n const competitionData = [...competitions];\n let source: Observable;\n let compIds: string;\n if (seeMoreComp?.sportId === sportId && !competitions.some((comp) => comp.competitionId === seeMoreComp?.id)) {\n competitionData.push({\n competitionId: seeMoreComp.id,\n competitionName: seeMoreComp.name,\n });\n compIds = competitionData.map((s) => s.competitionId).join(',');\n source = forkJoin([this.GetCompetitionsWithSeeMore(competitionData, seeMoreComp), this.getCompetitionName(seeMoreComp)]).pipe(\n map(([response, name]) => {\n return this.getCompetitionTabs(response, name, seeMoreComp);\n }),\n );\n } else {\n compIds = competitionData.map((s) => s.competitionId).join(',');\n source = this.GetCompetitionsWithSeeMore(competitionData, seeMoreComp).pipe(\n map((response) => {\n return this.getCompetitionTabs(response);\n }),\n );\n }\n\n return this.teamsCountByCompetitionsCache.getOrCreate(`teampages-count-${compIds}`, source);\n }\n\n private getCompetitionTabs(response: CompetitionDetail[], name?: string, seeMoreComp?: TeamPageCompetition): CompetitionDetail[] {\n if (!response) {\n return [];\n }\n let data = [...response];\n const seeMoreCompetition = data.find((x) => x.competitionId === seeMoreComp?.id);\n if (seeMoreCompetition) {\n seeMoreCompetition.competitionName = name ?? '';\n data = data.filter((x) => x.competitionId !== seeMoreCompetition.competitionId);\n data.push(seeMoreCompetition);\n }\n\n return data;\n }\n\n GetCompetitionsWithSeeMore(competitionData: CompetitionDetail[], seeMoreComp: TeamPageCompetition): Observable {\n return this.cdsMasterApi.getCompetitionCount(competitionData).pipe(\n map((response) => {\n if (!response) {\n return [];\n }\n const competitionwithteam = competitionData.filter((c) => response[c.competitionId] > 0);\n if (competitionwithteam.length > this.teamPagesConfig.maxCompetitions) {\n const seemorecompetition = competitionwithteam.find((k) => k.competitionId === seeMoreComp?.id);\n if (seemorecompetition) {\n const data = competitionwithteam.splice(0, this.teamPagesConfig.maxCompetitions - 1);\n data.push(seemorecompetition);\n\n return data;\n }\n\n return competitionwithteam.splice(0, this.teamPagesConfig.maxCompetitions);\n }\n\n return competitionwithteam;\n }),\n );\n }\n\n getCompetitionName(competition: TeamPageCompetition): Observable {\n return this.cdsApi.post('/info/competition/v2', { CompetitionId: competition.id }).pipe(\n map((res) => {\n return res?.name?.value ?? '';\n }),\n );\n }\n\n get seeMoreCompetition(): TeamPageCompetition {\n return this.m2LocalStore.get(this.seeMoreStorageKey) as TeamPageCompetition;\n }\n\n private getFavouriteType(source: OfferSource | undefined): FavouriteType {\n if (source === OfferSource.V2) {\n return FavouriteType.ParticipantV2;\n }\n\n return FavouriteType.Participant;\n }\n}\n","import { Injectable } from '@angular/core';\n\nimport { FixturePage, FixtureType, OfferCategory } from '@cds/betting-offer';\nimport { CountsFixturesResponse } from '@cds/betting-offer/domain-specific';\nimport { TagType } from '@cds/betting-offer/tags';\nimport { CountsFixturesRequest, FixtureState, OfferMapping, SortByCriteria } from '@cds/query-objects';\nimport { BettingOfferApi } from '@frontend/sports/betting-offer/feature/offer-service';\nimport { toInteger } from 'lodash-es';\nimport { Observable, of } from 'rxjs';\n\nimport { Tile } from '../highlights-marquee/marquee-tile.model';\nimport { MarqueeService } from '../highlights-marquee/marquee.service';\nimport { StatisticsConfigService } from '../statistics/statistics-config.service';\nimport { FavouriteType, FixtureTab, Team } from './team-pages.model';\n\n@Injectable({ providedIn: 'root' })\nexport class TeamPagesCoreMarqueeService {\n constructor(\n private bettingOffer: BettingOfferApi,\n private marqueeService: MarqueeService,\n private statisticsConfigService: StatisticsConfigService,\n ) {}\n\n getFixturesMarquees(selectedTeam: Team, marqueeFixtures?: string[]): Observable {\n if (!selectedTeam) {\n return of([]);\n }\n if (marqueeFixtures?.length) {\n return this.loadFixtureMarquees(marqueeFixtures);\n }\n\n return of([]);\n }\n\n private loadFixtureMarquees(fixtureIds: string[]): Observable {\n return this.marqueeService.loadTeamPagesMarquees(fixtureIds);\n }\n\n getFixtures(itemId: number, sportId: number, tabId: number): Observable {\n const request: CountsFixturesRequest = this.buildCountsFixtureRequest(itemId.toString(), sportId.toString(), tabId);\n\n return this.bettingOffer.getFixtureList(request.fixtureRequest);\n }\n\n getFixtureCounts(itemId: number, sportId: number): Observable {\n const request: CountsFixturesRequest = this.buildCountsFixtureRequest(itemId.toString(), sportId.toString());\n\n return this.bettingOffer.getCountsFixtures(request);\n }\n\n buildCountsFixtureRequest(\n itemId: string,\n sportId: string,\n tabId?: number,\n favouriteType: FavouriteType = FavouriteType.Participant,\n take?: number,\n skip?: number,\n ): CountsFixturesRequest {\n const request: CountsFixturesRequest = {\n fixtureCountsRequest: {\n state: FixtureState.Latest,\n tagTypes: [TagType.Sport, TagType.Region, TagType.Tournament, TagType.Competition].join(','),\n extendedTags: [TagType.Sport, TagType.Region, TagType.Tournament, TagType.Competition].join(','),\n sortBy: SortByCriteria.Tags,\n sportIds: sportId,\n },\n fixtureRequest: {\n sportIds: sportId,\n excludeLocationFilter: true,\n fixtureCategories: this.getFixtureCategories(tabId),\n fixtureTypes: FixtureType.Standard,\n offerCategories: this.getOfferCategories(tabId),\n offerMapping: OfferMapping.Filtered,\n skip,\n sortBy: this.getSorting(tabId),\n state: FixtureState.Latest,\n take,\n statisticsModes: this.statisticsConfigService.getStatisticsModesBySportId(toInteger(sportId)),\n },\n };\n if (favouriteType === FavouriteType.League) {\n request.fixtureRequest.competitionIds = itemId;\n request.fixtureCountsRequest.competitionIds = itemId;\n }\n\n if (favouriteType === FavouriteType.Participant || favouriteType === FavouriteType.ParticipantV2) {\n request.fixtureRequest.fixtureParticipantIds = itemId;\n request.fixtureCountsRequest.fixtureParticipantIds = itemId;\n }\n\n return request;\n }\n\n private getSorting(tabId?: number): SortByCriteria {\n return tabId && (tabId === FixtureTab.Outrights || tabId === FixtureTab.Specials)\n ? SortByCriteria.QueryCompetitionIds\n : SortByCriteria.StartDate;\n }\n\n private getOfferCategories(tabId?: number): OfferCategory {\n if (tabId === FixtureTab.Outrights) {\n return OfferCategory.Outrights;\n }\n if (tabId === FixtureTab.Specials) {\n return OfferCategory.Specials;\n }\n\n return OfferCategory.Gridable;\n }\n\n private getFixtureCategories(tabId?: number): string {\n if (tabId === FixtureTab.Outrights) {\n return OfferCategory.Outrights;\n }\n if (tabId === FixtureTab.Specials) {\n return OfferCategory.Specials;\n }\n\n return [OfferCategory.Gridable, OfferCategory.NonGridable, OfferCategory.Other].join(',');\n }\n}\n","import { Injectable } from '@angular/core';\n\nimport { StatisticsConfig } from '@frontend/sports/common/client-config-data-access';\nimport { SportConstant } from '@frontend/sports/common/core/data-access/constants';\nimport { CountItem, LeagueItem, RegionItem, isVirtualCompetitionGroupItem } from '@frontend/sports/common/core/data-access/sport-model';\nimport { Omit, isDefined } from '@frontend/sports/common/core/utils/extended-types';\nimport { LeagueGroup } from '@frontend/sports/grid/core/feature/model';\nimport { flatten } from 'lodash-es';\n\nexport interface GetStandingsParams {\n sport: CountItem;\n selectedVirtualGroupId?: number;\n expandedGroups?: number;\n}\n@Injectable({ providedIn: 'root' })\nexport class TeamPagesStandingService {\n constructor(private statisticsConfig: StatisticsConfig) {}\n getStandingsGroups(params: GetStandingsParams): LeagueGroup[] {\n const { sport, selectedVirtualGroupId, expandedGroups = this.statisticsConfig.expandedLeagueTablesCount } = params;\n\n if (sport.id !== SportConstant.Soccer) {\n return [];\n }\n\n const leagueGroups = !selectedVirtualGroupId\n ? this.getStandingsForCompetitions(sport)\n : this.getStandingsForVirtualGroups(sport, selectedVirtualGroupId);\n\n leagueGroups.forEach((leagueGroup, index) => (leagueGroup.collapsed = index >= expandedGroups));\n\n return leagueGroups;\n }\n\n private getStandingsForCompetitions(sport: CountItem): LeagueGroup[] {\n const leagueGroups = flatten(\n (sport.children).map((region) =>\n flatten(\n (region.children).map((leagueItem) =>\n leagueItem.isVirtual ? this.mapVirtualCompetition(leagueItem, region) : this.mapCompetition(leagueItem, region),\n ),\n ),\n ),\n );\n\n return leagueGroups;\n }\n\n private getStandingsForVirtualGroups(sport: CountItem, selectedVirtualGroupId: number): LeagueGroup[] {\n const leagueGroups = flatten(\n (sport.children).map((region) =>\n flatten((region.children).map((leagueItem) => this.mapVirtualGroups(leagueItem, region, selectedVirtualGroupId))),\n ),\n );\n\n return leagueGroups;\n }\n\n private mapCompetition(competition: LeagueItem, region: RegionItem): LeagueGroup[] {\n if (!competition.standings) {\n return [];\n }\n\n return [{ ...this.toLeagueGroup(competition, region), id: competition.id, name: competition.name, canBeFavourited: true }];\n }\n\n private mapVirtualCompetition(competition: LeagueItem, region: RegionItem): LeagueGroup[] {\n const standings: LeagueGroup[] = [];\n\n if (competition.standings) {\n standings.push({\n ...this.toLeagueGroup(competition, region),\n id: competition.siblings[0],\n name: competition.name,\n canBeFavourited: true,\n });\n }\n\n return [...standings, ...this.mapVirtualGroups(competition, region)];\n }\n\n private mapVirtualGroups(competition: LeagueItem, region: RegionItem, selectedVirtualGroup?: number): LeagueGroup[] {\n if (!competition.isVirtual || !competition.children) {\n return [];\n }\n\n const leagueGroups = competition.children\n .filter(isVirtualCompetitionGroupItem)\n .filter((virtualGroup) => virtualGroup.standings && (!isDefined(selectedVirtualGroup) || virtualGroup.id === selectedVirtualGroup))\n .map((virtualGroup) => ({\n ...this.toLeagueGroup(virtualGroup, region),\n id: virtualGroup.siblings[0],\n name: `${competition.name} - ${virtualGroup.name}`,\n canBeFavourited: !(virtualGroup.stageIds.length || virtualGroup.groupIds.length),\n }));\n\n return leagueGroups;\n }\n\n private toLeagueGroup(leagueItem: LeagueItem, region: RegionItem): Omit {\n return {\n count: leagueItem.counts.preMatch + leagueItem.counts.live,\n events: [],\n collapsed: false,\n collapsible: true,\n deferred: false,\n siblings: [leagueItem.id],\n region,\n };\n }\n}\n","import { Injectable } from '@angular/core';\n\nimport { Fixture, FixturePage } from '@cds/betting-offer';\nimport { CountsFixturesResponse } from '@cds/betting-offer/domain-specific';\nimport { Competition } from '@cds/betting-offer/tags';\nimport { FavouritesConfig, Sitecore } from '@frontend/sports/common/client-config-data-access';\nimport { SportConstant } from '@frontend/sports/common/core/data-access/constants';\nimport {\n CountItem,\n ItemCount,\n isCompetitionItem,\n isVirtualCompetitionGroupItem,\n sportModelMethods,\n} from '@frontend/sports/common/core/data-access/sport-model';\nimport { GridGrouping, LeagueGroup, SubscriptionTopic } from '@frontend/sports/grid/core/feature/model';\nimport { GridOption, ObservableGrid, ObservableGridFactory } from '@frontend/sports/grid/core/feature/observables';\nimport { min, orderBy } from 'lodash-es';\nimport { Observable, of } from 'rxjs';\nimport { map, switchMap } from 'rxjs/operators';\n\nimport { ReverseFixtureTab } from '../event-list-shared/sport/competitions/competition.models';\nimport { StatisticsApiService } from '../statistics/statistics-api.service';\nimport { TabBarItem } from '../tab-bar/tab-bar.models';\nimport { TeamPagesCoreMarqueeService } from './team-pages-core-marquee.service';\nimport { TeamPagesStandingService } from './team-pages-standing.service';\nimport { FixtureTab, FixtureView, Team, TeamPagesGridModel } from './team-pages.model';\n\n@Injectable({ providedIn: 'root' })\nexport class TeamPagesCoreGridService {\n constructor(\n private teamPagesCoreMarqueeService: TeamPagesCoreMarqueeService,\n public sitecore: Sitecore,\n private config: FavouritesConfig,\n private gridFactory: ObservableGridFactory,\n private teamPagesStandingService: TeamPagesStandingService,\n private cdsStatsService: StatisticsApiService,\n ) {}\n\n getGridModel(selectedTeam: Team | undefined): Observable {\n if (!selectedTeam) {\n return of({ tabs: [] });\n }\n\n return this.teamPagesCoreMarqueeService\n .getFixtureCounts(selectedTeam.id, selectedTeam.sport!.id)\n .pipe(switchMap((fixtureCounts) => this.setGridModel(selectedTeam, fixtureCounts)));\n }\n\n getFixturesForTab(teamPagesGridTabsModel: TeamPagesGridModel, tabId: number, team: Team, data?: FixturePage): Observable {\n teamPagesGridTabsModel.view = this.getView(tabId);\n\n if (tabId === FixtureTab.Standings) {\n return this.getStandingsForTeam(teamPagesGridTabsModel, team.id.toString());\n }\n if (team.id && team.sport?.id) {\n if (!data) {\n return this.teamPagesCoreMarqueeService\n .getFixtures(team.id, team.sport.id, tabId)\n .pipe(map((fixturedata) => this.getResult(teamPagesGridTabsModel, tabId, team, fixturedata)));\n }\n\n return of(this.getResult(teamPagesGridTabsModel, tabId, team, data));\n }\n teamPagesGridTabsModel.noMarketsForTeam = true;\n\n return of(teamPagesGridTabsModel);\n }\n\n private buildGridModel(fixtures: Fixture[], options: GridOption = {}): ObservableGrid {\n return this.gridFactory.fromResponse(`teamsPages`, fixtures, options);\n }\n\n private getResult(teamPagesGridTabsModel: TeamPagesGridModel, tabId: number, team: Team, data?: FixturePage): TeamPagesGridModel {\n const nonMatchesTab = tabId === FixtureTab.Outrights || tabId === FixtureTab.Specials;\n let competitionsOrder: CountItem[] = [];\n if (nonMatchesTab && teamPagesGridTabsModel.standings) {\n competitionsOrder = this.getCompetitionItems([teamPagesGridTabsModel.standings]);\n }\n\n const gridOptions = {\n marketGrouping: teamPagesGridTabsModel?.view === FixtureView.GridList,\n collapsedThreshold: this.getCollapseSize(tabId),\n subscriptionTopic: this.getTopic(tabId),\n grouping: GridGrouping.League,\n };\n const fixtures = data?.fixtures;\n teamPagesGridTabsModel.fixtureIds = data?.fixtures.map((k) => k.id);\n if (fixtures?.length) {\n let sortedFixtures = fixtures;\n\n teamPagesGridTabsModel.tabs.forEach((x) => {\n if (x.id === FixtureTab.Matches && tabId === FixtureTab.Matches) {\n x.count = fixtures?.length;\n }\n });\n\n if (nonMatchesTab && competitionsOrder.length > 1) {\n sortedFixtures = orderBy(fixtures, (f) => {\n return this.getCompetitionOrder(f.competition, competitionsOrder);\n });\n }\n\n if (teamPagesGridTabsModel) {\n teamPagesGridTabsModel.currentStandingsGroups = [];\n teamPagesGridTabsModel.gridModel = this.buildGridModel(sortedFixtures, gridOptions);\n }\n }\n\n teamPagesGridTabsModel.noMarketsForTeam = !fixtures?.length;\n\n return teamPagesGridTabsModel;\n }\n\n private getCompetitionOrder(item: T, competitionsOrder: CountItem[]): number {\n return competitionsOrder.find((co) => co.id === item.id)?.order ?? 0;\n }\n\n private getStandingsForTeam(teamPagesGridTabsModel: TeamPagesGridModel, teamId: string): Observable {\n if (teamPagesGridTabsModel) {\n delete teamPagesGridTabsModel.gridModel;\n if (teamId) {\n teamPagesGridTabsModel.currentStandings = teamPagesGridTabsModel.standings;\n\n if (teamPagesGridTabsModel.currentStandings) {\n const standings = this.teamPagesStandingService.getStandingsGroups({\n sport: teamPagesGridTabsModel.currentStandings,\n expandedGroups: 2,\n });\n\n teamPagesGridTabsModel.currentStandingsSport = teamPagesGridTabsModel.standings;\n teamPagesGridTabsModel.currentStandingsGroups = standings;\n\n const groupIds = teamPagesGridTabsModel.currentStandingsGroups?.map((group) => group.id);\n if (!groupIds) {\n return of(teamPagesGridTabsModel);\n }\n if (teamPagesGridTabsModel.currentStandingsSport?.id !== SportConstant.Soccer) {\n return of(teamPagesGridTabsModel);\n }\n\n return this.cdsStatsService.getCompetitionsStatistics(groupIds, teamPagesGridTabsModel.currentStandingsSport?.id).pipe(\n map((statistics) => {\n if (statistics) {\n teamPagesGridTabsModel.groups = teamPagesGridTabsModel.currentStandingsGroups?.map((standingGroup) => ({\n ...standingGroup,\n statistics: statistics.find((s) => s.competitionId === standingGroup.id),\n }));\n }\n\n return teamPagesGridTabsModel;\n }),\n );\n }\n }\n teamPagesGridTabsModel.currentStandingsGroups = [];\n }\n\n return of(teamPagesGridTabsModel);\n }\n\n private getView(tabId: number): FixtureView {\n if (tabId === FixtureTab.Matches) {\n return FixtureView.Grid;\n }\n\n if (tabId === FixtureTab.Standings) {\n return FixtureView.Standings;\n }\n\n return FixtureView.GridList;\n }\n\n private getCompetitionItems(countItems: CountItem[]): CountItem[] {\n const result: CountItem[] = [];\n for (const countItem of countItems) {\n if (countItem.children.length > 0) {\n result.push(...this.getCompetitionItems(countItem.children));\n } else if (isCompetitionItem(countItem) || isVirtualCompetitionGroupItem(countItem)) {\n result.push(countItem);\n }\n }\n\n return result;\n }\n\n private getCollapseSize(tabId: number): number | undefined {\n return tabId === FixtureTab.Outrights || tabId === FixtureTab.Specials ? this.config.collapseSize : undefined;\n }\n\n private getTopic(tabId: number): SubscriptionTopic | undefined {\n if (tabId === FixtureTab.Specials) {\n return SubscriptionTopic.Specials;\n }\n\n if (tabId === FixtureTab.Outrights) {\n return SubscriptionTopic.Outrights;\n }\n\n return undefined;\n }\n\n private setGridModel(selectedTeam: Team, countsFixturesResponse: CountsFixturesResponse | undefined): Observable {\n const gridTabCountsItem = sportModelMethods.fromTag(countsFixturesResponse?.counts);\n const teamPagesGridTabsModel: TeamPagesGridModel = {\n standings: gridTabCountsItem[0],\n tabs: this.getNavigation(gridTabCountsItem),\n };\n\n const selectedTab = teamPagesGridTabsModel.tabs.find((tabs) => tabs.active);\n\n if (selectedTab) {\n return this.getFixturesForTab(teamPagesGridTabsModel, selectedTab?.id, selectedTeam, countsFixturesResponse?.fixtures);\n }\n\n teamPagesGridTabsModel.noMarketsForTeam = true;\n\n return of(teamPagesGridTabsModel);\n }\n\n private getNavigation(gridTabCountsItem: CountItem[]): TabBarItem[] {\n if (!gridTabCountsItem.length) {\n return [];\n }\n\n const navigation: TabBarItem[] = [];\n\n const tabCounts = gridTabCountsItem[0];\n const counts = tabCounts.counts;\n let competitionStandings = false;\n\n competitionStandings = tabCounts && this.hasLeagueStatistics(tabCounts.children);\n const matchesCount = min([counts.preMatch + counts.live, counts.gridable + counts.nonGridable + counts.other]);\n const shouldShowStandings = tabCounts.id === SportConstant.Soccer && competitionStandings;\n const tabToSelect = this.getTabToSelect(counts, shouldShowStandings, matchesCount!);\n const append = (tab: FixtureTab, count: number, selected?: boolean) => {\n if (count) {\n navigation.push({\n id: tab,\n title: this.sitecore.event[ReverseFixtureTab[tab]],\n count,\n active: selected,\n });\n }\n };\n\n if (matchesCount) {\n navigation.push({\n id: FixtureTab.Matches,\n title: this.sitecore.event[ReverseFixtureTab[FixtureTab.Matches]],\n active: tabToSelect === FixtureTab.Matches,\n count: matchesCount,\n });\n }\n\n append(FixtureTab.Outrights, counts.outrights, tabToSelect === FixtureTab.Outrights);\n append(FixtureTab.Specials, counts.specials, tabToSelect === FixtureTab.Specials);\n\n if (shouldShowStandings) {\n navigation.push({\n id: FixtureTab.Standings,\n title: this.sitecore.event[ReverseFixtureTab[FixtureTab.Standings]],\n count: 0,\n active: tabToSelect === FixtureTab.Standings,\n });\n }\n\n return navigation;\n }\n\n private hasLeagueStatistics(countItems: CountItem[]): boolean {\n for (const countItem of countItems) {\n if (countItem.children.length > 0) {\n const hasStats = this.hasLeagueStatistics(countItem.children);\n if (hasStats) {\n return hasStats;\n }\n } else if ((isCompetitionItem(countItem) || isVirtualCompetitionGroupItem(countItem)) && countItem.standings) {\n return true;\n }\n }\n\n return false;\n }\n\n private getTabToSelect(counts: ItemCount, shouldShowStandings: boolean, matchesCount: number): FixtureTab {\n if (matchesCount) {\n return FixtureTab.Matches;\n }\n if (counts.outrights) {\n return FixtureTab.Outrights;\n }\n if (shouldShowStandings) {\n return FixtureTab.Standings;\n }\n if (counts.specials) {\n return FixtureTab.Specials;\n }\n\n return FixtureTab.Matches;\n }\n}\n","import { Injectable } from '@angular/core';\nimport { ActivatedRouteSnapshot, Data, Params } from '@angular/router';\n\nimport { OfferSource } from '@cds';\nimport { ParticipantImage } from '@cds/betting-offer';\nimport { EventParticipant, EventParticipantImage } from '@frontend/sports/betting-offer/feature/model';\nimport { Sitecore, TeamPagesConfig, TeamPagesModuleConfig } from '@frontend/sports/common/client-config-data-access';\nimport { CountItem } from '@frontend/sports/common/core/data-access/sport-model';\nimport { toDictionary } from '@frontend/sports/common/core/utils/collection';\nimport { NumberDictionary } from '@frontend/sports/common/core/utils/extended-types';\nimport { getAllParams } from '@frontend/sports/common/core/utils/route-params';\nimport { FavouritesService, FavouritesSport, FavouritesViewModel } from '@frontend/sports/favourites/core/feature';\nimport { CompetitionDetail, ParticipantDetail } from '@frontend/sports/types/components/team-pages';\nimport { UserService } from '@frontend/sports/user/feature';\nimport { LocalStoreService } from '@frontend/vanilla/core';\nimport { isNumber, orderBy, sortBy } from 'lodash-es';\nimport { Observable, Subject, of } from 'rxjs';\nimport { first, map, switchMap } from 'rxjs/operators';\n\nimport { MasterdataApiService } from '../cds/cds-masterdata-api.service';\nimport { CompetitionListService } from '../competition-list/services/competition-list.service';\nimport { LiveFavouriteFixturesService } from '../favourites/live-favourites-fixtures.service';\nimport { Tile } from '../highlights-marquee/marquee-tile.model';\nimport { TabBarItem } from '../tab-bar/tab-bar.models';\nimport { TeamPagesApiService } from './team-pages-api.service';\nimport { TeamPagesCoreGridService } from './team-pages-core-grid.service';\nimport { TeamPagesCoreMarqueeService } from './team-pages-core-marquee.service';\nimport {\n FavouriteType,\n Team,\n TeamMenuItem,\n TeamPageCompetition,\n TeamPagesDetailsModel,\n TeamPagesGridModel,\n TeamPagesNavigationViewModel,\n TeamPagesViewModel,\n} from './team-pages.model';\n\n@Injectable({ providedIn: 'root' })\nexport class TeamPagesCoreService {\n private blackListCompetitions: NumberDictionary;\n private backgroundColor: { [x: number]: string };\n private backgroundImage: { [x: number]: string };\n private seeMoreStorageKey = 'seeMoreCompetition';\n private readonly v2Prefix = '2_';\n private readonly v1Prefix = '';\n private readonly homeTeamSuffix = '_1';\n private readonly logoSuffix = '_logo';\n private selectedTeaminfo = new Subject();\n constructor(\n private teamPagesConfig: TeamPagesConfig,\n private favouritesService: FavouritesService,\n private userService: UserService,\n private teamPagesData: TeamPagesModuleConfig,\n public sitecore: Sitecore,\n private liveFavouritesService: LiveFavouriteFixturesService,\n private teamPagesCoreMarqueeService: TeamPagesCoreMarqueeService,\n private api: TeamPagesApiService,\n private m2LocalStore: LocalStoreService,\n private teamPagesCoreGridService: TeamPagesCoreGridService,\n private competitionListService: CompetitionListService,\n private cdsMasterApi: MasterdataApiService,\n ) {\n this.blackListCompetitions = this.getBlackListCompetitions();\n }\n\n get blackListCompetitionsIds(): NumberDictionary {\n return this.blackListCompetitions;\n }\n\n isTeamPagesEnabledForSport(sportId: number): boolean {\n return this.teamPagesConfig.isEnabled && this.teamPagesConfig.whitelistedSports.includes(sportId);\n }\n\n get selectedTeam(): Observable {\n return this.selectedTeaminfo.asObservable();\n }\n\n selectedTeamChanged(team: Team): void {\n this.selectedTeaminfo.next(team);\n }\n\n getModelByCompetition(\n route: ActivatedRouteSnapshot,\n isNavigationViewModel: boolean = false,\n compoundId?: string | undefined,\n ): Observable {\n const { sport, competition, team, league } = getAllParams(route);\n // if no competitions or leauge we need to show myteams which has favourite teams\n if (!competition && !league) {\n const response = this.getFavouriteTeams(Number(sport));\n let favouriteTeams: FavouritesViewModel[] = [];\n let teams: Team[] = [];\n if (response?.length) {\n favouriteTeams = response.filter(\n (f) => f.sport.id.toString() === sport && (f.type === FavouriteType.Participant || f.type === FavouriteType.ParticipantV2),\n );\n teams = this.mapToTeamsFromFavouritesResponse(favouriteTeams, team?.toString());\n if (teams.length === 0) {\n return of(undefined);\n }\n }\n\n return this.getTeamPageViewModel(teams, route, isNavigationViewModel);\n }\n\n return this.api.getTeamsByCompetitionId(compoundId || competition, Number(sport)).pipe(\n switchMap((teams) => {\n if (teams.length === 0) {\n return of(undefined);\n }\n\n return this.getTeamPageViewModel(teams, route, isNavigationViewModel);\n }),\n );\n }\n\n set seeMoreCompetition(comp: TeamPageCompetition) {\n this.m2LocalStore.set(this.seeMoreStorageKey, comp);\n }\n\n getTeamNavigationViewModel(route: ActivatedRouteSnapshot): Observable {\n return this.getModelByCompetition(route, true).pipe(\n map((model) => {\n if (!model) {\n return undefined;\n } else {\n return model.teamPagesNavigationViewModel;\n }\n }),\n );\n }\n\n getParticipantImageInfo(sportId: number, favouritesList: Team[]): Observable[] | undefined> {\n return this.cdsMasterApi.getImagesInfo(\n sportId,\n favouritesList?.map((x) => x.id),\n );\n }\n\n getCompetitionName(sportId: number, competitionId: string): string {\n const siteCoreSportDetails = this.teamPagesData.teamPageItems.sportDetails;\n const competitionsForSport = siteCoreSportDetails.find((sport) => sport.sportId === sportId);\n const competition = competitionsForSport?.competitionDetails.find((c) => c.competitionId === competitionId);\n\n return competition?.competitionName ?? '';\n }\n\n isFavouriteTeam(teamId: number): boolean {\n return this.favouritesService.favouritesList.some((fav) => fav.itemId === teamId.toString());\n }\n\n getImageType(\n sportId: number,\n paarticipantId: number,\n offersource?: OfferSource | FavouriteType,\n imageProfile?: EventParticipantImage,\n ): EventParticipantImage {\n if (!this.teamPagesConfig.imageProfileEnabledSportIds.includes(sportId)) {\n if (this.teamPagesConfig.sportsToDisplayLogos.includes(sportId)) {\n return offersource === OfferSource.V2 || offersource === FavouriteType.ParticipantV2\n ? {\n jersey: this.getImage(paarticipantId, this.v2Prefix, this.homeTeamSuffix),\n logo: this.getImage(paarticipantId, this.v2Prefix, this.logoSuffix),\n isParticipantProfile: false,\n }\n : {\n jersey: this.getImage(paarticipantId, this.v1Prefix, this.homeTeamSuffix),\n logo: this.getImage(paarticipantId, this.v1Prefix, this.logoSuffix),\n isParticipantProfile: false,\n };\n } else {\n return offersource === OfferSource.V2 || offersource === FavouriteType.ParticipantV2\n ? { jersey: this.getImage(paarticipantId, this.v2Prefix, this.homeTeamSuffix), isParticipantProfile: false }\n : { jersey: this.getImage(paarticipantId, this.v1Prefix, this.homeTeamSuffix), isParticipantProfile: false };\n }\n } else {\n return { jersey: imageProfile?.jersey, logo: imageProfile?.logo, isParticipantProfile: imageProfile?.isParticipantProfile ?? true };\n }\n }\n\n getImage(paarticipantId: number, prefix: string, sugffix: string): string {\n return prefix + paarticipantId + sugffix;\n }\n\n getBackgroundColor(sportId?: number): string {\n if (!sportId) {\n return '';\n }\n\n if (!this.backgroundColor) {\n this.backgroundColor = Object.assign(\n {},\n ...this.teamPagesData.backgroundImages.backgrounds.map((img) => ({ [img.sportId]: img.innerColor })),\n );\n }\n\n return this.backgroundColor[sportId];\n }\n\n getBackgroundImageUrl(sportId?: number): string {\n if (!sportId) {\n return '';\n }\n\n if (!this.backgroundImage) {\n this.backgroundImage = Object.assign({}, ...this.teamPagesData.backgroundImages.backgrounds.map((img) => ({ [img.sportId]: img.src })));\n }\n\n return this.backgroundImage[sportId];\n }\n\n getPopularTeams(sportId?: number): ParticipantDetail[] {\n return this.teamPagesData.teamPageItems.popularTeamsDetails?.filter((team) => team.sportId === sportId) || [];\n }\n\n mapTeamData(team: {\n id: number;\n name: string;\n sport: FavouritesSport;\n competitionId?: string;\n isSelected?: boolean;\n offerSource?: OfferSource | number;\n favType?: FavouriteType;\n imageProfile?: EventParticipantImage;\n }): Team {\n let offerSource: OfferSource | undefined;\n\n if (isNumber(team.offerSource)) {\n offerSource = team.offerSource === 1 ? OfferSource.V1 : OfferSource.V2;\n } else {\n offerSource = team.offerSource;\n }\n\n return {\n participantImage: this.getImageType(team.sport.id, team.id, offerSource, team.imageProfile),\n sportIcon: 'sports-' + team.sport.id,\n name: team.name,\n id: team.id,\n competitionId: team.competitionId,\n sport: team.sport,\n isSelected: team.isSelected || false,\n favouriteParticipant: {\n id: team.id,\n name: team.name,\n image: team.imageProfile,\n },\n offerSource,\n isFavourited: this.isFavouriteTeam(team.id) || false,\n teamType: team.favType,\n };\n }\n\n getParticipantImage(image: string, logo: string): EventParticipantImage {\n return {\n jersey: image,\n logo,\n isParticipantProfile: false,\n };\n }\n\n getFavouriteTeams(sportId: number): FavouritesViewModel[] {\n if (this.userService.isAuthenticated) {\n const favouritesList = this.favouritesService.favouritesList.filter(\n (team) => team.sport.id === sportId && (team.type === FavouriteType.Participant || team.type === FavouriteType.ParticipantV2),\n );\n\n // show latest favourited item\n return favouritesList.reverse();\n }\n\n return [];\n }\n\n getAllCompetitions(sportId: number): Observable {\n return this.teamPagesData.whenReady.pipe(\n first(),\n switchMap(() => {\n const siteCoreSportDetails = this.teamPagesData.teamPageItems.sportDetails;\n const competitionsForSport = siteCoreSportDetails?.find((sport) => sport.sportId === sportId)?.competitionDetails;\n if (competitionsForSport?.length) {\n const competitions = competitionsForSport.map((comp) => ({\n competitionId: comp.competitionId,\n competitionName: comp.competitionName,\n }));\n\n return this.api.getCompetitionsAndTeamsCount(competitions, sportId);\n }\n\n return of([]);\n }),\n );\n }\n\n private getBlackListCompetitions(): NumberDictionary {\n return toDictionary(\n this.teamPagesConfig.blacklistedCompetitions,\n (id) => Number(id.split(':')[1]),\n (_, index) => index,\n );\n }\n\n private mapToTeamsFromFavouritesResponse(favouriteTeams: FavouritesViewModel[], selectedTeam?: string): Team[] {\n if (!favouriteTeams || favouriteTeams.length === 0) {\n return [];\n }\n const teams: Team[] = [];\n favouriteTeams.map((favTeam) =>\n teams.push({\n id: Number(favTeam.itemId),\n isFavourited: true,\n name: favTeam.name,\n isSelected: favTeam.itemId === selectedTeam,\n sport: favTeam.sport,\n participantImage: this.getImageType(favTeam.sport.id, Number(favTeam.itemId), favTeam.type, favTeam.participantImage),\n offerSource: favTeam.type === FavouriteType.Participant ? OfferSource.V1 : OfferSource.V2,\n teamType: favTeam.type,\n favouriteParticipant: {\n id: Number(favTeam.itemId),\n name: favTeam.name,\n image: favTeam.imageProfile,\n },\n }),\n );\n\n return teams;\n }\n\n private getAllChildParams(route: ActivatedRouteSnapshot): Params {\n let params = { ...route.params };\n let childRoute = route.firstChild;\n while (childRoute) {\n params = Object.assign(params, childRoute.params);\n childRoute = childRoute.parent;\n }\n\n return params;\n }\n\n getTeamPageViewModel(teams: Team[], route: ActivatedRouteSnapshot, isNavigationViewModel: boolean = false): Observable {\n const teamPagesViewModel = {} as TeamPagesViewModel;\n const { sport, sportName, competition, competitionName, team, teamName } = getAllParams(route);\n const routeParams = {\n sport: Number(sport),\n sportName,\n competition,\n competitionName,\n team,\n teamName,\n data: route.data,\n };\n teamPagesViewModel.teamPagesNavigationViewModel = {};\n if (teams.length) {\n teams = teams.map((teamdata) => ({\n ...teamdata,\n isFavourited: this.isFavouriteTeam(teamdata.id),\n teamType: teamdata.teamType,\n offerSource: teamdata.offerSource,\n participantImage: teamdata.participantImage,\n }));\n\n if (routeParams.competition || route.params.league) {\n teams = orderBy(teams, (t) => t.name?.toLowerCase());\n const favTeamIds = this.getFavouriteTeams(Number(sport));\n const favTeamOrder = new Map(favTeamIds.map((val, index) => [val.itemId, index]));\n teams = sortBy(teams, (t) => favTeamOrder.get(t.id.toString()) ?? 9999);\n }\n const selectedTeam = teams.find((teamdata) => teamdata.id === Number(routeParams.team));\n teamPagesViewModel.teamPagesNavigationViewModel.selectedTeam = {\n id: selectedTeam ? selectedTeam.id : this.getAllChildParams(route).team || teams[0].id,\n isFavourited: false,\n isSelected: true,\n name: selectedTeam ? selectedTeam.name : this.getAllChildParams(route).teamName || teams[0].name,\n sport: { id: routeParams.sport, name: routeParams.sportName },\n participantImage: this.getImageType(\n routeParams.sport,\n routeParams.team || 0,\n selectedTeam?.offerSource,\n selectedTeam?.participantImage,\n ),\n offerSource: selectedTeam?.offerSource,\n competitionId: competition,\n };\n }\n if (isNavigationViewModel) {\n const favourites = this.getAllTeams(teams, teamPagesViewModel.teamPagesNavigationViewModel.selectedTeam);\n\n return this.getModel(routeParams, favourites, isNavigationViewModel);\n }\n\n return this.teamPagesCoreGridService.getGridModel(teamPagesViewModel.teamPagesNavigationViewModel.selectedTeam).pipe(\n switchMap((model) =>\n this.teamPagesCoreMarqueeService\n .getFixturesMarquees(teamPagesViewModel.teamPagesNavigationViewModel.selectedTeam, model.fixtureIds)\n .pipe(\n switchMap((fixturemarquees) => {\n const favourites = this.getAllTeams(teams, teamPagesViewModel.teamPagesNavigationViewModel.selectedTeam);\n\n return this.getModel(routeParams, favourites, false, model, fixturemarquees);\n }),\n ),\n ),\n );\n }\n\n private getModel(\n routeParams: { sport: number; sportName: string; competition: string; competitionName: string; team: number; teamName: string; data?: Data },\n teams: Team[],\n isOnlyNavigationViewModel: boolean = false,\n teampagesGridModel?: TeamPagesGridModel,\n fixtureMarquees?: Tile[],\n ): Observable {\n const team = teams.find((t) => t.isSelected);\n const selectedTeam = {\n id: team?.id ?? 0,\n isFavourited: true,\n isSelected: true,\n name: team?.name ?? '',\n sport: { id: routeParams.sport, name: routeParams.sportName },\n participantImage: this.getImageType(routeParams.sport, team?.id || 0, team?.offerSource, team?.participantImage),\n sportIcon: '',\n offerSource: team?.offerSource,\n competitionId: routeParams.competition,\n };\n const favouritesEnabled = this.liveFavouritesService.isLiveFavouriteEnabled() || this.liveFavouritesService.isSwipeFavouriteEnabled();\n const hasFavouritedTeams = teams.some((t) => t.isFavourited);\n\n return this.mapToCompetitionTabs(favouritesEnabled, hasFavouritedTeams, routeParams).pipe(\n map((tabs) => {\n const teamPagesNavigationViewModel = {\n teams,\n competitionTabs: isOnlyNavigationViewModel ? tabs : [],\n currentSport: { id: Number(routeParams.sport), name: routeParams.sportName },\n selectedTeam,\n hasFavouritedTeams,\n popularTeams: this.getPopularTeams(Number(routeParams.sport)),\n };\n if (isOnlyNavigationViewModel) {\n return {\n teamPagesNavigationViewModel,\n teamPagesDetailsModel: {},\n };\n }\n const teamPagesDetailsModel = {\n teampagesGridViewModel: {\n teamPagesGridModel: teampagesGridModel,\n selectedTeam,\n },\n bannerViewModel: {\n backgroundImage: this.getBackgroundImageUrl(routeParams.sport),\n team: selectedTeam,\n },\n marqueeTiles: fixtureMarquees,\n sportCounts: this.getSportsCounts(routeParams?.sport),\n };\n const teamPagesViewModel = {\n teamPagesNavigationViewModel,\n teamPagesDetailsModel,\n };\n\n return teamPagesViewModel;\n }),\n );\n }\n\n private getSportsCounts(sportId: number) {\n let sportsCounts: CountItem | undefined;\n this.competitionListService.getSportTree(sportId).subscribe((result) => {\n sportsCounts = result?.sport;\n });\n\n return sportsCounts;\n }\n\n private mapToCompetitionTabs(\n favouritesEnabled: boolean,\n hasFavouriteTeams: boolean,\n routeParams: { sport: number; sportName: string; competition: string; competitionName: string; team: number; teamName: string; data?: Data },\n ): Observable[]> {\n return this.getAllCompetitions(Number(routeParams.sport)).pipe(\n map((competitions) => {\n if (!competitions || competitions.length === 0) {\n return [];\n }\n const selectedCompetitionId = routeParams.competition || competitions[0].competitionId;\n const tabs = competitions.map((comp) => ({\n id: comp.competitionId,\n title: comp.competitionName,\n active: false,\n }));\n if (favouritesEnabled && this.userService.isAuthenticated) {\n tabs.splice(0, 0, {\n title: this.sitecore.globalMessages.MyTeams,\n active: this.userService.isAuthenticated && !routeParams.competition,\n id: TeamMenuItem.MyTeams,\n });\n }\n if (!tabs[0].active) {\n tabs.forEach((t) => (t.active = t.id === selectedCompetitionId));\n }\n const anyActiveTab = tabs.some((item) => item.active);\n tabs.push({\n title: this.sitecore.globalMessages.SeeMore,\n active: !anyActiveTab,\n id: TeamMenuItem.Showmore,\n });\n\n return tabs;\n }),\n );\n }\n\n private getAllTeams(teams: Team[], selectedTeam: Team): Team[] {\n if (!teams) {\n return [];\n }\n\n return teams.map((team) =>\n this.mapTeamData({\n id: team.id,\n name: team.name,\n sport: { id: selectedTeam.sport!.id, name: selectedTeam.sport!.name },\n competitionId: team.competitionId,\n isSelected: team.id === Number(selectedTeam.id),\n offerSource: team.offerSource,\n favType: team.teamType,\n imageProfile: team.participantImage,\n }),\n );\n }\n}\n","import { Component, HostBinding } from '@angular/core';\n\n@Component({\n selector: 'ms-component-loader',\n template: '
',\n standalone: true,\n})\nexport class ComponentLoaderComponent {\n @HostBinding('class') className = 'component-loader';\n}\n","import { Directive, EventEmitter, Input, OnDestroy, Output, ViewContainerRef } from '@angular/core';\n\nimport { ComponentLoaderComponent } from './component-loader.component';\nimport { ComponentProxy } from './component-loader.service';\n\n@Directive({\n selector: '[msComponentProxy]',\n standalone: true,\n})\nexport class ComponentProxyDirective implements OnDestroy {\n private componentProxy: ComponentProxy | undefined;\n\n @Output() componentLoaded = new EventEmitter();\n\n @Input() set msComponentProxyData(data: any | undefined) {\n if (data && this.componentProxy) {\n this.componentProxy.update(data);\n }\n }\n\n @Input() set msComponentProxy(value: ComponentProxy | undefined) {\n this.destroy();\n\n this.componentProxy = value;\n\n this.viewRef.createComponent(ComponentLoaderComponent);\n\n if (!this.componentProxy) {\n return;\n }\n\n this.componentProxy.create(this.viewRef).subscribe(() => this.componentLoaded.next());\n }\n\n constructor(private viewRef: ViewContainerRef) {}\n\n ngOnDestroy(): void {\n this.destroy();\n }\n\n private destroy(): void {\n this.componentProxy?.destroy();\n this.viewRef.clear();\n }\n}\n","import { Injectable } from '@angular/core';\n\nimport { FavouriteType } from '@frontend/sports/favourites/core/feature';\nimport { NOT_APPLICABLE, TrackingService, trackingConstants } from '@frontend/sports/tracking/feature';\n\nimport { FavouriteToggleTracking } from './favourite-toggle.component';\n\n@Injectable({\n providedIn: 'root',\n})\nexport class FavouriteToggleTrackingService {\n favouriteTypeMap = {\n [FavouriteType.League]: 'League',\n [FavouriteType.Fixture]: 'Event',\n [FavouriteType.FixtureV2]: 'Event',\n [FavouriteType.Participant]: 'Player/Team',\n [FavouriteType.ParticipantV2]: 'Player/Team',\n };\n\n constructor(private trackingService: TrackingService) {}\n\n trackToggle(isToggled: boolean, favouriteType: FavouriteType, tracking: FavouriteToggleTracking): void {\n const baseTracking = {\n [trackingConstants.COMPONENT_ACTION_EVENT]: `${isToggled ? 'Add' : 'Remove'} - Favourites`,\n [trackingConstants.COMPONENT_POSITION_EVENT]: tracking.source ?? NOT_APPLICABLE,\n [trackingConstants.COMPONENT_LOCATION_EVENT]: NOT_APPLICABLE,\n [trackingConstants.COMPONENT_EVENT_DETAILS]: tracking.name ?? NOT_APPLICABLE,\n [trackingConstants.COMPONENT_CONTENT_POSITION]: tracking.position?.toString() ?? NOT_APPLICABLE,\n };\n\n const commonTracking = {\n [trackingConstants.COMPONENT_CATEGORY_EVENT]: 'Sports Favourites',\n [trackingConstants.COMPONENT_URL_CLICKED]: NOT_APPLICABLE,\n [trackingConstants.COMPONENT_LABEL_EVENT]: this.favouriteTypeMap[favouriteType] ?? NOT_APPLICABLE,\n };\n\n this.trackingService.track(trackingConstants.EVENT_FAVOURITES, {\n ...commonTracking,\n ...baseTracking,\n ...tracking?.additional,\n });\n\n this.trackingService.track(trackingConstants.EVENT_CONTENT_VIEW, {\n ...commonTracking,\n [trackingConstants.COMPONENT_ACTION_EVENT]: 'load',\n [trackingConstants.COMPONENT_POSITION_EVENT]: 'toaster',\n [trackingConstants.COMPONENT_LOCATION_EVENT]: `item ${isToggled ? 'added' : 'removed'}`,\n [trackingConstants.COMPONENT_EVENT_DETAILS]: 'favourites toaster',\n });\n }\n}\n","import { ChangeDetectionStrategy, ChangeDetectorRef, Component, EventEmitter, HostBinding, Input, OnChanges, OnDestroy, Output } from '@angular/core';\n\nimport { EventParticipantImage } from '@frontend/sports/betting-offer/feature/model';\nimport { Sitecore } from '@frontend/sports/common/client-config-data-access';\nimport { isDefined } from '@frontend/sports/common/core/utils/extended-types';\nimport { ISimpleChanges } from '@frontend/sports/common/core/utils/simple-change';\nimport { FavouriteType, FavouritesService, FavouritesViewModel } from '@frontend/sports/favourites/core/feature';\nimport { TrackData } from '@frontend/sports/tracking/feature';\nimport { UserService } from '@frontend/sports/user/feature';\nimport { Observable, Subscription, of } from 'rxjs';\nimport { catchError, filter, map } from 'rxjs/operators';\n\nimport { EpcotConfigService, EpcotModule } from '../../common/epcot-config.service';\nimport { RedirectHelperService } from '../../navigation-core/redirect-helper.service';\nimport { FavouriteToggleTrackingService } from './favourite-toggle-tracking.service';\n\nexport interface FavouriteToggleTracking {\n name: string;\n source: string;\n position?: number;\n additional?: Partial;\n}\n\n@Component({\n selector: 'ms-favourite-toggle',\n templateUrl: 'favourite-toggle.html',\n changeDetection: ChangeDetectionStrategy.OnPush,\n standalone: true,\n})\nexport class FavouriteToggleComponent implements OnChanges, OnDestroy {\n @Input() id: string;\n @Input() name: string;\n @Input() sport: number;\n @Input() type: FavouriteType;\n @Input() static: boolean;\n @Input() tracking: FavouriteToggleTracking;\n @Input() participantImage?: EventParticipantImage;\n @Output() toggle = new EventEmitter();\n @HostBinding('class') className = 'favourite-toggle';\n\n model?: FavouritesViewModel;\n disabled: boolean;\n toggleClass: boolean;\n flag = false;\n private isEpcotEnabled: boolean;\n private subscription?: Subscription;\n\n constructor(\n private userService: UserService,\n private favouritesService: FavouritesService,\n private redirectHelper: RedirectHelperService,\n private changeRef: ChangeDetectorRef,\n private trackingService: FavouriteToggleTrackingService,\n public sitecore: Sitecore,\n private epcotConfigService: EpcotConfigService,\n ) {\n this.isEpcotEnabled = this.epcotConfigService.isEnabled(EpcotModule.Favorites);\n }\n\n setToggle(event: MouseEvent): void {\n event.preventDefault();\n event.stopPropagation();\n this.flag = true;\n if (!this.userService.isAuthenticated) {\n this.redirectHelper.goToLogin();\n } else {\n if (this.model) {\n this.remove();\n } else {\n this.add();\n }\n }\n }\n\n ngOnChanges(changes: ISimpleChanges): void {\n if (changes.static) {\n if (this.static) {\n this.unsubscribe();\n } else {\n this.subscribe();\n }\n }\n\n this.setState();\n }\n\n ngOnDestroy(): void {\n this.unsubscribe();\n }\n\n private unsubscribe(): void {\n if (this.subscription) {\n this.subscription.unsubscribe();\n this.subscription = undefined;\n }\n }\n\n private subscribe(): void {\n if (!this.subscription) {\n this.subscription = this.favouritesService.change.subscribe(() => this.setState());\n }\n }\n\n private remove(): void {\n this.toggleClass = false;\n this.favouritesService\n .remove(this.model!)\n .pipe(this.handleError())\n .subscribe(() => this.toggled(this.sitecore.favouritesMessages.RemoveSuccess));\n }\n\n private add(): void {\n this.toggleClass = true;\n this.favouritesService\n .add(this.id, this.sport, this.type, this.name, this.participantImage)\n .pipe(this.handleError())\n .subscribe(() => this.toggled(this.sitecore.favouritesMessages.AddSuccess, false, this.isEpcotEnabled ? '' : 'theme-success-i'));\n }\n\n private handleError = () => (source: Observable) =>\n source.pipe(\n map(() => true),\n catchError((error: Error) => {\n this.toggleClass = !!this.model;\n this.toggled(error.message, true);\n return of(false);\n }),\n filter((result) => result),\n );\n\n private toggled(message: string, toggleError: boolean = false, icon?: string): void {\n this.favouritesService.notify(message, icon);\n this.setState();\n\n const isToggled = isDefined(this.model);\n this.toggle.emit(isToggled);\n this.changeRef.markForCheck();\n if (!toggleError) {\n this.trackingService.trackToggle(isToggled, this.type, this.tracking);\n }\n }\n\n private setState(): void {\n this.disabled = !this.userService.isAuthenticated;\n this.model = this.favouritesService.get(this.id, this.sport, this.type);\n if (!this.flag) {\n this.toggleClass = !!this.model;\n }\n\n this.changeRef.markForCheck();\n }\n}\n","\n","import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core';\n\nimport { FavouriteType } from '@frontend/sports/favourites/core/feature';\nimport { TrackingSource, trackingConstants } from '@frontend/sports/tracking/feature';\n\nimport { FavouriteToggleComponent, FavouriteToggleTracking } from './favourite-toggle.component';\n\n@Component({\n selector: 'ms-favourite-league-toggle',\n template: `\n \n `,\n changeDetection: ChangeDetectionStrategy.OnPush,\n imports: [FavouriteToggleComponent],\n standalone: true,\n})\nexport class FavouriteLeagueToggleComponent {\n @Input() id: string | number;\n @Input() name: string;\n @Input() sport: number;\n @Input() static: boolean;\n @Input() tracking: TrackingSource;\n @Output() toggle = new EventEmitter();\n\n type = FavouriteType.League;\n\n get favouriteTracking(): FavouriteToggleTracking {\n return {\n name: this.name,\n additional: {\n [trackingConstants.SPORT_ID]: this.sport.toString(),\n [trackingConstants.SPORT_LEAGUE_ID]: this.id.toString(),\n },\n ...this.tracking,\n };\n }\n}\n","import { ChangeDetectionStrategy, Component, EventEmitter, Input, OnChanges, Output } from '@angular/core';\n\nimport { OfferSource } from '@cds';\nimport { EventModel } from '@frontend/sports/betting-offer/feature/model';\nimport { ISimpleChanges } from '@frontend/sports/common/core/utils/simple-change';\nimport { FavouriteType } from '@frontend/sports/favourites/core/feature';\nimport { TrackingSource, trackingConstants } from '@frontend/sports/tracking/feature';\n\nimport { FavouriteToggleComponent, FavouriteToggleTracking } from './favourite-toggle.component';\n\n@Component({\n selector: 'ms-favourite-event-toggle',\n template: `\n \n `,\n changeDetection: ChangeDetectionStrategy.OnPush,\n imports: [FavouriteToggleComponent],\n standalone: true,\n})\nexport class FavouriteEventToggleComponent implements OnChanges {\n @Input() event: EventModel;\n @Input() static: boolean;\n @Input() tracking: TrackingSource;\n @Output() toggle = new EventEmitter();\n\n type: FavouriteType;\n\n ngOnChanges(changes: ISimpleChanges): void {\n if (changes.event) {\n const previous = changes.event.previousValue ? changes.event.previousValue.id : undefined;\n const current = changes.event.currentValue ? changes.event.currentValue.id : undefined;\n\n if (previous !== current) {\n this.type = this.getType();\n }\n }\n }\n\n get favouriteTracking(): FavouriteToggleTracking {\n return {\n name: this.event.name,\n additional: {\n [trackingConstants.SPORT_ID]: this.event.sport.id.toString(),\n [trackingConstants.SPORT_EVENT_ID]: this.event.id.toString(),\n },\n ...this.tracking,\n };\n }\n\n private getType(): FavouriteType {\n if (!this.event.offerSource || this.event.offerSource === OfferSource.V1) {\n return FavouriteType.Fixture;\n }\n\n return FavouriteType.FixtureV2;\n }\n}\n","import { ChangeDetectionStrategy, Component, EventEmitter, Input, OnChanges, Output } from '@angular/core';\n\nimport { OfferSource } from '@cds';\nimport { EventParticipant, EventParticipantImage } from '@frontend/sports/betting-offer/feature/model';\nimport { ISimpleChanges } from '@frontend/sports/common/core/utils/simple-change';\nimport { FavouriteType } from '@frontend/sports/favourites/core/feature';\nimport { TrackingSource, trackingConstants } from '@frontend/sports/tracking/feature';\n\nimport { FavouriteToggleComponent, FavouriteToggleTracking } from './favourite-toggle.component';\n\n@Component({\n selector: 'ms-favourite-participant-toggle',\n template: `\n \n `,\n changeDetection: ChangeDetectionStrategy.OnPush,\n standalone: true,\n imports: [FavouriteToggleComponent],\n})\nexport class FavouriteParticipantToggleComponent implements OnChanges {\n @Input() participant: EventParticipant;\n @Input() sport: number;\n @Input() source: OfferSource;\n @Input() static: boolean;\n @Input() tracking: TrackingSource;\n @Input() participantImage?: EventParticipantImage;\n @Output() toggle = new EventEmitter();\n\n name: string;\n type: FavouriteType;\n\n get favouriteTracking(): FavouriteToggleTracking {\n return {\n name: this.getName(),\n additional: {\n [trackingConstants.SPORT_ID]: this.sport.toString(),\n },\n ...this.tracking,\n };\n }\n\n ngOnChanges(changes: ISimpleChanges): void {\n if (changes.participant) {\n const previous = changes.participant.previousValue ? changes.participant.previousValue.id : undefined;\n const current = changes.participant.currentValue ? changes.participant.currentValue.id : undefined;\n\n if (previous !== current) {\n this.name = this.getName();\n }\n }\n\n if (changes.source) {\n this.type = this.getType();\n }\n }\n\n private getType(): FavouriteType {\n if (!this.source || this.source === OfferSource.V1) {\n return FavouriteType.Participant;\n }\n\n return FavouriteType.ParticipantV2;\n }\n\n private getName(): string {\n return this.participant.country ? `${this.participant.name} (${this.participant.country})` : this.participant.name;\n }\n}\n","import { NgModule } from '@angular/core';\n\nimport { FavouriteEventToggleComponent } from './toggle/favourite-event-toggle.component';\nimport { FavouriteLeagueToggleComponent } from './toggle/favourite-league-toggle.component';\nimport { FavouriteParticipantToggleComponent } from './toggle/favourite-participant-toggle.component';\n\n@NgModule({\n imports: [FavouriteEventToggleComponent, FavouriteLeagueToggleComponent, FavouriteParticipantToggleComponent],\n declarations: [],\n exports: [FavouriteEventToggleComponent, FavouriteLeagueToggleComponent, FavouriteParticipantToggleComponent],\n})\nexport class FavouritesModule {}\n","import { InjectionToken, Type } from '@angular/core';\n\nimport { Widget, WidgetType } from '@frontend/sports/types/components/widget';\nimport { Observable } from 'rxjs';\n\nexport interface WidgetSkeletonModel {\n widget?: Widget;\n showSkeleton$: Observable;\n}\nexport interface WidgetSkeletonConfig {\n visible: boolean;\n loaderClass?: string;\n}\n// so we can inject it\nexport class WidgetSkeletonData {\n widget: Widget;\n widgetsBeforeLoaded$: Observable;\n config: (payload: T) => Observable;\n}\nexport interface WidgetSkeletonDefinition {\n config: (payload: T) => Observable;\n type: WidgetType;\n component: Type;\n}\n\nexport const WIDGET_SKELETON = new InjectionToken>('ms-widget-skeleton');\n","import { Component, DestroyRef, Injectable, Injector, Type, effect, inject, signal } from '@angular/core';\nimport { takeUntilDestroyed } from '@angular/core/rxjs-interop';\n\nimport { WidgetConfig } from '@frontend/sports/common/client-config-data-access';\nimport { LoggerFactory } from '@frontend/sports/common/core/feature/logging';\nimport { toDictionary } from '@frontend/sports/common/core/utils/collection';\nimport { bufferWithSize } from '@frontend/sports/common/core/utils/rxjs';\nimport { Widget, WidgetContext, WidgetLocation, WidgetType } from '@frontend/sports/types/components/widget';\nimport { NativeAppService } from '@frontend/vanilla/core';\nimport { omit } from 'lodash-es';\nimport { Subject, Subscription, combineLatest, iif, of } from 'rxjs';\nimport { debounceTime, filter, map, take, timeout } from 'rxjs/operators';\n\nimport { WidgetData, WidgetLoaderService } from './widget-loader.service';\nimport { WIDGET_SKELETON, WidgetSkeletonDefinition } from './widget-skeleton.model';\n\nexport interface RegisteredWidget {\n componentType: Type>;\n injector?: Injector;\n}\n\n@Component({\n template: '',\n host: {\n '[class.hidden]': '_hidden()',\n },\n})\nexport abstract class WidgetComponent {\n get hidden() {\n return this._hidden();\n }\n\n set hidden(hidden) {\n this._hidden.set(hidden);\n }\n\n _hidden = signal(!inject(NativeAppService).isTerminal);\n hasData = true;\n private widgetAlreadyReady: boolean;\n private widgetLoaderService = inject(WidgetLoaderService);\n private widgetConfig = signal | undefined>(undefined);\n private parentConfig?: Widget;\n private widgetLoadedSubscription$: Subscription;\n private logger = inject(LoggerFactory).getLogger('WidgetComponent');\n private widgetClientConfig = inject(WidgetConfig);\n\n get parent(): Readonly> | undefined {\n return this.parentConfig;\n }\n\n get config(): Readonly> {\n return this.widgetConfig()!;\n }\n\n protected logEmptyData(): void {\n this.logger.warning(`Empty widget data configured for ${this.config.id}`);\n }\n\n protected readonly destroyRef = inject(DestroyRef);\n\n constructor() {\n effect(() => {\n const config = this.widgetConfig();\n const visible = !this._hidden();\n if (config) {\n this.widgetLoaderService.setWidgetVisible(`${this.config.location}-${this.config.id}`, visible);\n }\n });\n }\n\n onResolve(widget: Widget, parent?: Widget, shouldCallOnData: boolean = true, isWidgetRefresh: boolean = false): void {\n this.widgetConfig.set(omit(widget, 'payload') as Widget);\n\n if (parent) {\n this.parentConfig = omit(parent, 'payload') as Widget;\n }\n\n if (shouldCallOnData) {\n if (this.config.location !== WidgetLocation.Right && !isWidgetRefresh) {\n this.widgetAlreadyReady = false;\n this.subscribeWidgetLoaded();\n }\n\n this.onData?.(widget.payload, isWidgetRefresh);\n if (parent?.payload) {\n this.onParentData?.(parent.payload);\n }\n }\n }\n\n protected onData?(data?: T, isWidgetRefresh?: boolean): void;\n\n protected onParentData?(data: unknown): void;\n\n getContext(): Partial {\n return { widgetId: this.config.id };\n }\n\n protected setWidgetLoaded(): void {\n if (this.widgetAlreadyReady) {\n this.hidden = !this.hasData;\n\n return;\n }\n\n //Adding below check to avoid null config from widget constructor on initial load\n if (this.config) {\n this.widgetLoaderService.setwidgetLoaded(`${this.config.location}-${this.config.id}`);\n }\n }\n\n private subscribeWidgetLoaded(): void {\n this.widgetLoadedSubscription$?.unsubscribe();\n this.widgetLoadedSubscription$ = combineLatest([\n this.widgetLoaderService.widgetLoadedDetails$(this.config.location),\n iif(\n () => [WidgetLocation.Left, WidgetLocation.Center].includes(this.config.location),\n this.widgetLoaderService.allTopLocationWidgetsLoaded$,\n of(true),\n ),\n ])\n .pipe(\n map(([widgetsData, topLocationWidgetsLoaded]) => topLocationWidgetsLoaded && this.checkWidgetReady(widgetsData)),\n filter(Boolean),\n take(1),\n timeout({\n each: this.widgetClientConfig.orderedRenderingTimeout,\n with: () => of(false),\n }),\n takeUntilDestroyed(this.destroyRef),\n )\n .subscribe((loadedWithoutTimeout) => {\n if (!loadedWithoutTimeout) {\n this.logger.warning(\n `Widget ordered rendering timed out after ${this.widgetClientConfig.orderedRenderingTimeout}ms for ${this.config.id}`,\n );\n }\n this.widgetAlreadyReady = true;\n this.hidden = !this.hasData;\n });\n }\n\n private checkWidgetReady = (widgets: WidgetData[]): boolean => {\n return widgets.findIndex((x) => x.widgetId === this.config.id && x.isLoaded) > -1 && this.checkPreceedingWidgetsReady(widgets);\n };\n\n private checkPreceedingWidgetsReady = (widgets: WidgetData[]): boolean => {\n const currentWidgetOrder = widgets.find((widget) => widget.widgetId === this.config.id)!.order;\n const filteredWidgets = widgets.filter((x) => x.order <= currentWidgetOrder);\n\n return filteredWidgets.every((x) => x.isLoaded);\n };\n}\n\n@Injectable({\n providedIn: 'root',\n})\nexport class WidgetRegistryService {\n // inject all candidates that got registered\n private readonly widgetSkeletonConfig = inject[]>(WIDGET_SKELETON, { optional: true });\n // create a dictionary for faster access\n readonly widgetSkeletons = this.widgetSkeletonConfig\n ? toDictionary(\n this.widgetSkeletonConfig,\n ({ type }) => type.toString(),\n (v) => v,\n )\n : {};\n\n private readonly widgets = new Map();\n private readonly lazyWidgets = new Subject();\n\n private readonly debounceTimer = this.lazyWidgets.pipe(debounceTime(2));\n readonly lazyWidgets$ = this.lazyWidgets.pipe(\n bufferWithSize(10, this.debounceTimer),\n filter((data) => data.length > 0),\n );\n\n register(name: WidgetType, widget: Type>): void {\n this.assertNotAlreadyRegistered(name);\n this.widgets.set(name, { componentType: widget });\n }\n\n registerLazy(name: WidgetType, widget: Type>, injector?: Injector): void {\n this.assertNotAlreadyRegistered(name);\n\n this.widgets.set(name, { componentType: widget, injector });\n this.lazyWidgets.next(name);\n }\n\n get(name: WidgetType): RegisteredWidget | null {\n return this.widgets.get(name) ?? null;\n }\n\n private assertNotAlreadyRegistered(type: WidgetType): void {\n if (this.widgets.has(type)) {\n throw new Error(`Widget with name ${type} is already registered`);\n }\n }\n}\n","@if (fallbackUrl && !showParticipant) {\n \n}\n@if (participantUrl) {\n \n}\n","import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, OnInit } from '@angular/core';\n\nimport { ImagesConfig } from '@frontend/sports/common/client-config-data-access';\nimport { VirtualCompetitionImageType } from '@frontend/sports/common/core/data-access/sport-model';\nimport { ImageRootPath } from '@frontend/sports/participant-image/feature';\nimport { UtilsService } from '@frontend/vanilla/core';\n\nimport { EpcotConfigService } from '../../common/epcot-config.service';\n\n@Component({\n selector: 'ms-tournament-groups-participants',\n templateUrl: 'tournament-groups-participants.html',\n changeDetection: ChangeDetectionStrategy.OnPush,\n standalone: true,\n})\nexport class TournamentGroupsParticipantsComponent implements OnInit {\n constructor(\n private imagesConfig: ImagesConfig,\n private utilsService: UtilsService,\n private changeRef: ChangeDetectorRef,\n private epcotConfigService: EpcotConfigService,\n ) {\n this.isEpcotEnabled = this.epcotConfigService.isEnabled();\n }\n\n private static FALLBACK_IMAGE = 'fallback';\n private static FALLBACK_FLAGS_IMAGE = 'fallback_flag';\n private static FALLBACK_IMAGE_EPCOT = 'epcot_fallback';\n\n @Input() participantId: string;\n @Input() sportId: number;\n @Input() imageType: VirtualCompetitionImageType;\n\n fallbackUrl: string;\n participantUrl: string;\n showParticipant = false;\n isEpcotEnabled = false;\n\n ngOnInit(): void {\n const participantTemplateUrl = this.imagesConfig.participantImageUrl;\n\n let fallbackImage = this.isEpcotEnabled\n ? TournamentGroupsParticipantsComponent.FALLBACK_IMAGE_EPCOT\n : TournamentGroupsParticipantsComponent.FALLBACK_IMAGE;\n // when we have default imageType show home jursey\n let participantId = this.participantId + '_1';\n\n if (this.imageType === VirtualCompetitionImageType.Flags) {\n fallbackImage = this.isEpcotEnabled\n ? TournamentGroupsParticipantsComponent.FALLBACK_IMAGE_EPCOT\n : TournamentGroupsParticipantsComponent.FALLBACK_FLAGS_IMAGE;\n participantId = this.participantId;\n }\n\n this.participantUrl = this.utilsService.format(participantTemplateUrl, ImageRootPath.Live, this.sportId, participantId);\n this.fallbackUrl = this.utilsService.format(participantTemplateUrl, ImageRootPath.Live, this.sportId, fallbackImage);\n }\n\n participantImageLoad(): void {\n this.showParticipant = true;\n this.changeRef.markForCheck();\n }\n}\n","\n \n\n\n\n
\n
\n {{ group.title }}\n
\n
\n
\n\n\n
\n
\n {{ group.title }}\n
\n
0\" class=\"cards-participants-container\">\n
\n
\n \n \n
\n
{{ participant.code }}
\n
\n
\n
\n
\n","import { CommonModule } from '@angular/common';\nimport { ChangeDetectionStrategy, Component, HostBinding, Input, OnChanges, OnInit, ViewChild } from '@angular/core';\n\nimport { Sitecore } from '@frontend/sports/common/client-config-data-access';\nimport { VirtualCompetitionDisplayMode, VirtualCompetitionGroupParticipant } from '@frontend/sports/common/core/data-access/sport-model';\nimport { ScrollAdapterComponent } from '@frontend/sports/common/core/feature/scroll-adapter';\nimport { ISimpleChanges } from '@frontend/sports/common/core/utils/simple-change';\nimport { TimerService } from '@frontend/sports/common/core/utils/timer';\nimport { NOT_APPLICABLE, TrackingService, trackingConstants } from '@frontend/sports/tracking/feature';\n\nimport { ScrollSelectedInViewDirective } from '../../directives/scroll-selected-in-view/scroll-selected-in-view.directive';\nimport { FavouritesModule } from '../../favourites/favourites.module';\nimport { RedirectHelperService } from '../../navigation-core/redirect-helper.service';\nimport { TournamentGroupsParticipantsComponent } from './tournament-groups-participants.component';\nimport { VirtualCompetitionModel } from './virtual-competition.service';\n\nexport interface TournamentGroup {\n id: number;\n favouriteId: number;\n title: string;\n active: boolean;\n url?: string;\n isFavourite: boolean;\n canBeFavourited?: boolean;\n favouriteName: string;\n participants: VirtualCompetitionGroupParticipant[];\n}\n\n@Component({\n selector: 'ms-tournament-groups',\n templateUrl: 'tournament-groups.html',\n changeDetection: ChangeDetectionStrategy.OnPush,\n standalone: true,\n imports: [CommonModule, ScrollAdapterComponent, ScrollSelectedInViewDirective, TournamentGroupsParticipantsComponent, FavouritesModule],\n})\nexport class TournamentGroupsComponent implements OnInit, OnChanges {\n @Input() model: VirtualCompetitionModel;\n\n @HostBinding('class') className = 'tournament-groups';\n @ViewChild(ScrollAdapterComponent, { static: true }) scrollAdapter: ScrollAdapterComponent;\n active: TournamentGroup | undefined;\n VirtualCompetitionDisplayMode = VirtualCompetitionDisplayMode;\n\n constructor(\n private timer: TimerService,\n public sitecore: Sitecore,\n public redirectHelperService: RedirectHelperService,\n private trackingService: TrackingService,\n ) {}\n\n ngOnInit(): void {\n this.active = this.model.groups && this.model.groups.find((group) => group.active);\n }\n\n ngOnChanges(changes: ISimpleChanges): void {\n this.timer.setTimeout(() => this.scrollAdapter.setArrows());\n }\n\n selected(event: Event, active: TournamentGroup): void {\n event.preventDefault();\n\n if (this.model.groups.find((group) => group.id === active.id)) {\n this.model.groups.forEach((group) => (group.active = group.id === active.id));\n this.active = active;\n\n this.trackingService.track('Event.Clicks', {\n [trackingConstants.COMPONENT_CATEGORY_EVENT]: 'Tournaments Groups',\n [trackingConstants.COMPONENT_LABEL_EVENT]: `Tournaments Group: ${active.title}`,\n [trackingConstants.COMPONENT_ACTION_EVENT]: 'Group - Clicked',\n [trackingConstants.COMPONENT_POSITION_EVENT]: NOT_APPLICABLE,\n [trackingConstants.COMPONENT_LOCATION_EVENT]: 'Tournaments Page',\n [trackingConstants.COMPONENT_EVENT_DETAILS]: 'Visitor Clicks on a Tournaments Group',\n [trackingConstants.COMPONENT_URL_CLICKED]: active.url || NOT_APPLICABLE,\n });\n\n this.redirectHelperService.goToPage(active.url, true);\n }\n }\n\n trackTabsBy(index: number, group: TournamentGroup): number {\n return group.id;\n }\n\n getGroupMenuItemId(item: TournamentGroup | undefined): string {\n const itemIndex = !item ? -1 : this.model.groups.findIndex((group) => group.id === item.id);\n const itemId = !item ? 0 : item.id;\n\n return `${itemId}#${itemIndex}`;\n }\n}\n","
\n {{ tokenModel.badgeText }}\n
\n","import { AsyncPipe, NgClass, NgIf } from '@angular/common';\nimport { ChangeDetectionStrategy, Component, Input } from '@angular/core';\n\nimport { Sitecore } from '@frontend/sports/common/client-config-data-access';\nimport { Store } from '@ngrx/store';\nimport { uniq, values } from 'lodash-es';\nimport { IBetslipRootState } from 'packages/sports/common/betslip/base/store/state';\nimport { BetslipIntegrationService } from 'packages/sports/common/betslip/integration/betslip-integration.service';\nimport { IRewardToken } from 'packages/sports/common/betslip/modules/reward-tokens/reward-tokens.model';\nimport { selectRewardTokens } from 'packages/sports/common/betslip/modules/reward-tokens/selectors';\nimport { getNonAccaTokens, isAccaBoostToken } from 'packages/sports/common/betslip/modules/reward-tokens/services/reward-tokens.utils';\nimport { BehaviorSubject, combineLatest } from 'rxjs';\nimport { concatMap, map } from 'rxjs/operators';\n\nimport { RewardTokenType } from '../../tokens-base/token-base.models';\n\n@Component({\n selector: 'ms-reward-token-eligible-tag',\n templateUrl: 'reward-token-eligible-tag.html',\n changeDetection: ChangeDetectionStrategy.OnPush,\n standalone: true,\n imports: [AsyncPipe, NgIf, NgClass],\n})\nexport class RewardTokenEligibleTagComponent {\n @Input() set leagues(value: number[]) {\n this.leagues$.next(value);\n }\n\n @Input() set sport(value: number) {\n this.sport$.next(value);\n }\n\n private sport$ = new BehaviorSubject(null);\n private leagues$ = new BehaviorSubject([]);\n\n private readonly badgeModels: Record = {\n [RewardTokenType.OddsBoost]: {\n badgeText: this.sitecore.crmTokens.messages.OddsBoost,\n cssClass: 'reward-token-eligible-tag--odds-boost',\n },\n [RewardTokenType.RiskFreeBet]: {\n badgeText: this.sitecore.crmTokens.messages.RiskFree,\n cssClass: 'reward-token-eligible-tag--risk-free',\n },\n [RewardTokenType.FreeBet]: {\n badgeText: this.sitecore.crmTokens.messages.Freebet,\n cssClass: 'reward-token-eligible-tag--freebet',\n },\n [RewardTokenType.AccaBoost]: {\n badgeText: this.sitecore.accaBoost.AccaBoost,\n cssClass: 'reward-token-eligible-tag--acca-boost',\n },\n } as Record;\n\n private readonly betslipInitialized$ = this.betslipIntegrationService.betslipInitialized$();\n private readonly tokens$ = this.store.select(selectRewardTokens).pipe(\n map((state) => {\n const allTokens = values(state);\n const nonAccaBoostTokens = getNonAccaTokens(allTokens);\n\n // if there are more than one acca boost token, ignore them for showing the eligibility tag\n if (allTokens.length - nonAccaBoostTokens.length > 1) {\n return nonAccaBoostTokens;\n }\n\n return allTokens;\n }),\n );\n\n private isEligible(token: IRewardToken, leagueIds: number[], sportId: number | null): boolean {\n if (isAccaBoostToken(token)) {\n const competitions = (sportId && token.filter.sportsConfigs[sportId]?.competitions) || {};\n\n return leagueIds.some((c) => competitions[c]);\n }\n\n return token.filter.competitions.some((c) => leagueIds.includes(c.id));\n }\n\n readonly tokenModel$ = this.betslipInitialized$.pipe(\n concatMap(() => {\n return combineLatest([this.tokens$, this.sport$, this.leagues$]);\n }),\n map(([tokens, sportId, leagueIds]) => {\n const eligibleTokens = tokens.filter(\n (t) => Object.keys(this.badgeModels).includes(t.rewardTokenType) && this.isEligible(t, leagueIds, sportId),\n );\n\n const tokenTypes = uniq(eligibleTokens.map((t) => t.rewardTokenType));\n\n if (tokenTypes.length === 0) {\n return null;\n }\n\n if (tokenTypes.length === 1) {\n return this.badgeModels[tokenTypes[0]];\n }\n\n return { badgeText: this.sitecore.crmTokens.messages.Rewards, cssClass: 'reward-token-eligible-tag--rewards' };\n }),\n );\n\n constructor(\n public sitecore: Sitecore,\n private store: Store,\n private betslipIntegrationService: BetslipIntegrationService,\n ) {}\n}\n","@if (title) {\n
\n @if (showFavoriteToggle && league) {\n \n }\n\n
\n

{{ title }}

\n @if (sport && league) {\n \n }\n
\n \n
\n}\n\n\n@if (tabsVM?.length > 1) {\n @if (showPrimaryNavAsPill) {\n \n } @else {\n \n \n }\n}\n\n @if (sport && league) {\n \n }\n\n","import { NgClass, NgTemplateOutlet } from '@angular/common';\nimport { ChangeDetectionStrategy, Component, HostBinding, Input, OnChanges, SimpleChanges, TemplateRef } from '@angular/core';\nimport { Router } from '@angular/router';\n\nimport { LeagueModel } from '@frontend/sports/betting-offer/feature/model';\nimport { GridConfig, PrettyUrlsConfig, Sitecore } from '@frontend/sports/common/client-config-data-access';\nimport { CountItem, ItemType, LeagueItem, children } from '@frontend/sports/common/core/data-access/sport-model';\nimport { interpolateDoubleBracesString } from '@frontend/sports/common/core/utils/string';\nimport { ThemeNotificationVariant } from '@frontend/sports/common/ui/sports-themed-notifcation-bubble';\nimport { SelectedTabItem, TabGroupWithCounterComponent, TabItemUiViewModel } from '@frontend/sports/common/ui/sports-themed-tab-group';\nimport { CompetitionLogoComponent } from '@frontend/sports/competition-logos/feature';\nimport { TrackingService, trackingConstants } from '@frontend/sports/tracking/feature';\nimport { PrimaryNavigationDisplayType } from '@frontend/sports/types/configuration';\nimport { lowerCase } from 'lodash-es';\n\nimport { CalendarService } from '../../calendar/calendar.service';\nimport { getItemTypeForSport } from '../../competition-list/competition-list.models';\nimport { CompetitionRouteService } from '../../competition-list/competition-route.service';\nimport { FavouriteLeagueToggleComponent } from '../../favourites/toggle/favourite-league-toggle.component';\nimport { TabBarItem } from '../../tab-bar/tab-bar.models';\nimport { WidgetPillComponent } from '../../widget/common/widget-pill.component';\nimport { WidgetTabBarDisplay, WidgetTabBarItem } from '../../widget/common/widget-tab-bar.component';\nimport { FixtureTab } from './competitions/competition.models';\nimport { RewardTokenEligibleTagComponent } from './reward-token-eligible-tag.component';\n\n@Component({\n selector: 'ms-fixture-list-header',\n templateUrl: 'fixture-list-header.html',\n standalone: true,\n imports: [\n NgTemplateOutlet,\n FavouriteLeagueToggleComponent,\n CompetitionLogoComponent,\n RewardTokenEligibleTagComponent,\n NgClass,\n TabGroupWithCounterComponent,\n WidgetPillComponent,\n ],\n changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class FixtureListHeaderComponent implements OnChanges {\n @Input() sport?: CountItem;\n @Input() navigation?: TabBarItem[];\n @Input() showFavoriteToggle? = true;\n @Input() competitionTitle: string;\n @Input() headerTitle = '';\n @Input() offerBadgeTemplate?: TemplateRef;\n\n @HostBinding('class') className = 'fixture-list-header';\n tabsVM: TabItemUiViewModel[] | undefined;\n pills: WidgetTabBarItem[] | undefined;\n @HostBinding('class.active') get active(): boolean {\n return !!this.title || (!!this.navigation && this.navigation.length > 0);\n }\n\n @HostBinding('class.calendar') isCalendarPage = false;\n\n title?: string;\n league?: LeagueModel;\n region?: CountItem;\n regionType: ItemType.Region | ItemType.Tournament;\n variant: ThemeNotificationVariant = {\n 'mgm-4': 'utility',\n };\n\n showPrimaryNavAsPill = false;\n\n constructor(\n public sitecore: Sitecore,\n private gridConfig: GridConfig,\n private route: CompetitionRouteService,\n private router: Router,\n private urlConfig: PrettyUrlsConfig,\n private calendar: CalendarService,\n private trackingService: TrackingService,\n ) {\n this.showPrimaryNavAsPill = this.gridConfig.primaryNavigationType === PrimaryNavigationDisplayType.Pill;\n }\n\n ngOnChanges(changes: SimpleChanges): void {\n if (changes.sport) {\n this.league = undefined;\n this.title = undefined;\n\n const route = this.route.params();\n const interval = this.calendar.getInterval(route.context);\n\n if (\n (route.context !== this.urlConfig.translations.betting &&\n route.context !== this.urlConfig.translations.standings &&\n route.context !== this.urlConfig.translations.bets &&\n route.context !== this.urlConfig.translations.conferences &&\n !interval) ||\n !changes.sport?.currentValue ||\n !this.sport\n ) {\n return;\n }\n\n if (interval) {\n this.title = this.headerTitle || interval.title;\n this.isCalendarPage = true;\n\n return;\n }\n\n this.isCalendarPage = false;\n\n this.regionType = getItemTypeForSport(this.sport.id);\n this.region = children(this.sport, this.regionType).find((item) => item.id === route.region);\n\n let league: LeagueItem | undefined;\n if (!route.league) {\n if (!this.region) {\n return;\n } else {\n this.title = `${this.sitecore.globalMessages.All} ${this.region.name}`;\n }\n } else {\n league = children(this.sport, ItemType.Competition).pop() as LeagueItem;\n this.league = {\n id: league.realCompetitionId || league.id,\n name: league.name,\n parentLeagueId: league.siblings[0],\n imageProfile: league.imageProfile,\n };\n\n if (route.conference && !this.competitionTitle) {\n const conference = children(this.sport, ItemType.Conference).pop();\n this.title = interpolateDoubleBracesString(this.sitecore.seo.ConferenceHeadingTitle, {\n conference: conference?.name ?? '',\n });\n\n return;\n }\n\n this.title =\n this.competitionTitle ||\n interpolateDoubleBracesString(this.sitecore.seo.LeagueHeadingTitle, {\n league: league.name,\n });\n }\n }\n this.tabsVM = this.navigation?.map((t) => ({\n counter: t.count?.toString(),\n id: t.id.toString(),\n icon: t.icon,\n title: t.title,\n active: t.active,\n url: t.url,\n }));\n this.pills = this.navigation?.map((t) => ({\n id: t.id.toString(),\n icon: t.icon,\n title: t.title,\n active: t.active,\n url: t.url,\n display: WidgetTabBarDisplay.Default,\n context: {},\n itemId: t.id,\n widgetId: '',\n }));\n }\n\n changedPill(selectedTab: WidgetTabBarItem): void {\n const tabItem = this.navigation?.find((t) => t.id.toString() === selectedTab.id);\n this.routeTab(tabItem);\n }\n\n changed(selectedTab: SelectedTabItem): void {\n const tabItem = this.navigation?.find((t) => t.id.toString() === selectedTab.name);\n this.routeTab(tabItem);\n }\n\n routeTab(tabItem: TabBarItem | undefined) {\n if (!tabItem) {\n return;\n }\n const url = this.route.path();\n\n this.router\n .navigate([url], {\n replaceUrl: true,\n queryParams: {\n tab: lowerCase(FixtureTab[tabItem.id]),\n },\n })\n .then(() => {\n const route = this.route.params();\n this.trackingService.track(trackingConstants.EVENT_NAVIGATION_MENUS, {\n [trackingConstants.PAGE_NAVIGATION_MENUS]: `CompetitionPage_${route.marketOffer}_${tabItem.id}`,\n });\n });\n }\n\n get iconClass(): string {\n if (this.region) {\n return !this.isTournament ? `country-icon c${this.region.id}` : 'sports-tournament';\n }\n\n return '';\n }\n\n get isTournament(): boolean {\n return this.regionType === ItemType.Tournament;\n }\n}\n","\n \n \n \n\n","import { ChangeDetectionStrategy, Component, HostBinding } from '@angular/core';\n\nimport { CountItem } from '@frontend/sports/common/core/data-access/sport-model';\nimport { CompetitionHeaderPayload } from '@frontend/sports/types/components/widget/types';\nimport { Observable, ReplaySubject, of } from 'rxjs';\nimport { map, switchMap, tap } from 'rxjs/operators';\n\nimport { SportsCacheService } from '../../client-caching/sports-cache.service';\nimport { CompetitionRouteService } from '../../competition-list/competition-route.service';\nimport { CompetitionRoute } from '../../navigation/navigation.models';\nimport { WidgetComponent } from '../../widget/core/widget-registry.service';\nimport { PruneHelperService } from './competitions/prune-helper.service';\nimport { VirtualCompetitionModel, VirtualCompetitionService } from './virtual-competition.service';\n\ntype ViewModel = CompetitionHeaderPayload & {\n sport: CountItem;\n isFavouritesEnabled: boolean;\n virtualCompetition: VirtualCompetitionModel | null;\n};\n\n@Component({\n selector: 'ms-competition-header-widget',\n templateUrl: 'competition-header-widget.html',\n changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class CompetitionHeaderWidgetComponent extends WidgetComponent {\n private readonly payload$ = new ReplaySubject(1);\n readonly vm$: Observable;\n\n @HostBinding('class') className = 'competition-header-widget event-list';\n\n constructor(\n private competitionRouteService: CompetitionRouteService,\n private virtualCompetitionService: VirtualCompetitionService,\n private pruneService: PruneHelperService,\n private bettingCache: SportsCacheService,\n ) {\n super();\n\n this.vm$ = this.payload$.pipe(\n switchMap((payload) => {\n const route = this.competitionRouteService.params();\n\n if (!route.sport || !route.league) {\n return of(null);\n }\n\n return this.bettingCache.getSport(route.sport).pipe(switchMap((sport) => this.getModel$(sport, route, payload)));\n }),\n );\n }\n\n private getModel$(sport: CountItem | undefined, route: CompetitionRoute, payload: CompetitionHeaderPayload): Observable {\n const prunnedSportItem = this.pruneService.prune(sport, route);\n const virtualCompetitionItem = this.virtualCompetitionService.getCompetition(sport, route);\n\n const source$ =\n !prunnedSportItem || !virtualCompetitionItem\n ? of(null)\n : this.virtualCompetitionService.subscribe$(prunnedSportItem, virtualCompetitionItem);\n\n return source$.pipe(\n map((virtualCompetitionModel) => {\n return {\n sport: prunnedSportItem!,\n title: payload.title,\n // non-virtual competitions and tv2 virtual competitions with one competition assigned, can have favorites enabled\n isFavouritesEnabled: !virtualCompetitionModel || !!virtualCompetitionModel.realCompetitionId,\n virtualCompetition: virtualCompetitionModel,\n };\n }),\n tap((_) => this.setWidgetLoaded()),\n );\n }\n\n protected override onData(payload: CompetitionHeaderPayload): void {\n this.payload$.next(payload);\n }\n}\n","import { Injectable, Type } from '@angular/core';\n\nimport { LoggerFactory } from '@frontend/sports/common/core/feature/logging';\n\nimport { TeamPagesViewModel } from '../teampages-core/team-pages.model';\nimport { TeamPagesModule } from '../teampages/team-pages.module';\nimport { ComponentLoaderService, ComponentProxy } from './component-loader.service';\nimport ModuleLoaderService from './module-loader.service';\n\nexport interface TeamPagesCompetitionViewBinding {\n teampagesViewModel: TeamPagesViewModel;\n hasCompetitions: boolean;\n}\n\n@Injectable({ providedIn: 'root' })\nexport class TeamPagesComponentLoaderService extends ComponentLoaderService {\n moduleName = 'TeamPagesModule';\n\n constructor(moduleLoader: ModuleLoaderService, loggerFactory: LoggerFactory) {\n super(moduleLoader, loggerFactory);\n }\n\n getTeamPagesComponent(): ComponentProxy {\n return this.getComponentProxy((module) => module.competitionTeamPagesComponent, 'team-pages');\n }\n\n getModule(): Promise> {\n return import(/* webpackChunkName: \"ms-teampages\" */ '../teampages/team-pages.module').then((m) => m.TeamPagesModule);\n }\n}\n","\n \n \n\n","import { AsyncPipe, NgIf } from '@angular/common';\nimport { Component, HostBinding } from '@angular/core';\n\nimport { LeagueItem } from '@frontend/sports/common/core/data-access/sport-model';\nimport { RouterEventsService } from '@frontend/sports/common/core/utils/router-events';\nimport { interpolateDoubleBracesString } from '@frontend/sports/common/core/utils/string';\nimport { CompetitionTeamsPayload } from '@frontend/sports/types/components/widget/types';\nimport { Observable, ReplaySubject, of } from 'rxjs';\nimport { concatMap, switchMap, tap, withLatestFrom } from 'rxjs/operators';\n\nimport { SportsCacheService } from '../../client-caching/sports-cache.service';\nimport { CompetitionRouteService } from '../../competition-list/competition-route.service';\nimport { ComponentProxy } from '../../deferred/component-loader.service';\nimport { ComponentProxyDirective } from '../../deferred/component-proxy.directive';\nimport { TeamPagesCompetitionViewBinding, TeamPagesComponentLoaderService } from '../../deferred/team-pages-component-loader.service';\nimport { TeamPagesApiService } from '../../teampages-core/team-pages-api.service';\nimport { TeamPagesCoreService } from '../../teampages-core/team-pages-core.service';\nimport { TeamPagesViewModel } from '../../teampages-core/team-pages.model';\nimport { WidgetCommonModule } from '../../widget/common/widget-common.module';\nimport { WidgetComponent } from '../../widget/core/widget-registry.service';\nimport { PruneHelperService } from './competitions/prune-helper.service';\nimport { VirtualCompetitionService } from './virtual-competition.service';\n\n@Component({\n selector: 'ms-competition-teams-widget',\n templateUrl: 'competition-teams-widget.html',\n standalone: true,\n imports: [NgIf, AsyncPipe, WidgetCommonModule, ComponentProxyDirective],\n})\nexport class CompetitionTeamsWidgetComponent extends WidgetComponent {\n private payload = new ReplaySubject(1);\n private payload$ = this.payload.asObservable();\n\n readonly teamPagesCompetitionComponent: ComponentProxy;\n\n readonly vm$: Observable;\n title: string | undefined = undefined;\n\n @HostBinding('class') className = 'competition-teams-widget grid-wrapper';\n @HostBinding('class.competition-teams-widget--standalone') get standalone() {\n return !this.parent;\n }\n\n constructor(\n private loader: TeamPagesComponentLoaderService,\n private teamPagesApi: TeamPagesApiService,\n private teamPagesCore: TeamPagesCoreService,\n private routeEvents: RouterEventsService,\n private bettingCache: SportsCacheService,\n private competitionRoute: CompetitionRouteService,\n private pruneService: PruneHelperService,\n private virtualCompetitionService: VirtualCompetitionService,\n ) {\n super();\n\n this.teamPagesCompetitionComponent = this.loader.getTeamPagesComponent();\n\n this.vm$ = this.payload$.pipe(\n withLatestFrom(this.routeEvents.currentActivationEnd),\n switchMap(([payload, route]) => {\n const sport$ = this.bettingCache.getSport(this.competitionRoute.params().sport!);\n\n return sport$.pipe(\n concatMap((sport) => {\n if (!route?.snapshot) {\n return of(undefined);\n }\n\n const { participants, competitionId } = payload;\n const teams = participants\n .filter((item) => item && item.participantId)\n .map((item) => this.teamPagesApi.mapTeam(item, competitionId));\n\n const leagueRoute = this.competitionRoute.params();\n const prunedSport = this.pruneService.prune(sport, leagueRoute);\n const competition = this.virtualCompetitionService.getCompetition(prunedSport, leagueRoute, !!leagueRoute.isVirtual);\n\n this.title = this.interpolateTitle(payload.title, competition);\n\n return this.teamPagesCore.getTeamPageViewModel(teams, route.snapshot);\n }),\n );\n }),\n tap((model) => {\n if (model) {\n this.teamPagesCompetitionComponent.update({ teampagesViewModel: model });\n }\n this.setWidgetLoaded();\n }),\n );\n }\n\n override onData(payload: CompetitionTeamsPayload): void {\n this.payload.next(payload);\n }\n\n private interpolateTitle(title: string | undefined, league: LeagueItem | undefined): string {\n return interpolateDoubleBracesString(title, { competition: league?.name ?? '' });\n }\n}\n","import { Injectable, Optional } from '@angular/core';\n\nimport { FixturePage, FixtureType, OfferCategory } from '@cds/betting-offer';\nimport { FixtureRequest, FixtureState, OfferMapping, SortByCriteria } from '@cds/query-objects';\nimport { BettingOfferApi } from '@frontend/sports/betting-offer/feature/offer-service';\nimport { GridConfig } from '@frontend/sports/common/client-config-data-access';\nimport { SportConstant } from '@frontend/sports/common/core/data-access/constants';\nimport { CountItem, sportModelMethods } from '@frontend/sports/common/core/data-access/sport-model';\nimport { GridSorting } from '@frontend/sports/grid/core/feature/model';\nimport { toString } from 'lodash-es';\nimport { Observable } from 'rxjs';\nimport { map } from 'rxjs/operators';\n\nimport { CalendarService } from '../../../calendar/calendar.service';\nimport { SportsCacheService } from '../../../client-caching/sports-cache.service';\nimport { MarketOfferUrlParam } from '../../../navigation-core/url-helper.service';\nimport { CompetitionRoute } from '../../../navigation/navigation.models';\nimport { StatisticsConfigService } from '../../../statistics/statistics-config.service';\n\n@Injectable({ providedIn: 'root' })\nexport class EventListService {\n constructor(\n protected bettingContent: BettingOfferApi,\n protected calendarService: CalendarService,\n protected gridConfig: GridConfig,\n protected bettingCache: SportsCacheService,\n @Optional() protected statisticsConfigService?: StatisticsConfigService,\n ) {}\n\n getSportList(start?: string, end?: string): Observable {\n const cached = !start && !end;\n\n const source = cached\n ? this.bettingCache.getSportList()\n : this.bettingContent.getSportList({ from: start, to: end }).pipe(map((sports) => sportModelMethods.fromTag(sports)));\n\n return source;\n }\n\n getFixtureList(\n route: CompetitionRoute,\n sorting?: GridSorting,\n skip?: number,\n take?: number,\n interval: { from?: string; to?: string } = {},\n ): Observable {\n skip = skip || 0;\n take = take || this.gridConfig.initialSize;\n\n const fixtureRequest = this.createFixtureRequest(route);\n const dateInterval = this.calendarService.getTimeInterval(route.context) || interval;\n\n if (!fixtureRequest.dynamicOfferCategories && (dateInterval.from || dateInterval.to || route.tournament)) {\n fixtureRequest.fixtureCategories = [\n OfferCategory.Gridable,\n OfferCategory.NonGridable,\n OfferCategory.Other,\n OfferCategory.Specials,\n OfferCategory.Outrights,\n ].join(',');\n\n fixtureRequest.offerCategories =\n route.marketOffer === MarketOfferUrlParam.Specials || route.marketOffer === MarketOfferUrlParam.Outrights\n ? [OfferCategory.Outrights, OfferCategory.Specials].join(',')\n : OfferCategory.Gridable;\n }\n\n fixtureRequest.skip = skip;\n fixtureRequest.take = take;\n fixtureRequest.sortBy = sorting === GridSorting.Time ? SortByCriteria.StartDate : SortByCriteria.Tags;\n fixtureRequest.from = dateInterval.from;\n fixtureRequest.to = dateInterval.to;\n\n return this.bettingContent.getFixtureList(fixtureRequest);\n }\n\n createFixtureRequest(route: CompetitionRoute): FixtureRequest {\n let offerCategories: string | undefined;\n let offerTemplates: string | undefined;\n let fixtureCategories: string | undefined;\n let isPriceBoost = false;\n const hasSubcategories = route.outrightCategory || route.specialCategory;\n\n if (!route.marketTemplate) {\n if (route.marketOffer === MarketOfferUrlParam.PriceBoost) {\n isPriceBoost = true;\n } else if (route.marketOffer === MarketOfferUrlParam.Outrights) {\n offerCategories = OfferCategory.Outrights;\n fixtureCategories = !hasSubcategories ? OfferCategory.Outrights : undefined;\n } else if (route.marketOffer === MarketOfferUrlParam.Specials) {\n offerCategories = OfferCategory.Specials;\n fixtureCategories = !hasSubcategories ? OfferCategory.Specials : undefined;\n } else {\n if (route.marketOffer === MarketOfferUrlParam.NonGridable) {\n offerCategories = OfferCategory.NonGridable;\n } else {\n offerCategories = OfferCategory.Gridable;\n }\n\n fixtureCategories = [OfferCategory.Gridable, OfferCategory.NonGridable, OfferCategory.Other].join(',');\n }\n } else {\n offerTemplates = route.marketTemplate.toString();\n }\n\n return {\n fixtureTypes: FixtureType.Standard,\n state: FixtureState.Latest,\n offerMapping: OfferMapping.Filtered,\n offerCategories: route.dynamicOfferCategory ? undefined : offerCategories,\n dynamicOfferCategories: route.dynamicOfferCategory,\n outrightGroups: route.outrightCategory,\n specialGroups: route.specialCategory,\n fixtureCategories: route.dynamicOfferCategory ? undefined : fixtureCategories,\n sportIds: toString(route.sport),\n regionIds: route.sport === SportConstant.Tennis ? undefined : toString(route.region),\n tournamentIds: route.sport === SportConstant.Tennis ? toString(route.region) : undefined,\n competitionIds: route.isVirtual ? undefined : toString(route.league),\n conferenceIds: route.isVirtual ? undefined : toString(route.conference),\n virtualCompetitionIds: route.isVirtual ? toString(route.league) : undefined,\n virtualCompetitionGroupIds: (route.isVirtual && toString(route.virtualCompetitionGroup)) || undefined,\n gameTemplateIds: offerTemplates,\n isPriceBoost,\n statisticsModes: this.statisticsConfigService?.getStatisticsModesBySportId(route.sport),\n };\n }\n}\n","import { Observable } from 'rxjs';\n\nimport { BreadcrumbModel, BreadcrumbOptions } from './breadcrumb.models';\nimport { BreadcrumbSlot } from './breadcrumbs-slot.service';\n\nexport type BreadcrumbData = BreadcrumbModel[] | BreadcrumbSlotResolve;\nexport type BreadcrumbResult = BreadcrumbData | Observable;\n\nexport interface BreadcrumbSlotResolve extends BreadcrumbSlot {\n schemaBreadcrumbs?: BreadcrumbModel[];\n}\n\nexport interface IBreadcrumbsResolve {\n resolve(model: any): BreadcrumbResult;\n}\n\nexport abstract class BreadcrumbsResolve implements IBreadcrumbsResolve {\n protected crumbs: BreadcrumbModel[] = [];\n\n resolve(model: T): BreadcrumbResult {\n this.crumbs = [];\n\n return this.customResolve(model);\n }\n\n protected abstract customResolve(model: T): BreadcrumbResult;\n\n protected createCrumb = (options: BreadcrumbOptions): BreadcrumbModel => {\n if (!options.sportId || !options.leagueId) {\n return {\n icon: options.icon,\n title: options.title,\n url: options.url,\n classes: options.classes,\n ignoreBrowserHistory: options.ignoreBrowserHistory,\n bgColor: options.bgColor,\n color: options.color,\n };\n }\n\n options.isWithRightImage = options.isWithRightImage || false;\n\n return {\n icon: options.icon,\n title: options.title,\n url: options.url,\n sportId: options.sportId,\n leagueId: options.leagueId,\n isWithRightImage: options.isWithRightImage,\n classes: options.classes,\n ignoreBrowserHistory: options.ignoreBrowserHistory,\n bgColor: options.bgColor,\n color: options.color,\n imageProfile: options.imageProfile,\n };\n };\n\n protected addToCrumbs(crumbsToAdd: BreadcrumbModel[]): BreadcrumbModel[] {\n this.crumbs.push(...crumbsToAdd);\n\n return this.crumbs;\n }\n}\n","import { Route, UrlMatchResult, UrlSegment, UrlSegmentGroup } from '@angular/router';\n\nimport { keys } from 'lodash-es';\n\n// uses regex expession defined on route data\n// matcherPattern - regex to match\n// matcherMapping - consumes current segment and maps params from regex matching with the specified result index\nexport function regexMatcher(segments: UrlSegment[], segmentGroup: UrlSegmentGroup, route: Route): UrlMatchResult {\n if (!route.data?.matcherPattern) {\n throw new Error('No regex defined on route');\n }\n\n if (segments.length === 0) {\n return null as any; // the angular matcher function is typed incorrectly and it can actually handle null\n }\n\n const regex = new RegExp(route.data.matcherPattern);\n const max = route.data.matcherMode === 'full' ? segments.length : 1;\n const url = segments\n .filter((segment, index) => index < max)\n .map((segment) => segment.path)\n .join('/');\n\n const match = regex.exec(url);\n const matcherMapping = route.data.matcherMapping;\n\n if (match) {\n if (!matcherMapping) {\n return { consumed: [], posParams: {} };\n }\n\n const posParams: { [name: string]: UrlSegment } = {};\n\n keys(matcherMapping).forEach((key) => {\n const index: number = matcherMapping[key];\n\n posParams[key] = new UrlSegment(match[index], {});\n });\n\n return { consumed: segments.slice(0, max), posParams };\n }\n\n return null as any;\n}\n","import { Route, UrlMatchResult, UrlSegment, UrlSegmentGroup } from '@angular/router';\n\nimport { isEmpty } from 'lodash-es';\n\nimport { regexMatcher } from './regex.matcher';\n\nconst NO_MATCH: UrlMatchResult = null as any;\n\nexport function nameIdentifierMatcher(segments: UrlSegment[], segmentGroup: UrlSegmentGroup, route: Route): UrlMatchResult {\n if (!segments.length || !route.data || !route.data.matcherParam) {\n return NO_MATCH;\n }\n\n const segmentsToParse = segments.slice(0, 1);\n const posParams = parsePosParams(segmentsToParse, [route.data.matcherParam]);\n\n return isEmpty(posParams) ? NO_MATCH : { consumed: segmentsToParse, posParams };\n}\n\nexport function multiNameIdentifierExactMatcher(segments: UrlSegment[], segmentGroup: UrlSegmentGroup, route: Route): UrlMatchResult {\n if (!segments.length || !route.data || !route.data.matcherParams || segments.length !== route.data.matcherParams.length) {\n return NO_MATCH;\n }\n\n const posParams = parsePosParams(segments, route.data.matcherParams);\n\n return isEmpty(posParams) ? NO_MATCH : { consumed: segments, posParams };\n}\n\nexport function multiNameIdentifierMatcher(segments: UrlSegment[], segmentGroup: UrlSegmentGroup, route: Route): UrlMatchResult {\n if (!segments.length || !route.data || !route.data.matcherParams || segments.length > route.data.matcherParams.length) {\n return NO_MATCH;\n }\n\n const posParams = parsePosParams(segments, route.data.matcherParams);\n\n return isEmpty(posParams) ? NO_MATCH : { consumed: segments, posParams };\n}\n\nexport function multiNameIdentifierMatcher_WithRegexMatcher(segments: UrlSegment[], segmentGroup: UrlSegmentGroup, route: Route): UrlMatchResult {\n if (\n !segments.length ||\n !route.data ||\n !route.data.matcherParams ||\n segments.length > route.data.matcherParams.length ||\n !route.data.matcherPattern\n ) {\n return NO_MATCH;\n }\n\n const regexResult = regexMatcher(segmentGroup.segments, segmentGroup, route);\n\n if (regexResult === NO_MATCH) {\n return regexResult;\n }\n\n const posParams = parsePosParams(segments, route.data.matcherParams);\n\n return isEmpty(posParams)\n ? NO_MATCH\n : { consumed: [...regexResult.consumed, ...segments], posParams: { ...posParams, ...regexResult.posParams } };\n}\n\nexport function multiNameIdentifierMatcher_WithChildren(segments: UrlSegment[], segmentGroup: UrlSegmentGroup, route: Route): UrlMatchResult {\n if (!segments.length || !route.data || !route.data.matcherParams || segments.length < route.data.matcherParams.length) {\n return NO_MATCH;\n }\n\n let parentSegments = segments;\n if (segments.length > route.data.matcherParams.length) {\n parentSegments = segments.slice(0, route.data.matcherParams.length);\n }\n\n const posParams = parsePosParams(parentSegments, route.data.matcherParams);\n\n return isEmpty(posParams) ? NO_MATCH : { consumed: parentSegments, posParams };\n}\n\nfunction parsePosParams(segments: UrlSegment[], matcherParams: string[]): { [name: string]: UrlSegment } {\n const posParams: { [name: string]: UrlSegment } = {};\n for (let i = 0; i < segments.length; i++) {\n const nameIdentifierPair = parseSegment(segments[i]);\n const paramName = matcherParams[i];\n\n if (nameIdentifierPair.id) {\n posParams[`${paramName}`] = new UrlSegment(nameIdentifierPair.id, {});\n\n if (nameIdentifierPair.name) {\n posParams[`${paramName}Name`] = new UrlSegment(nameIdentifierPair.name, {});\n }\n }\n }\n\n return posParams;\n}\n\nfunction parseSegment(segment: UrlSegment): { id: string; name: string } {\n const valuesRegex = new RegExp('((.*)-)?((\\\\d+:)?\\\\d+)$', 'gmi');\n const valuesMatch: string[] = valuesRegex.exec(segment.path) || [];\n\n const name = valuesMatch[2];\n const id = valuesMatch[3];\n\n return { id, name };\n}\n","export enum TopNavigationVersion {\n V2 = 'V2',\n V3 = 'V3',\n}\n","/**\n * @license\n * Copyright Google LLC All Rights Reserved.\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://angular.dev/license\n */\nimport { ɵisPromise as isPromise } from '@angular/core';\nimport { from, isObservable, of } from 'rxjs';\nexport function shallowEqualArrays(a, b) {\n if (a.length !== b.length) return false;\n for (let i = 0; i < a.length; ++i) {\n if (!shallowEqual(a[i], b[i])) return false;\n }\n return true;\n}\nexport function shallowEqual(a, b) {\n // While `undefined` should never be possible, it would sometimes be the case in IE 11\n // and pre-chromium Edge. The check below accounts for this edge case.\n const k1 = a ? getDataKeys(a) : undefined;\n const k2 = b ? getDataKeys(b) : undefined;\n if (!k1 || !k2 || k1.length != k2.length) {\n return false;\n }\n let key;\n for (let i = 0; i < k1.length; i++) {\n key = k1[i];\n if (!equalArraysOrString(a[key], b[key])) {\n return false;\n }\n }\n return true;\n}\n/**\n * Gets the keys of an object, including `symbol` keys.\n */\nexport function getDataKeys(obj) {\n return [...Object.keys(obj), ...Object.getOwnPropertySymbols(obj)];\n}\n/**\n * Test equality for arrays of strings or a string.\n */\nexport function equalArraysOrString(a, b) {\n if (Array.isArray(a) && Array.isArray(b)) {\n if (a.length !== b.length) return false;\n const aSorted = [...a].sort();\n const bSorted = [...b].sort();\n return aSorted.every((val, index) => bSorted[index] === val);\n } else {\n return a === b;\n }\n}\n/**\n * Return the last element of an array.\n */\nexport function last(a) {\n return a.length > 0 ? a[a.length - 1] : null;\n}\nexport function wrapIntoObservable(value) {\n if (isObservable(value)) {\n return value;\n }\n if (isPromise(value)) {\n // Use `Promise.resolve()` to wrap promise-like instances.\n // Required ie when a Resolver returns a AngularJS `$q` promise to correctly trigger the\n // change detection.\n return from(Promise.resolve(value));\n }\n return of(value);\n}\n","/**\n * @license\n * Copyright Google LLC All Rights Reserved.\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://angular.dev/license\n */\nimport { Injectable, ɵRuntimeError as RuntimeError } from '@angular/core';\nimport { convertToParamMap, PRIMARY_OUTLET } from './shared';\nimport { equalArraysOrString, shallowEqual } from './utils/collection';\nimport * as i0 from \"@angular/core\";\nconst pathCompareMap = {\n 'exact': equalSegmentGroups,\n 'subset': containsSegmentGroup\n};\nconst paramCompareMap = {\n 'exact': equalParams,\n 'subset': containsParams,\n 'ignored': () => true\n};\nexport function containsTree(container, containee, options) {\n return pathCompareMap[options.paths](container.root, containee.root, options.matrixParams) && paramCompareMap[options.queryParams](container.queryParams, containee.queryParams) && !(options.fragment === 'exact' && container.fragment !== containee.fragment);\n}\nfunction equalParams(container, containee) {\n // TODO: This does not handle array params correctly.\n return shallowEqual(container, containee);\n}\nfunction equalSegmentGroups(container, containee, matrixParams) {\n if (!equalPath(container.segments, containee.segments)) return false;\n if (!matrixParamsMatch(container.segments, containee.segments, matrixParams)) {\n return false;\n }\n if (container.numberOfChildren !== containee.numberOfChildren) return false;\n for (const c in containee.children) {\n if (!container.children[c]) return false;\n if (!equalSegmentGroups(container.children[c], containee.children[c], matrixParams)) return false;\n }\n return true;\n}\nfunction containsParams(container, containee) {\n return Object.keys(containee).length <= Object.keys(container).length && Object.keys(containee).every(key => equalArraysOrString(container[key], containee[key]));\n}\nfunction containsSegmentGroup(container, containee, matrixParams) {\n return containsSegmentGroupHelper(container, containee, containee.segments, matrixParams);\n}\nfunction containsSegmentGroupHelper(container, containee, containeePaths, matrixParams) {\n if (container.segments.length > containeePaths.length) {\n const current = container.segments.slice(0, containeePaths.length);\n if (!equalPath(current, containeePaths)) return false;\n if (containee.hasChildren()) return false;\n if (!matrixParamsMatch(current, containeePaths, matrixParams)) return false;\n return true;\n } else if (container.segments.length === containeePaths.length) {\n if (!equalPath(container.segments, containeePaths)) return false;\n if (!matrixParamsMatch(container.segments, containeePaths, matrixParams)) return false;\n for (const c in containee.children) {\n if (!container.children[c]) return false;\n if (!containsSegmentGroup(container.children[c], containee.children[c], matrixParams)) {\n return false;\n }\n }\n return true;\n } else {\n const current = containeePaths.slice(0, container.segments.length);\n const next = containeePaths.slice(container.segments.length);\n if (!equalPath(container.segments, current)) return false;\n if (!matrixParamsMatch(container.segments, current, matrixParams)) return false;\n if (!container.children[PRIMARY_OUTLET]) return false;\n return containsSegmentGroupHelper(container.children[PRIMARY_OUTLET], containee, next, matrixParams);\n }\n}\nfunction matrixParamsMatch(containerPaths, containeePaths, options) {\n return containeePaths.every((containeeSegment, i) => {\n return paramCompareMap[options](containerPaths[i].parameters, containeeSegment.parameters);\n });\n}\n/**\n * @description\n *\n * Represents the parsed URL.\n *\n * Since a router state is a tree, and the URL is nothing but a serialized state, the URL is a\n * serialized tree.\n * UrlTree is a data structure that provides a lot of affordances in dealing with URLs\n *\n * @usageNotes\n * ### Example\n *\n * ```\n * @Component({templateUrl:'template.html'})\n * class MyComponent {\n * constructor(router: Router) {\n * const tree: UrlTree =\n * router.parseUrl('/team/33/(user/victor//support:help)?debug=true#fragment');\n * const f = tree.fragment; // return 'fragment'\n * const q = tree.queryParams; // returns {debug: 'true'}\n * const g: UrlSegmentGroup = tree.root.children[PRIMARY_OUTLET];\n * const s: UrlSegment[] = g.segments; // returns 2 segments 'team' and '33'\n * g.children[PRIMARY_OUTLET].segments; // returns 2 segments 'user' and 'victor'\n * g.children['support'].segments; // return 1 segment 'help'\n * }\n * }\n * ```\n *\n * @publicApi\n */\nexport class UrlTree {\n constructor(/** The root segment group of the URL tree */\n root = new UrlSegmentGroup([], {}), /** The query params of the URL */\n queryParams = {}, /** The fragment of the URL */\n fragment = null) {\n this.root = root;\n this.queryParams = queryParams;\n this.fragment = fragment;\n if (typeof ngDevMode === 'undefined' || ngDevMode) {\n if (root.segments.length > 0) {\n throw new RuntimeError(4015 /* RuntimeErrorCode.INVALID_ROOT_URL_SEGMENT */, 'The root `UrlSegmentGroup` should not contain `segments`. ' + 'Instead, these segments belong in the `children` so they can be associated with a named outlet.');\n }\n }\n }\n get queryParamMap() {\n this._queryParamMap ??= convertToParamMap(this.queryParams);\n return this._queryParamMap;\n }\n /** @docsNotRequired */\n toString() {\n return DEFAULT_SERIALIZER.serialize(this);\n }\n}\n/**\n * @description\n *\n * Represents the parsed URL segment group.\n *\n * See `UrlTree` for more information.\n *\n * @publicApi\n */\nexport class UrlSegmentGroup {\n constructor(/** The URL segments of this group. See `UrlSegment` for more information */\n segments, /** The list of children of this group */\n children) {\n this.segments = segments;\n this.children = children;\n /** The parent node in the url tree */\n this.parent = null;\n Object.values(children).forEach(v => v.parent = this);\n }\n /** Whether the segment has child segments */\n hasChildren() {\n return this.numberOfChildren > 0;\n }\n /** Number of child segments */\n get numberOfChildren() {\n return Object.keys(this.children).length;\n }\n /** @docsNotRequired */\n toString() {\n return serializePaths(this);\n }\n}\n/**\n * @description\n *\n * Represents a single URL segment.\n *\n * A UrlSegment is a part of a URL between the two slashes. It contains a path and the matrix\n * parameters associated with the segment.\n *\n * @usageNotes\n * ### Example\n *\n * ```\n * @Component({templateUrl:'template.html'})\n * class MyComponent {\n * constructor(router: Router) {\n * const tree: UrlTree = router.parseUrl('/team;id=33');\n * const g: UrlSegmentGroup = tree.root.children[PRIMARY_OUTLET];\n * const s: UrlSegment[] = g.segments;\n * s[0].path; // returns 'team'\n * s[0].parameters; // returns {id: 33}\n * }\n * }\n * ```\n *\n * @publicApi\n */\nexport class UrlSegment {\n constructor(/** The path part of a URL segment */\n path, /** The matrix parameters associated with a segment */\n parameters) {\n this.path = path;\n this.parameters = parameters;\n }\n get parameterMap() {\n this._parameterMap ??= convertToParamMap(this.parameters);\n return this._parameterMap;\n }\n /** @docsNotRequired */\n toString() {\n return serializePath(this);\n }\n}\nexport function equalSegments(as, bs) {\n return equalPath(as, bs) && as.every((a, i) => shallowEqual(a.parameters, bs[i].parameters));\n}\nexport function equalPath(as, bs) {\n if (as.length !== bs.length) return false;\n return as.every((a, i) => a.path === bs[i].path);\n}\nexport function mapChildrenIntoArray(segment, fn) {\n let res = [];\n Object.entries(segment.children).forEach(([childOutlet, child]) => {\n if (childOutlet === PRIMARY_OUTLET) {\n res = res.concat(fn(child, childOutlet));\n }\n });\n Object.entries(segment.children).forEach(([childOutlet, child]) => {\n if (childOutlet !== PRIMARY_OUTLET) {\n res = res.concat(fn(child, childOutlet));\n }\n });\n return res;\n}\n/**\n * @description\n *\n * Serializes and deserializes a URL string into a URL tree.\n *\n * The url serialization strategy is customizable. You can\n * make all URLs case insensitive by providing a custom UrlSerializer.\n *\n * See `DefaultUrlSerializer` for an example of a URL serializer.\n *\n * @publicApi\n */\nexport let UrlSerializer = /*#__PURE__*/(() => {\n class UrlSerializer {\n static {\n this.ɵfac = function UrlSerializer_Factory(__ngFactoryType__) {\n return new (__ngFactoryType__ || UrlSerializer)();\n };\n }\n static {\n this.ɵprov = /* @__PURE__ */i0.ɵɵdefineInjectable({\n token: UrlSerializer,\n factory: () => (() => new DefaultUrlSerializer())(),\n providedIn: 'root'\n });\n }\n }\n return UrlSerializer;\n})();\n/*#__PURE__*/(() => {\n (typeof ngDevMode === \"undefined\" || ngDevMode) && void 0;\n})();\n/**\n * @description\n *\n * A default implementation of the `UrlSerializer`.\n *\n * Example URLs:\n *\n * ```\n * /inbox/33(popup:compose)\n * /inbox/33;open=true/messages/44\n * ```\n *\n * DefaultUrlSerializer uses parentheses to serialize secondary segments (e.g., popup:compose), the\n * colon syntax to specify the outlet, and the ';parameter=value' syntax (e.g., open=true) to\n * specify route specific parameters.\n *\n * @publicApi\n */\nexport class DefaultUrlSerializer {\n /** Parses a url into a `UrlTree` */\n parse(url) {\n const p = new UrlParser(url);\n return new UrlTree(p.parseRootSegment(), p.parseQueryParams(), p.parseFragment());\n }\n /** Converts a `UrlTree` into a url */\n serialize(tree) {\n const segment = `/${serializeSegment(tree.root, true)}`;\n const query = serializeQueryParams(tree.queryParams);\n const fragment = typeof tree.fragment === `string` ? `#${encodeUriFragment(tree.fragment)}` : '';\n return `${segment}${query}${fragment}`;\n }\n}\nconst DEFAULT_SERIALIZER = /*#__PURE__*/new DefaultUrlSerializer();\nexport function serializePaths(segment) {\n return segment.segments.map(p => serializePath(p)).join('/');\n}\nfunction serializeSegment(segment, root) {\n if (!segment.hasChildren()) {\n return serializePaths(segment);\n }\n if (root) {\n const primary = segment.children[PRIMARY_OUTLET] ? serializeSegment(segment.children[PRIMARY_OUTLET], false) : '';\n const children = [];\n Object.entries(segment.children).forEach(([k, v]) => {\n if (k !== PRIMARY_OUTLET) {\n children.push(`${k}:${serializeSegment(v, false)}`);\n }\n });\n return children.length > 0 ? `${primary}(${children.join('//')})` : primary;\n } else {\n const children = mapChildrenIntoArray(segment, (v, k) => {\n if (k === PRIMARY_OUTLET) {\n return [serializeSegment(segment.children[PRIMARY_OUTLET], false)];\n }\n return [`${k}:${serializeSegment(v, false)}`];\n });\n // use no parenthesis if the only child is a primary outlet route\n if (Object.keys(segment.children).length === 1 && segment.children[PRIMARY_OUTLET] != null) {\n return `${serializePaths(segment)}/${children[0]}`;\n }\n return `${serializePaths(segment)}/(${children.join('//')})`;\n }\n}\n/**\n * Encodes a URI string with the default encoding. This function will only ever be called from\n * `encodeUriQuery` or `encodeUriSegment` as it's the base set of encodings to be used. We need\n * a custom encoding because encodeURIComponent is too aggressive and encodes stuff that doesn't\n * have to be encoded per https://url.spec.whatwg.org.\n */\nfunction encodeUriString(s) {\n return encodeURIComponent(s).replace(/%40/g, '@').replace(/%3A/gi, ':').replace(/%24/g, '$').replace(/%2C/gi, ',');\n}\n/**\n * This function should be used to encode both keys and values in a query string key/value. In\n * the following URL, you need to call encodeUriQuery on \"k\" and \"v\":\n *\n * http://www.site.org/html;mk=mv?k=v#f\n */\nexport function encodeUriQuery(s) {\n return encodeUriString(s).replace(/%3B/gi, ';');\n}\n/**\n * This function should be used to encode a URL fragment. In the following URL, you need to call\n * encodeUriFragment on \"f\":\n *\n * http://www.site.org/html;mk=mv?k=v#f\n */\nexport function encodeUriFragment(s) {\n return encodeURI(s);\n}\n/**\n * This function should be run on any URI segment as well as the key and value in a key/value\n * pair for matrix params. In the following URL, you need to call encodeUriSegment on \"html\",\n * \"mk\", and \"mv\":\n *\n * http://www.site.org/html;mk=mv?k=v#f\n */\nexport function encodeUriSegment(s) {\n return encodeUriString(s).replace(/\\(/g, '%28').replace(/\\)/g, '%29').replace(/%26/gi, '&');\n}\nexport function decode(s) {\n return decodeURIComponent(s);\n}\n// Query keys/values should have the \"+\" replaced first, as \"+\" in a query string is \" \".\n// decodeURIComponent function will not decode \"+\" as a space.\nexport function decodeQuery(s) {\n return decode(s.replace(/\\+/g, '%20'));\n}\nexport function serializePath(path) {\n return `${encodeUriSegment(path.path)}${serializeMatrixParams(path.parameters)}`;\n}\nfunction serializeMatrixParams(params) {\n return Object.entries(params).map(([key, value]) => `;${encodeUriSegment(key)}=${encodeUriSegment(value)}`).join('');\n}\nfunction serializeQueryParams(params) {\n const strParams = Object.entries(params).map(([name, value]) => {\n return Array.isArray(value) ? value.map(v => `${encodeUriQuery(name)}=${encodeUriQuery(v)}`).join('&') : `${encodeUriQuery(name)}=${encodeUriQuery(value)}`;\n }).filter(s => s);\n return strParams.length ? `?${strParams.join('&')}` : '';\n}\nconst SEGMENT_RE = /^[^\\/()?;#]+/;\nfunction matchSegments(str) {\n const match = str.match(SEGMENT_RE);\n return match ? match[0] : '';\n}\nconst MATRIX_PARAM_SEGMENT_RE = /^[^\\/()?;=#]+/;\nfunction matchMatrixKeySegments(str) {\n const match = str.match(MATRIX_PARAM_SEGMENT_RE);\n return match ? match[0] : '';\n}\nconst QUERY_PARAM_RE = /^[^=?&#]+/;\n// Return the name of the query param at the start of the string or an empty string\nfunction matchQueryParams(str) {\n const match = str.match(QUERY_PARAM_RE);\n return match ? match[0] : '';\n}\nconst QUERY_PARAM_VALUE_RE = /^[^&#]+/;\n// Return the value of the query param at the start of the string or an empty string\nfunction matchUrlQueryParamValue(str) {\n const match = str.match(QUERY_PARAM_VALUE_RE);\n return match ? match[0] : '';\n}\nclass UrlParser {\n constructor(url) {\n this.url = url;\n this.remaining = url;\n }\n parseRootSegment() {\n this.consumeOptional('/');\n if (this.remaining === '' || this.peekStartsWith('?') || this.peekStartsWith('#')) {\n return new UrlSegmentGroup([], {});\n }\n // The root segment group never has segments\n return new UrlSegmentGroup([], this.parseChildren());\n }\n parseQueryParams() {\n const params = {};\n if (this.consumeOptional('?')) {\n do {\n this.parseQueryParam(params);\n } while (this.consumeOptional('&'));\n }\n return params;\n }\n parseFragment() {\n return this.consumeOptional('#') ? decodeURIComponent(this.remaining) : null;\n }\n parseChildren() {\n if (this.remaining === '') {\n return {};\n }\n this.consumeOptional('/');\n const segments = [];\n if (!this.peekStartsWith('(')) {\n segments.push(this.parseSegment());\n }\n while (this.peekStartsWith('/') && !this.peekStartsWith('//') && !this.peekStartsWith('/(')) {\n this.capture('/');\n segments.push(this.parseSegment());\n }\n let children = {};\n if (this.peekStartsWith('/(')) {\n this.capture('/');\n children = this.parseParens(true);\n }\n let res = {};\n if (this.peekStartsWith('(')) {\n res = this.parseParens(false);\n }\n if (segments.length > 0 || Object.keys(children).length > 0) {\n res[PRIMARY_OUTLET] = new UrlSegmentGroup(segments, children);\n }\n return res;\n }\n // parse a segment with its matrix parameters\n // ie `name;k1=v1;k2`\n parseSegment() {\n const path = matchSegments(this.remaining);\n if (path === '' && this.peekStartsWith(';')) {\n throw new RuntimeError(4009 /* RuntimeErrorCode.EMPTY_PATH_WITH_PARAMS */, (typeof ngDevMode === 'undefined' || ngDevMode) && `Empty path url segment cannot have parameters: '${this.remaining}'.`);\n }\n this.capture(path);\n return new UrlSegment(decode(path), this.parseMatrixParams());\n }\n parseMatrixParams() {\n const params = {};\n while (this.consumeOptional(';')) {\n this.parseParam(params);\n }\n return params;\n }\n parseParam(params) {\n const key = matchMatrixKeySegments(this.remaining);\n if (!key) {\n return;\n }\n this.capture(key);\n let value = '';\n if (this.consumeOptional('=')) {\n const valueMatch = matchSegments(this.remaining);\n if (valueMatch) {\n value = valueMatch;\n this.capture(value);\n }\n }\n params[decode(key)] = decode(value);\n }\n // Parse a single query parameter `name[=value]`\n parseQueryParam(params) {\n const key = matchQueryParams(this.remaining);\n if (!key) {\n return;\n }\n this.capture(key);\n let value = '';\n if (this.consumeOptional('=')) {\n const valueMatch = matchUrlQueryParamValue(this.remaining);\n if (valueMatch) {\n value = valueMatch;\n this.capture(value);\n }\n }\n const decodedKey = decodeQuery(key);\n const decodedVal = decodeQuery(value);\n if (params.hasOwnProperty(decodedKey)) {\n // Append to existing values\n let currentVal = params[decodedKey];\n if (!Array.isArray(currentVal)) {\n currentVal = [currentVal];\n params[decodedKey] = currentVal;\n }\n currentVal.push(decodedVal);\n } else {\n // Create a new value\n params[decodedKey] = decodedVal;\n }\n }\n // parse `(a/b//outlet_name:c/d)`\n parseParens(allowPrimary) {\n const segments = {};\n this.capture('(');\n while (!this.consumeOptional(')') && this.remaining.length > 0) {\n const path = matchSegments(this.remaining);\n const next = this.remaining[path.length];\n // if is is not one of these characters, then the segment was unescaped\n // or the group was not closed\n if (next !== '/' && next !== ')' && next !== ';') {\n throw new RuntimeError(4010 /* RuntimeErrorCode.UNPARSABLE_URL */, (typeof ngDevMode === 'undefined' || ngDevMode) && `Cannot parse url '${this.url}'`);\n }\n let outletName = undefined;\n if (path.indexOf(':') > -1) {\n outletName = path.slice(0, path.indexOf(':'));\n this.capture(outletName);\n this.capture(':');\n } else if (allowPrimary) {\n outletName = PRIMARY_OUTLET;\n }\n const children = this.parseChildren();\n segments[outletName] = Object.keys(children).length === 1 ? children[PRIMARY_OUTLET] : new UrlSegmentGroup([], children);\n this.consumeOptional('//');\n }\n return segments;\n }\n peekStartsWith(str) {\n return this.remaining.startsWith(str);\n }\n // Consumes the prefix when it is present and returns whether it has been consumed\n consumeOptional(str) {\n if (this.peekStartsWith(str)) {\n this.remaining = this.remaining.substring(str.length);\n return true;\n }\n return false;\n }\n capture(str) {\n if (!this.consumeOptional(str)) {\n throw new RuntimeError(4011 /* RuntimeErrorCode.UNEXPECTED_VALUE_IN_URL */, (typeof ngDevMode === 'undefined' || ngDevMode) && `Expected \"${str}\".`);\n }\n }\n}\nexport function createRoot(rootCandidate) {\n return rootCandidate.segments.length > 0 ? new UrlSegmentGroup([], {\n [PRIMARY_OUTLET]: rootCandidate\n }) : rootCandidate;\n}\n/**\n * Recursively\n * - merges primary segment children into their parents\n * - drops empty children (those which have no segments and no children themselves). This latter\n * prevents serializing a group into something like `/a(aux:)`, where `aux` is an empty child\n * segment.\n * - merges named outlets without a primary segment sibling into the children. This prevents\n * serializing a URL like `//(a:a)(b:b) instead of `/(a:a//b:b)` when the aux b route lives on the\n * root but the `a` route lives under an empty path primary route.\n */\nexport function squashSegmentGroup(segmentGroup) {\n const newChildren = {};\n for (const [childOutlet, child] of Object.entries(segmentGroup.children)) {\n const childCandidate = squashSegmentGroup(child);\n // moves named children in an empty path primary child into this group\n if (childOutlet === PRIMARY_OUTLET && childCandidate.segments.length === 0 && childCandidate.hasChildren()) {\n for (const [grandChildOutlet, grandChild] of Object.entries(childCandidate.children)) {\n newChildren[grandChildOutlet] = grandChild;\n }\n } // don't add empty children\n else if (childCandidate.segments.length > 0 || childCandidate.hasChildren()) {\n newChildren[childOutlet] = childCandidate;\n }\n }\n const s = new UrlSegmentGroup(segmentGroup.segments, newChildren);\n return mergeTrivialChildren(s);\n}\n/**\n * When possible, merges the primary outlet child into the parent `UrlSegmentGroup`.\n *\n * When a segment group has only one child which is a primary outlet, merges that child into the\n * parent. That is, the child segment group's segments are merged into the `s` and the child's\n * children become the children of `s`. Think of this like a 'squash', merging the child segment\n * group into the parent.\n */\nfunction mergeTrivialChildren(s) {\n if (s.numberOfChildren === 1 && s.children[PRIMARY_OUTLET]) {\n const c = s.children[PRIMARY_OUTLET];\n return new UrlSegmentGroup(s.segments.concat(c.segments), c.children);\n }\n return s;\n}\nexport function isUrlTree(v) {\n return v instanceof UrlTree;\n}\n","/**\n * @license\n * Copyright Google LLC All Rights Reserved.\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://angular.dev/license\n */\nimport { createEnvironmentInjector, isStandalone, ɵisNgModule as isNgModule, ɵRuntimeError as RuntimeError } from '@angular/core';\nimport { PRIMARY_OUTLET } from '../shared';\n/**\n * Creates an `EnvironmentInjector` if the `Route` has providers and one does not already exist\n * and returns the injector. Otherwise, if the `Route` does not have `providers`, returns the\n * `currentInjector`.\n *\n * @param route The route that might have providers\n * @param currentInjector The parent injector of the `Route`\n */\nexport function getOrCreateRouteInjectorIfNeeded(route, currentInjector) {\n if (route.providers && !route._injector) {\n route._injector = createEnvironmentInjector(route.providers, currentInjector, `Route: ${route.path}`);\n }\n return route._injector ?? currentInjector;\n}\nexport function getLoadedRoutes(route) {\n return route._loadedRoutes;\n}\nexport function getLoadedInjector(route) {\n return route._loadedInjector;\n}\nexport function getLoadedComponent(route) {\n return route._loadedComponent;\n}\nexport function getProvidersInjector(route) {\n return route._injector;\n}\nexport function validateConfig(config, parentPath = '', requireStandaloneComponents = false) {\n // forEach doesn't iterate undefined values\n for (let i = 0; i < config.length; i++) {\n const route = config[i];\n const fullPath = getFullPath(parentPath, route);\n validateNode(route, fullPath, requireStandaloneComponents);\n }\n}\nexport function assertStandalone(fullPath, component) {\n if (component && isNgModule(component)) {\n throw new RuntimeError(4014 /* RuntimeErrorCode.INVALID_ROUTE_CONFIG */, `Invalid configuration of route '${fullPath}'. You are using 'loadComponent' with a module, ` + `but it must be used with standalone components. Use 'loadChildren' instead.`);\n } else if (component && !isStandalone(component)) {\n throw new RuntimeError(4014 /* RuntimeErrorCode.INVALID_ROUTE_CONFIG */, `Invalid configuration of route '${fullPath}'. The component must be standalone.`);\n }\n}\nfunction validateNode(route, fullPath, requireStandaloneComponents) {\n if (typeof ngDevMode === 'undefined' || ngDevMode) {\n if (!route) {\n throw new RuntimeError(4014 /* RuntimeErrorCode.INVALID_ROUTE_CONFIG */, `\n Invalid configuration of route '${fullPath}': Encountered undefined route.\n The reason might be an extra comma.\n\n Example:\n const routes: Routes = [\n { path: '', redirectTo: '/dashboard', pathMatch: 'full' },\n { path: 'dashboard', component: DashboardComponent },, << two commas\n { path: 'detail/:id', component: HeroDetailComponent }\n ];\n `);\n }\n if (Array.isArray(route)) {\n throw new RuntimeError(4014 /* RuntimeErrorCode.INVALID_ROUTE_CONFIG */, `Invalid configuration of route '${fullPath}': Array cannot be specified`);\n }\n if (!route.redirectTo && !route.component && !route.loadComponent && !route.children && !route.loadChildren && route.outlet && route.outlet !== PRIMARY_OUTLET) {\n throw new RuntimeError(4014 /* RuntimeErrorCode.INVALID_ROUTE_CONFIG */, `Invalid configuration of route '${fullPath}': a componentless route without children or loadChildren cannot have a named outlet set`);\n }\n if (route.redirectTo && route.children) {\n throw new RuntimeError(4014 /* RuntimeErrorCode.INVALID_ROUTE_CONFIG */, `Invalid configuration of route '${fullPath}': redirectTo and children cannot be used together`);\n }\n if (route.redirectTo && route.loadChildren) {\n throw new RuntimeError(4014 /* RuntimeErrorCode.INVALID_ROUTE_CONFIG */, `Invalid configuration of route '${fullPath}': redirectTo and loadChildren cannot be used together`);\n }\n if (route.children && route.loadChildren) {\n throw new RuntimeError(4014 /* RuntimeErrorCode.INVALID_ROUTE_CONFIG */, `Invalid configuration of route '${fullPath}': children and loadChildren cannot be used together`);\n }\n if (route.redirectTo && (route.component || route.loadComponent)) {\n throw new RuntimeError(4014 /* RuntimeErrorCode.INVALID_ROUTE_CONFIG */, `Invalid configuration of route '${fullPath}': redirectTo and component/loadComponent cannot be used together`);\n }\n if (route.component && route.loadComponent) {\n throw new RuntimeError(4014 /* RuntimeErrorCode.INVALID_ROUTE_CONFIG */, `Invalid configuration of route '${fullPath}': component and loadComponent cannot be used together`);\n }\n if (route.redirectTo && route.canActivate) {\n throw new RuntimeError(4014 /* RuntimeErrorCode.INVALID_ROUTE_CONFIG */, `Invalid configuration of route '${fullPath}': redirectTo and canActivate cannot be used together. Redirects happen before activation ` + `so canActivate will never be executed.`);\n }\n if (route.path && route.matcher) {\n throw new RuntimeError(4014 /* RuntimeErrorCode.INVALID_ROUTE_CONFIG */, `Invalid configuration of route '${fullPath}': path and matcher cannot be used together`);\n }\n if (route.redirectTo === void 0 && !route.component && !route.loadComponent && !route.children && !route.loadChildren) {\n throw new RuntimeError(4014 /* RuntimeErrorCode.INVALID_ROUTE_CONFIG */, `Invalid configuration of route '${fullPath}'. One of the following must be provided: component, loadComponent, redirectTo, children or loadChildren`);\n }\n if (route.path === void 0 && route.matcher === void 0) {\n throw new RuntimeError(4014 /* RuntimeErrorCode.INVALID_ROUTE_CONFIG */, `Invalid configuration of route '${fullPath}': routes must have either a path or a matcher specified`);\n }\n if (typeof route.path === 'string' && route.path.charAt(0) === '/') {\n throw new RuntimeError(4014 /* RuntimeErrorCode.INVALID_ROUTE_CONFIG */, `Invalid configuration of route '${fullPath}': path cannot start with a slash`);\n }\n if (route.path === '' && route.redirectTo !== void 0 && route.pathMatch === void 0) {\n const exp = `The default value of 'pathMatch' is 'prefix', but often the intent is to use 'full'.`;\n throw new RuntimeError(4014 /* RuntimeErrorCode.INVALID_ROUTE_CONFIG */, `Invalid configuration of route '{path: \"${fullPath}\", redirectTo: \"${route.redirectTo}\"}': please provide 'pathMatch'. ${exp}`);\n }\n if (requireStandaloneComponents) {\n assertStandalone(fullPath, route.component);\n }\n }\n if (route.children) {\n validateConfig(route.children, fullPath, requireStandaloneComponents);\n }\n}\nfunction getFullPath(parentPath, currentRoute) {\n if (!currentRoute) {\n return parentPath;\n }\n if (!parentPath && !currentRoute.path) {\n return '';\n } else if (parentPath && !currentRoute.path) {\n return `${parentPath}/`;\n } else if (!parentPath && currentRoute.path) {\n return currentRoute.path;\n } else {\n return `${parentPath}/${currentRoute.path}`;\n }\n}\n/** Returns the `route.outlet` or PRIMARY_OUTLET if none exists. */\nexport function getOutlet(route) {\n return route.outlet || PRIMARY_OUTLET;\n}\n/**\n * Sorts the `routes` such that the ones with an outlet matching `outletName` come first.\n * The order of the configs is otherwise preserved.\n */\nexport function sortByMatchingOutlets(routes, outletName) {\n const sortedConfig = routes.filter(r => getOutlet(r) === outletName);\n sortedConfig.push(...routes.filter(r => getOutlet(r) !== outletName));\n return sortedConfig;\n}\n/**\n * Gets the first injector in the snapshot's parent tree.\n *\n * If the `Route` has a static list of providers, the returned injector will be the one created from\n * those. If it does not exist, the returned injector may come from the parents, which may be from a\n * loaded config or their static providers.\n *\n * Returns `null` if there is neither this nor any parents have a stored injector.\n *\n * Generally used for retrieving the injector to use for getting tokens for guards/resolvers and\n * also used for getting the correct injector to use for creating components.\n */\nexport function getClosestRouteInjector(snapshot) {\n if (!snapshot) return null;\n // If the current route has its own injector, which is created from the static providers on the\n // route itself, we should use that. Otherwise, we start at the parent since we do not want to\n // include the lazy loaded injector from this route.\n if (snapshot.routeConfig?._injector) {\n return snapshot.routeConfig._injector;\n }\n for (let s = snapshot.parent; s; s = s.parent) {\n const route = s.routeConfig;\n // Note that the order here is important. `_loadedInjector` stored on the route with\n // `loadChildren: () => NgModule` so it applies to child routes with priority. The `_injector`\n // is created from the static providers on that parent route, so it applies to the children as\n // well, but only if there is no lazy loaded NgModuleRef injector.\n if (route?._loadedInjector) return route._loadedInjector;\n if (route?._injector) return route._injector;\n }\n return null;\n}\n","/**\n * @license\n * Copyright Google LLC All Rights Reserved.\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://angular.dev/license\n */\nimport { ɵisInjectable as isInjectable } from '@angular/core';\nimport { equalParamsAndUrlSegments } from '../router_state';\nimport { equalPath } from '../url_tree';\nimport { shallowEqual } from '../utils/collection';\nimport { nodeChildrenAsMap } from '../utils/tree';\nexport class CanActivate {\n constructor(path) {\n this.path = path;\n this.route = this.path[this.path.length - 1];\n }\n}\nexport class CanDeactivate {\n constructor(component, route) {\n this.component = component;\n this.route = route;\n }\n}\nexport function getAllRouteGuards(future, curr, parentContexts) {\n const futureRoot = future._root;\n const currRoot = curr ? curr._root : null;\n return getChildRouteGuards(futureRoot, currRoot, parentContexts, [futureRoot.value]);\n}\nexport function getCanActivateChild(p) {\n const canActivateChild = p.routeConfig ? p.routeConfig.canActivateChild : null;\n if (!canActivateChild || canActivateChild.length === 0) return null;\n return {\n node: p,\n guards: canActivateChild\n };\n}\nexport function getTokenOrFunctionIdentity(tokenOrFunction, injector) {\n const NOT_FOUND = Symbol();\n const result = injector.get(tokenOrFunction, NOT_FOUND);\n if (result === NOT_FOUND) {\n if (typeof tokenOrFunction === 'function' && !isInjectable(tokenOrFunction)) {\n // We think the token is just a function so return it as-is\n return tokenOrFunction;\n } else {\n // This will throw the not found error\n return injector.get(tokenOrFunction);\n }\n }\n return result;\n}\nfunction getChildRouteGuards(futureNode, currNode, contexts, futurePath, checks = {\n canDeactivateChecks: [],\n canActivateChecks: []\n}) {\n const prevChildren = nodeChildrenAsMap(currNode);\n // Process the children of the future route\n futureNode.children.forEach(c => {\n getRouteGuards(c, prevChildren[c.value.outlet], contexts, futurePath.concat([c.value]), checks);\n delete prevChildren[c.value.outlet];\n });\n // Process any children left from the current route (not active for the future route)\n Object.entries(prevChildren).forEach(([k, v]) => deactivateRouteAndItsChildren(v, contexts.getContext(k), checks));\n return checks;\n}\nfunction getRouteGuards(futureNode, currNode, parentContexts, futurePath, checks = {\n canDeactivateChecks: [],\n canActivateChecks: []\n}) {\n const future = futureNode.value;\n const curr = currNode ? currNode.value : null;\n const context = parentContexts ? parentContexts.getContext(futureNode.value.outlet) : null;\n // reusing the node\n if (curr && future.routeConfig === curr.routeConfig) {\n const shouldRun = shouldRunGuardsAndResolvers(curr, future, future.routeConfig.runGuardsAndResolvers);\n if (shouldRun) {\n checks.canActivateChecks.push(new CanActivate(futurePath));\n } else {\n // we need to set the data\n future.data = curr.data;\n future._resolvedData = curr._resolvedData;\n }\n // If we have a component, we need to go through an outlet.\n if (future.component) {\n getChildRouteGuards(futureNode, currNode, context ? context.children : null, futurePath, checks);\n // if we have a componentless route, we recurse but keep the same outlet map.\n } else {\n getChildRouteGuards(futureNode, currNode, parentContexts, futurePath, checks);\n }\n if (shouldRun && context && context.outlet && context.outlet.isActivated) {\n checks.canDeactivateChecks.push(new CanDeactivate(context.outlet.component, curr));\n }\n } else {\n if (curr) {\n deactivateRouteAndItsChildren(currNode, context, checks);\n }\n checks.canActivateChecks.push(new CanActivate(futurePath));\n // If we have a component, we need to go through an outlet.\n if (future.component) {\n getChildRouteGuards(futureNode, null, context ? context.children : null, futurePath, checks);\n // if we have a componentless route, we recurse but keep the same outlet map.\n } else {\n getChildRouteGuards(futureNode, null, parentContexts, futurePath, checks);\n }\n }\n return checks;\n}\nfunction shouldRunGuardsAndResolvers(curr, future, mode) {\n if (typeof mode === 'function') {\n return mode(curr, future);\n }\n switch (mode) {\n case 'pathParamsChange':\n return !equalPath(curr.url, future.url);\n case 'pathParamsOrQueryParamsChange':\n return !equalPath(curr.url, future.url) || !shallowEqual(curr.queryParams, future.queryParams);\n case 'always':\n return true;\n case 'paramsOrQueryParamsChange':\n return !equalParamsAndUrlSegments(curr, future) || !shallowEqual(curr.queryParams, future.queryParams);\n case 'paramsChange':\n default:\n return !equalParamsAndUrlSegments(curr, future);\n }\n}\nfunction deactivateRouteAndItsChildren(route, context, checks) {\n const children = nodeChildrenAsMap(route);\n const r = route.value;\n Object.entries(children).forEach(([childName, node]) => {\n if (!r.component) {\n deactivateRouteAndItsChildren(node, context, checks);\n } else if (context) {\n deactivateRouteAndItsChildren(node, context.children.getContext(childName), checks);\n } else {\n deactivateRouteAndItsChildren(node, null, checks);\n }\n });\n if (!r.component) {\n checks.canDeactivateChecks.push(new CanDeactivate(null, r));\n } else if (context && context.outlet && context.outlet.isActivated) {\n checks.canDeactivateChecks.push(new CanDeactivate(context.outlet.component, r));\n } else {\n checks.canDeactivateChecks.push(new CanDeactivate(null, r));\n }\n}\n","import { Injector } from '@angular/core';\nimport { ActivatedRouteSnapshot, UrlSegment } from '@angular/router';\n// @ts-ignore\nimport { equalPath } from '@angular/router/esm2022/src/url_tree';\n// @ts-ignore\nimport { shallowEqual } from '@angular/router/esm2022/src/utils/collection';\n// @ts-ignore\nimport { getClosestRouteInjector } from '@angular/router/esm2022/src/utils/config';\n// @ts-ignore\nimport { getTokenOrFunctionIdentity } from '@angular/router/esm2022/src/utils/preactivation';\n\nexport function getToken(token: any, snapshot: ActivatedRouteSnapshot, moduleInjector: Injector): T {\n return getTokenWithInjector(token, snapshot, moduleInjector).token;\n}\n\nexport function getTokenWithInjector(\n token: any,\n snapshot: ActivatedRouteSnapshot,\n moduleInjector: Injector,\n): { token: T; injector: Injector } {\n const injector = getClosestRouteInjector(snapshot) || moduleInjector;\n\n return { token: getTokenOrFunctionIdentity(token, injector), injector };\n}\n\nexport function paramsOrQueryParamsChange(from: ActivatedRouteSnapshot, to: ActivatedRouteSnapshot): boolean {\n const popupChanged = from.queryParamMap.has('popup') !== to.queryParamMap.has('popup') && to.queryParamMap.has('popup');\n\n if (popupChanged) {\n return false;\n }\n\n // same as: https://github.com/angular/angular/blob/master/packages/router/src/utils/preactivation.ts#L164\n return !equalParamsAndUrlSegments(from, to) || !shallowEqual(from.queryParams, to.queryParams);\n}\n\nexport function equalParamsAndUrlSegments(f: ActivatedRouteSnapshot, t: ActivatedRouteSnapshot): boolean {\n const equalUrlParams = shallowEqual(f.params, t.params) && equalSegments(f.url, t.url);\n const parentsMismatch = !f.parent !== !t.parent;\n\n return equalUrlParams && !parentsMismatch && (!f.parent || equalParamsAndUrlSegments(f.parent, t.parent!));\n}\n\nfunction equalSegments(aSegments: UrlSegment[], bSegments: UrlSegment[]): boolean {\n return equalPath(aSegments, bSegments) && aSegments.every((a, i) => shallowEqual(a.parameters, bSegments[i].parameters));\n}\n","import { Injectable } from '@angular/core';\n\nimport { SessionStoreService } from '@frontend/vanilla/core';\n\nimport { SitemapStateModel } from './sitemap.models';\n\n@Injectable({\n providedIn: 'root',\n})\nexport class SitemapSaveStateService {\n private sessionKey = 'sitemap-state';\n\n constructor(private sessionStore: SessionStoreService) {}\n\n get sitemap() {\n return (this.sessionStore.get(this.sessionKey) as SitemapStateModel) ?? {};\n }\n\n set sitemap(items: SitemapStateModel) {\n this.sessionStore.set(this.sessionKey, items);\n }\n}\n","import { Injectable } from '@angular/core';\n\nimport { SitemapSaveStateService } from './sitemap-save-state.service';\nimport { SitemapItemBaseModel, SitemapStateModel } from './sitemap.models';\n\n@Injectable({\n providedIn: 'root',\n})\nexport class SitemapStateService {\n private navigationPath: SitemapStateModel;\n\n constructor(private saveStateService: SitemapSaveStateService) {\n this.navigationPath = {\n currentState: this.saveStateService.sitemap.currentState || [],\n siblings: this.saveStateService.sitemap.siblings || [],\n };\n }\n\n toggleSitemapItem(item: SitemapItemBaseModel): void {\n this.actionWithSaveState(() => {\n if (item.active) {\n this.activateSitemapItem(item);\n } else {\n this.deactivateSitemapItem(item);\n }\n });\n }\n\n setPath(newPath: SitemapItemBaseModel[]): void {\n this.actionWithSaveState(() => {\n this.navigationPath.currentState = newPath;\n });\n }\n\n setSiblings(siblings: SitemapItemBaseModel[]): void {\n this.actionWithSaveState(() => {\n this.navigationPath.siblings = siblings;\n });\n }\n\n getItem(id: string, level: number): SitemapItemBaseModel | undefined {\n return this.navigationPath.currentState.find((n) => n.id === id && n.level === level);\n }\n\n get active(): SitemapItemBaseModel | undefined {\n return this.navigationPath.currentState.find((itm) => itm.active);\n }\n\n get drawerItem(): SitemapItemBaseModel | undefined {\n return this.navigationPath.currentState.find((n) => n.isInDrawer);\n }\n\n get navigation(): SitemapStateModel {\n return this.navigationPath;\n }\n\n get siblings(): SitemapItemBaseModel[] {\n return this.navigationPath.siblings;\n }\n\n private activateSitemapItem(item: SitemapItemBaseModel): void {\n const activeDrawerItem = this.navigationPath.currentState.find((n) => item.activeDrawerItem?.id && n.id === item.activeDrawerItem?.id);\n if (activeDrawerItem) {\n this.navigationPath.currentState = this.navigationPath.currentState.filter((itm) => itm.level <= activeDrawerItem.level);\n activeDrawerItem.active = true;\n\n return;\n }\n\n this.navigationPath.currentState = this.navigationPath.currentState.filter((itm) => itm.level < item.level);\n this.navigationPath.currentState.push({ ...item });\n this.navigationPath.currentState.filter((n) => n.id !== item.id && n.level !== item.level).forEach((n) => (n.active = false));\n this.navigationPath.siblings.filter((n) => n.id !== item.id).forEach((n) => (n.active = false));\n }\n\n private deactivateSitemapItem(item: SitemapItemBaseModel): void {\n if (item.level === 1) {\n this.navigationPath.currentState = [];\n\n return;\n }\n\n this.navigationPath.currentState = this.navigationPath.currentState.filter((itm) => itm.level < item.level);\n const newActiveItem = this.navigationPath.currentState.find((itm) => itm.level === item.level - 1);\n if (newActiveItem) {\n this.navigationPath.currentState.forEach((n) => (n.active = false));\n newActiveItem.active = true;\n }\n }\n\n private actionWithSaveState(action: () => void): void {\n action();\n this.saveStateService.sitemap = this.navigationPath;\n }\n}\n","import { ActivatedRouteSnapshot, Params } from '@angular/router';\n\nimport { Tag } from '@cds/betting-offer/tags';\n\nexport interface SitemapModel {\n showGoHome?: boolean;\n items: SitemapItemModel[];\n}\nexport interface SitemapItemBaseModel {\n id: string;\n title?: string;\n type: SitemapItemType;\n url?: string;\n href?: string; // used for seo\n active?: boolean;\n level: number;\n parentUrl?: string;\n isInDrawer?: boolean;\n hasLogo?: boolean;\n activeDrawerItem?: SitemapItemBaseModel;\n sportId?: number;\n competitionId?: number;\n conferenceId?: number;\n icon?: string;\n children?: SitemapItemBaseModel[];\n drawerItems?: SitemapItemBaseModel[];\n drawerSubTitle?: string;\n parentId?: string;\n showIcon?: boolean;\n isVirtual?: boolean;\n badge?: string;\n showActiveDrawerItemTitle?: boolean;\n tournamentId?: number;\n isCompetitionFallback?: boolean;\n}\nexport interface SiteMapResponse {\n data: SitemapItemBaseModel[];\n competitionFallback: SitemapItemBaseModel[];\n}\nexport interface SitemapItemModel extends SitemapItemBaseModel {\n showSeparator?: boolean;\n drawer?: SitemapDrawerModel;\n count?: number;\n scrollId?: string;\n}\n\nexport interface SitemapDrawerModel {\n type: SitemapDrawerType;\n sportId?: number;\n competitionId?: number;\n title: string;\n subTitle?: string;\n items?: SitemapItemBaseModel[];\n regions?: SitemapRegion[];\n}\n\nexport interface SitemapRegion {\n title: string;\n url?: string;\n urlText?: string;\n items?: SitemapItemBaseModel[];\n}\n\nexport enum SitemapItemType {\n Sport = 'Sport',\n Competition = 'Competition',\n Live = 'Live',\n CustomLink = 'CustomLink',\n AllLeaguesOrTournaments = 'AllLeaguesOrTournaments',\n Region = 'Region',\n LiveVideo = 'LiveVideo',\n Coupons = 'Coupons',\n Calendar = 'Calendar',\n Featured = 'Featured',\n Standings = 'Standings',\n Outrights = 'Outrights',\n Specials = 'Specials',\n MultiBuilder = 'MultiBuilder',\n Favourites = 'Favourites',\n Tournament = 'Tournament',\n Conference = 'Conference',\n WorldCupHub = 'WorldCupHub',\n VirtualCompetition = 'VirtualCompetition',\n Esports = 'Esports',\n DynamicOfferCategory = 'DynamicOfferCategory',\n MultiSportsHub = 'MultiSportsHub',\n}\n\nexport enum SitemapDrawerType {\n Popular = 'Popular',\n All = 'All',\n SelectConference = 'SelectConference',\n}\n\nexport interface SitemapResolveModel {\n routeSnapshot: ActivatedRouteSnapshot;\n model: TModel;\n state: SitemapStateModel;\n config: SitemapItemBaseModel[];\n competitionFallback: SitemapItemBaseModel[];\n}\n\nexport const AllowedMarketGroupSitemapItems = [SitemapItemType.Outrights, SitemapItemType.Specials] as const;\nexport type AllowedMarketGroupSitemapTypes = (typeof AllowedMarketGroupSitemapItems)[number];\nexport interface SitemapHomeModel {\n sitemap: SitemapItemModel[];\n sportsWithCompetitionsOrTournaments: SitemapItemBaseModel[];\n}\n\nexport interface SitemapTooltip {\n visible: boolean;\n message?: string;\n}\n\nexport interface SitemapStateModel {\n currentState: SitemapItemBaseModel[];\n siblings: SitemapItemBaseModel[];\n}\nexport interface VirtalResolverModel {\n event: Event;\n sports: SportDetails[];\n params: Params;\n}\nexport interface SportDetails {\n sport: Tag;\n competitions: Competition[];\n}\nexport interface Competition {\n competition: Tag;\n}\n","import { Injectable } from '@angular/core';\n\nimport { last, orderBy } from 'lodash-es';\n\nimport { SitemapStateService } from './sitemap-state.service';\nimport { SitemapItemBaseModel, SitemapItemModel, SitemapItemType } from './sitemap.models';\n\n@Injectable({\n providedIn: 'root',\n})\nexport class SitemapStateMapper {\n constructor(private stateService: SitemapStateService) {}\n\n syncState(items: SitemapItemModel[]): void {\n const activeFromState = this.stateService.active;\n const activeFromItems = last(\n orderBy(\n items\n .filter((itm) => itm.activeDrawerItem)\n .flatMap((itm) => itm.activeDrawerItem)\n .concat(items.filter((itm) => itm.active)),\n (itm) => itm!.level,\n ),\n );\n\n if (!activeFromState && !activeFromItems) {\n return;\n }\n if (!activeFromState || activeFromState?.id !== activeFromItems?.id || activeFromState?.type !== activeFromItems?.type) {\n const newNavPath = this.getNavigationPath(items);\n this.stateService.setPath(newNavPath);\n }\n }\n\n saveSiblingsInState(items: SitemapItemModel[]): void {\n const lastLevelItem = items[items.length - 1].level;\n const siblings = items.filter((itm) => itm.level === lastLevelItem);\n this.stateService.setSiblings(siblings);\n }\n\n getSiblingsFromState(items: SitemapItemModel[]): void {\n const siblings = this.stateService.siblings;\n const activeEl = items.find((itm) => !!itm.active)!;\n const specificTypes = [SitemapItemType.Coupons, SitemapItemType.Tournament];\n const activeItemIndex = siblings.findIndex(\n (item) => activeEl && (item.id === activeEl.id || (specificTypes.find((t) => t === activeEl.type) && item.type === activeEl.type)),\n );\n\n // when element is not part of the visible sitemap, we do not keep siblings\n if (activeItemIndex === -1) {\n return;\n }\n\n if (items.length === 1) {\n items.splice(0, 1, ...siblings);\n items[activeItemIndex] = activeEl;\n } else {\n const ind = items.findIndex((itm) => itm.level === activeEl.level);\n items[ind - 1].showSeparator = true;\n items.splice(ind, items.length, ...siblings);\n items[ind + activeItemIndex] = activeEl;\n }\n }\n\n private getNavigationPath(items: SitemapItemModel[]): SitemapItemBaseModel[] {\n const newPath: SitemapItemBaseModel[] = [];\n const defaultActive = items.find((itm) => itm.active);\n const tryPushDrawerItem = (item: SitemapItemModel) => {\n if (item.activeDrawerItem) {\n newPath.push(item.activeDrawerItem);\n }\n };\n if (defaultActive) {\n for (let l = 1; l < defaultActive.level; l++) {\n const item = items.find((itm) => itm.level === l);\n if (item) {\n newPath.push({ ...item });\n tryPushDrawerItem(item);\n }\n }\n newPath.push({ ...defaultActive });\n if (defaultActive.level === 1) {\n tryPushDrawerItem(defaultActive);\n }\n const lastIndx = newPath.length - 1;\n newPath.forEach((p, indx) => (p.active = indx === lastIndx));\n }\n\n return newPath;\n }\n}\n","import { Injectable } from '@angular/core';\n\nimport { ApiService } from '@frontend/sports/common/api-utils';\nimport { usePreloaderData } from '@frontend/sports/common/core/utils/preloaded-data';\nimport { BehaviorSubject, Observable } from 'rxjs';\n\nimport { SiteMapResponse, SitemapModel } from './sitemap.models';\n\n@Injectable({\n providedIn: 'root',\n})\nexport class SitemapService {\n private _sitemap$ = new BehaviorSubject({ items: [] });\n\n constructor(private api: ApiService) {}\n\n get sitemap$(): Observable {\n return this._sitemap$.asObservable();\n }\n\n @usePreloaderData({\n preloader: (window).MS2JS?.SiteMapLoader,\n })\n getSiteMapConfiguration(): Observable {\n return this.api.get('sitemap');\n }\n\n setSitemap(sitemap: SitemapModel): void {\n this._sitemap$.next(sitemap);\n }\n}\n","import { Injectable, Injector, Type } from '@angular/core';\nimport { ActivatedRouteSnapshot, ActivationEnd } from '@angular/router';\n\nimport { LayoutNavigationConfig, SitemapSitecore } from '@frontend/sports/common/client-config-data-access';\nimport { LoggerFactory, SportsRemoteLogger } from '@frontend/sports/common/core/feature/logging';\nimport { RouterEventsService } from '@frontend/sports/common/core/utils/router-events';\nimport { filterSports } from '@frontend/sports/host-app/sports-product/feature/utils';\nimport { OnAppInit } from '@frontend/vanilla/core';\nimport { Subject, forkJoin } from 'rxjs';\nimport { first, takeUntil } from 'rxjs/operators';\n\nimport { TopNavigationVersion } from '../navigation-core/navigation-core.models';\nimport { getToken } from '../router/router.exports';\nimport { SitemapStateMapper } from './sitemap-state-mapper';\nimport { SitemapStateService } from './sitemap-state.service';\nimport { SitemapItemBaseModel } from './sitemap.models';\nimport { SitemapResolve } from './sitemap.resolver';\nimport { SitemapService } from './sitemap.service';\n\n@Injectable({\n providedIn: 'root',\n})\nexport class SitemapBootstrapService implements OnAppInit {\n resolversMap: { [key: string]: Type> };\n private validSubscription$ = new Subject();\n private resolver?: SitemapResolve;\n private lastResolverType?: any;\n private readonly logger: SportsRemoteLogger;\n private isV3Navigation: boolean;\n private sitemapConfiguration: SitemapItemBaseModel[] = [];\n private competitionFallback: SitemapItemBaseModel[] = [];\n private emptySitemap = { items: [] };\n private lastActivationEnd?: ActivationEnd;\n private configIsLoaded?: boolean;\n private lastActivatedRouteSnapshot?: ActivatedRouteSnapshot;\n\n constructor(\n private injector: Injector,\n private routerEvents: RouterEventsService,\n loggerFactory: LoggerFactory,\n private sitemapService: SitemapService,\n private layoutNavConfig: LayoutNavigationConfig,\n private stateMapper: SitemapStateMapper,\n private stateService: SitemapStateService,\n private sitecore: SitemapSitecore,\n ) {\n this.isV3Navigation = this.layoutNavConfig.topNavigationVersion === TopNavigationVersion.V3;\n\n if (this.isV3Navigation) {\n this.logger = loggerFactory.getLogger('SitemapBootstrapService');\n const messages = this.sitecore.whenReady.pipe(first());\n const service = this.sitemapService.getSiteMapConfiguration().pipe(first());\n forkJoin([service, messages]).subscribe(([config, _]) => {\n this.sitemapConfiguration = config.data;\n this.competitionFallback = config.competitionFallback;\n this.configIsLoaded = true;\n if (this.lastActivationEnd) {\n this.activationEnded(this.lastActivationEnd);\n }\n });\n }\n }\n\n onAppInit(): void {\n if (this.isV3Navigation) {\n this.routerEvents.currentActivationEnd.pipe(filterSports()).subscribe((event) => this.activationEnded(event));\n }\n }\n\n private activationEnded(event: ActivationEnd | undefined): void {\n if (!event) {\n return;\n }\n\n this.lastActivationEnd = event;\n\n if (!this.configIsLoaded) {\n return;\n }\n\n const resolverType = this.findFirstSitemapResolver(event.snapshot);\n if (!resolverType) {\n this.renewSubscription();\n this.lastResolverType = null;\n this.sitemapService.setSitemap(this.emptySitemap);\n\n return;\n }\n\n if (resolverType !== this.lastResolverType) {\n this.renewSubscription();\n\n this.resolver = getToken>(resolverType, event.snapshot, this.injector);\n\n if (!this.resolver) {\n this.sitemapService.setSitemap(this.emptySitemap);\n this.logger.error(`Could not resolve Sitemap resolver: ${resolverType} - route: ${event}`);\n\n return;\n }\n\n this.lastResolverType = resolverType;\n }\n\n if (this.lastActivatedRouteSnapshot !== event.snapshot) {\n this.lastActivatedRouteSnapshot = event.snapshot;\n const model = event.snapshot.data.model;\n const resolveModel = {\n routeSnapshot: event.snapshot,\n model,\n state: this.stateService.navigation,\n config: this.sitemapConfiguration,\n competitionFallback: this.competitionFallback,\n };\n this.resolver\n ?.resolve(resolveModel, this.stateMapper)\n .pipe(takeUntil(this.validSubscription$))\n .subscribe((sitemap) => this.sitemapService.setSitemap(sitemap));\n }\n }\n\n private renewSubscription(): void {\n this.validSubscription$.next(undefined);\n this.validSubscription$.complete();\n this.validSubscription$ = new Subject();\n }\n\n private findFirstSitemapResolver(snapshot: ActivatedRouteSnapshot): Type> | undefined {\n let crtSnapshot: ActivatedRouteSnapshot | null = snapshot;\n\n while (crtSnapshot && !crtSnapshot.data?.sitemap) {\n crtSnapshot = crtSnapshot.parent;\n }\n\n const resolverTypeName = crtSnapshot?.data?.sitemap;\n if (!resolverTypeName || !this.resolversMap) {\n return undefined;\n }\n\n return this.resolversMap[resolverTypeName];\n }\n}\n","import { Injectable } from '@angular/core';\n\nimport { BehaviorSubject, Observable } from 'rxjs';\n\nexport interface BreadcrumbSlot {\n component: any;\n inputs?: any;\n required?: boolean;\n}\n\n@Injectable({ providedIn: 'root' })\nexport class BreadcrumbsSlotService {\n private _componentRegistered: BehaviorSubject;\n\n constructor() {\n this._componentRegistered = new BehaviorSubject(undefined);\n }\n\n setSlot(slot: BreadcrumbSlot): void {\n this._componentRegistered.next(slot);\n }\n\n clearSlot(): void {\n this._componentRegistered.next(undefined);\n }\n\n get slotRegistered$(): Observable {\n return this._componentRegistered.asObservable();\n }\n\n get registeredComponent(): BreadcrumbSlot | undefined {\n return this._componentRegistered.getValue();\n }\n}\n","import { Injectable } from '@angular/core';\n\nimport { CountItem } from '@frontend/sports/common/core/data-access/sport-model';\nimport { filterSportsEmitLast } from '@frontend/sports/host-app/sports-product/feature/utils';\nimport { sumBy } from 'lodash-es';\nimport { NEVER, Subject, combineLatest, switchMap } from 'rxjs';\nimport { distinctUntilKeyChanged } from 'rxjs/operators';\n\nimport { BreadcrumbsSlotService } from '../breadcrumbs/breadcrumbs-slot.service';\nimport { AdaptiveLayoutService } from '../layout/adaptive-layout.service';\n\ninterface LiveNowLinkData {\n sports: CountItem[];\n url?: string;\n}\n\nconst loadLiveNowItemComponent = () => import('./live-now-item.component').then((c) => c.LiveNowItemComponent);\n\n@Injectable({ providedIn: 'root' })\nexport class LiveNowLinkRegisterService {\n private data$ = new Subject();\n\n constructor(\n private adaptiveLayout: AdaptiveLayoutService,\n private breadcrumbSlot: BreadcrumbsSlotService,\n ) {\n const layoutState$ = this.adaptiveLayout.stateChange$.pipe(distinctUntilKeyChanged('headerSubNavigation'));\n\n combineLatest([this.data$, layoutState$])\n .pipe(\n filterSportsEmitLast(),\n switchMap(([data, state]) => {\n if (!data?.sports?.length || !state.headerSubNavigation) {\n this.breadcrumbSlot.clearSlot();\n\n return NEVER;\n }\n\n const liveCount = sumBy(data.sports, (sport: CountItem) => sport.counts.live);\n\n if (liveCount) {\n return loadLiveNowItemComponent().then((component) => ({\n component,\n inputs: {\n count: liveCount,\n liveUrl: data.url,\n sport: data.sports.length === 1 && data.sports[0].id ? data.sports[0] : undefined,\n },\n }));\n }\n this.breadcrumbSlot.clearSlot();\n\n return NEVER;\n }),\n )\n .subscribe(({ component, inputs }) => this.setLiveButton(component, inputs));\n }\n\n addLiveButtonWithMultipleSports(sports: CountItem[], url: string): void {\n this.data$.next({ sports, url });\n }\n\n addLiveButton(sport?: CountItem, url?: string): void {\n this.data$.next({ sports: sport ? [sport] : [], url });\n }\n\n removeLiveButton(): void {\n this.data$.next();\n }\n\n private setLiveButton(\n component: any,\n inputs: {\n count: number;\n liveUrl?: string;\n sport?: CountItem;\n },\n ): void {\n this.breadcrumbSlot.setSlot({\n component,\n inputs,\n });\n }\n}\n","import { Injectable } from '@angular/core';\n\nimport { LocalStoreService } from '@frontend/vanilla/core';\n\nexport interface StorageItem {\n data: T;\n timestamp: number;\n}\n\n@Injectable({\n providedIn: 'root',\n})\nexport class LocalStorageService {\n constructor(private localStore: LocalStoreService) {}\n\n getItem(storageKey: string): T[] {\n const y = this.localStore.get(storageKey) || [];\n\n return y;\n }\n\n getItemObject(storageKey: string): T | null {\n return this.localStore.get(storageKey);\n }\n\n saveItem(storageKey: string, input: T): void {\n if (!input) {\n return;\n }\n\n this.localStore.set(storageKey, input);\n }\n\n clearStorage(storageKey: string): void {\n this.localStore.remove(storageKey);\n }\n\n addTimestampToItem(item: T): StorageItem {\n const timestamp = new Date().getTime();\n\n return { data: item, timestamp } as StorageItem;\n }\n\n sortStorageItemsByTimestamp(items: StorageItem[]): StorageItem[] {\n return items.sort((a, b) => b.timestamp - a.timestamp);\n }\n\n clearExpiredItems(expirationDays: number, storageKey: string): void {\n const storedItems: StorageItem[] = this.getItem>(storageKey);\n const currentTime = new Date().getTime();\n let hasItemRemoved = false;\n\n if (storedItems.length) {\n const filteredArray = storedItems.filter((item) => {\n if (!item?.timestamp) {\n return false;\n }\n const millisecondsInOneDay: number = 1000 * 60 * 60 * 24;\n const daysDifference: number = Math.floor((currentTime - item.timestamp) / millisecondsInOneDay);\n\n if (daysDifference >= expirationDays) {\n hasItemRemoved = true;\n\n return false;\n }\n\n return true;\n });\n\n if (hasItemRemoved) {\n this.saveItem[]>(storageKey, filteredArray);\n }\n }\n }\n}\n","import { Injectable } from '@angular/core';\n\nimport { VirtualCompetitionGroupItem } from '@frontend/sports/common/core/data-access/sport-model';\n\nimport { FixtureList } from '../event-list-shared/sport/competitions/competition.models';\n\n@Injectable({ providedIn: 'root' })\nexport class CompetitionListSeoService {\n private realCompetitionId: number | undefined;\n\n getRealCompetitionId(): number | undefined {\n return this.realCompetitionId;\n }\n\n saveRealCompetitionId(id: number | undefined): void {\n this.realCompetitionId = id;\n }\n\n storeRealCompetitionId(fixtureList: FixtureList | undefined): void {\n this.realCompetitionId = undefined;\n const virtualCompetition = fixtureList && fixtureList.virtualCompetition;\n if (virtualCompetition) {\n const virtualGroupId = fixtureList.params && fixtureList.params.virtualCompetitionGroup;\n const virtualGroup =\n virtualCompetition.children &&\n (virtualCompetition.children.find((group) => group.id === virtualGroupId) as VirtualCompetitionGroupItem | undefined);\n this.realCompetitionId = virtualGroup ? virtualGroup.siblings[0] : virtualCompetition.siblings[0];\n }\n }\n}\n","import { Injectable } from '@angular/core';\nimport { Params } from '@angular/router';\n\nimport { ApiService } from '@frontend/sports/common/api-utils';\nimport { PrettyUrlsConfig } from '@frontend/sports/common/client-config-data-access';\nimport { LoggerFactory, SportsRemoteLogger } from '@frontend/sports/common/core/feature/logging';\nimport { assign, isArray, isEqual, keys, pickBy } from 'lodash-es';\nimport { Observable, of } from 'rxjs';\nimport { catchError, map, switchMap, tap } from 'rxjs/operators';\n\nimport { MasterdataApiService } from '../cds/cds-masterdata-api.service';\nimport { CompetitionListSeoService } from '../competition-list/competition-list-seo.service';\nimport { CompetitionRouteService } from '../competition-list/competition-route.service';\nimport { SportUrlParam } from '../navigation-core/url-helper.service';\nimport { CompetitionRoute } from '../navigation/navigation.models';\nimport { SeoContentApiRequest, SeoContentApiResponse, SeoRoute } from './seo.models';\n\ninterface SeoContentRequest {\n filter?: string;\n lobby?: boolean;\n live?: boolean;\n virtual?: boolean;\n highlights?: boolean;\n allSports?: boolean;\n sport?: number;\n region?: number | string;\n league?: number;\n event?: string;\n video?: boolean;\n conference?: number;\n virtualCompetitionId?: number;\n virtualCompetitionGroupId?: number;\n esportsLobby?: boolean;\n esportsHighlights?: boolean;\n multiSportsLobby?: boolean;\n}\n\n@Injectable({ providedIn: 'root' })\nexport class SeoContentService {\n private lastRequestApiParams: SeoContentApiRequest | null = null;\n private lastResponse: SeoContentApiResponse | null = null;\n private readonly logger: SportsRemoteLogger;\n\n constructor(\n private competitionRoute: CompetitionRouteService,\n private api: ApiService,\n loggerFactory: LoggerFactory,\n private urlConfig: PrettyUrlsConfig,\n private competitionListSeoService: CompetitionListSeoService,\n private masterDataApiService: MasterdataApiService,\n ) {\n this.logger = loggerFactory.getLogger('SeoContentService');\n }\n\n getForEventDetails(request: SeoContentRequest): Observable {\n return this.performApiRequest(request);\n }\n\n getSeoContent(routeParams: Params, data: SeoRoute): Observable {\n const competitionData = this.competitionRoute.params();\n const league = competitionData.league;\n const routeData = assign(competitionData, routeParams);\n\n if (!routeData) {\n return of(undefined);\n }\n\n return this.getLeagueId(routeData, league || routeData.league).pipe(\n switchMap((leagueId) => {\n return this.performApiRequest({\n filter: this.getSeoFilter(routeData),\n lobby: data.sportsLobby,\n live: data.live,\n virtual: data.virtual,\n highlights: data.liveHighlights,\n allSports: data.sportsList,\n sport: routeData.sport,\n region: routeData.region || routeData.tournament,\n league: leagueId,\n video: data.liveVideo,\n conference: routeData.conference,\n virtualCompetitionId: routeData.isVirtual ? routeParams.league : null,\n virtualCompetitionGroupId: routeData.isVirtual ? routeParams.virtualCompetitionGroup : null,\n esportsLobby: data.esportsLobby,\n esportsHighlights: data.esportsHighlights,\n multiSportsLobby: data.multiSportsLobby,\n }).pipe(\n tap((response) => {\n if (response) {\n const fixtureContext: (string | undefined)[] = [\n this.urlConfig.translations.betting,\n this.urlConfig.translations.conferences,\n this.urlConfig.translations.after2days,\n this.urlConfig.translations.after3days,\n this.urlConfig.translations.in30minutes,\n this.urlConfig.translations.in60minutes,\n this.urlConfig.translations.in180minutes,\n this.urlConfig.translations.today,\n this.urlConfig.translations.tomorrow,\n ];\n\n response.h1Included = fixtureContext.includes(competitionData.context) && !!competitionData.region;\n }\n }),\n );\n }),\n );\n }\n\n private getLeagueId(routeData: CompetitionRoute & Params, leagueId: number | number[] | undefined): Observable {\n const league = isArray(leagueId) ? undefined : leagueId;\n if (!routeData.isVirtual) {\n return of(league);\n }\n const realCompetitionId = this.competitionListSeoService.getRealCompetitionId();\n if (realCompetitionId) {\n return of(realCompetitionId);\n }\n if (routeData.sport && league) {\n return this.masterDataApiService\n .getVirtualCompetitionInfo({\n competitionId: league,\n sportId: routeData.sport,\n virtualCompetitionGroupId: routeData.virtualCompetitionGroup || 0,\n virtualCompetitionId: league,\n })\n .pipe(\n map(\n (virtualCompetitionInfo) =>\n virtualCompetitionInfo?.virtualCompetitionGroup?.competitionIds[0] ||\n virtualCompetitionInfo?.virtualCompetition?.competitionIds[0],\n ),\n catchError((err) => {\n if (err.status) {\n this.logger.error(err, 'Error fetching virtual competition info');\n }\n\n return of(undefined);\n }),\n );\n }\n\n return of(league);\n }\n\n private performApiRequest(request: SeoContentRequest): Observable {\n const apiParams: SeoContentApiRequest = pickBy(request, (prop) => prop != null && prop !== false && prop !== '');\n\n if (this.lastResponse != null && isEqual(apiParams, this.lastRequestApiParams)) {\n return of(this.lastResponse);\n }\n\n return this.api.get('seo/tags', apiParams).pipe(\n tap((response) => {\n this.lastRequestApiParams = apiParams;\n this.lastResponse = response;\n }),\n catchError((err) => {\n // check if request got cancelled (happens e.g. if the user quickly navigates to another page or if the signal was lost) and only log exception if it's a real error\n if (err.status) {\n this.logger.error(err, 'Error getting SEO tags');\n }\n\n return of(undefined);\n }),\n );\n }\n\n private getSeoFilter(routeData: CompetitionRoute & Params): string | undefined {\n const contextKey = (input: string | undefined) =>\n keys(this.urlConfig.translations)\n .filter((prop) => this.urlConfig.translations[prop] === input)\n .pop();\n\n const subContext = contextKey(routeData.subContext);\n\n return subContext === SportUrlParam.Competitions ? subContext : contextKey(routeData.context);\n }\n}\n","import { Injectable } from '@angular/core';\n\nimport { trackingConstants } from '@frontend/sports/tracking/feature';\nimport { isNil } from 'lodash-es';\nimport { IPickTracking } from 'packages/sports/common/betslip/core/picks/pick-models';\n\n@Injectable({ providedIn: 'root' })\nexport class PickSourceProvider {\n getTracking(): { [key: string]: string } {\n return {};\n }\n\n get(\n source: string,\n contentPosition?: number,\n isFallbackMarketEnabled?: boolean,\n marqueeName?: string,\n sitecoreTemplateId?: string,\n marqueeType?: string,\n trackingOptions?: { [key: string]: string },\n isAutomatedMarquee?: boolean,\n isInSheetView?: boolean,\n ): IPickTracking {\n const baseTracking: IPickTracking = {\n source,\n additional: trackingOptions || {},\n };\n if (!isNil(contentPosition)) {\n baseTracking[trackingConstants.COMPONENT_CONTENT_POSITION] = contentPosition.toString();\n }\n\n if (!isNil(isAutomatedMarquee)) {\n baseTracking.additional![trackingConstants.MARQUEE_CONTENT_LOGIC] = isAutomatedMarquee ? 'default - automated' : 'default';\n }\n\n if (isInSheetView) {\n baseTracking.additional![trackingConstants.COMPONENT_MODULE_NAME] = trackingConstants.SEE_ALL_OVERLAY;\n baseTracking.sheetviewSuffix = `/${trackingConstants.SEE_ALL_OVERLAY}`;\n }\n\n return baseTracking;\n }\n}\n","import { Injectable } from '@angular/core';\n\nimport { hasValue } from '@frontend/sports/common/core/utils/extended-types';\nimport { trackingConstants } from '@frontend/sports/tracking/feature';\nimport { Widget } from '@frontend/sports/types/components/widget';\nimport { isEmpty, isNil } from 'lodash-es';\nimport { IPickTracking } from 'packages/sports/common/betslip/core/picks/pick-models';\n\nimport { PickSourceProvider } from '../../option-pick/pick-source.provider';\nimport { ModularConfigAccessorService } from './modular-config-accessor.service';\n\n@Injectable()\nexport class ModularPickSourceService extends PickSourceProvider {\n constructor(private configAccessor: ModularConfigAccessorService) {\n super();\n }\n\n override getTracking(): { [key: string]: string } {\n return this.getBaseTracking();\n }\n\n override get(\n source: string,\n contentPosition?: number,\n isFallbackMarketEnabled?: boolean,\n marqueeName?: string,\n sitecoreTemplateId?: string,\n marqueeType?: string,\n trackingOptions?: { [key: string]: string },\n isAutomatedMarquee?: boolean,\n ): IPickTracking {\n const config = this.configAccessor.getWidget();\n const page = this.configAccessor.getPage();\n\n if (!config) {\n return super.get(source);\n }\n\n const baseTracking = {\n source: this.buildPath(page, config.templateName, source).toLowerCase(),\n additional: this.getBaseTracking(config),\n };\n\n if (!isNil(contentPosition)) {\n baseTracking.additional[trackingConstants.COMPONENT_CONTENT_POSITION] = contentPosition.toString();\n }\n\n if (!isNil(isAutomatedMarquee)) {\n baseTracking.additional[trackingConstants.MARQUEE_CONTENT_LOGIC] = isAutomatedMarquee ? 'default - automated' : 'default';\n } else if (!isNil(isFallbackMarketEnabled)) {\n baseTracking.additional[trackingConstants.MARQUEE_CONTENT_LOGIC] = isFallbackMarketEnabled ? 'fallback' : 'default';\n }\n\n if (!isNil(marqueeName)) {\n baseTracking.additional[trackingConstants.MARQUEE_NAME] = marqueeName;\n }\n\n if (!isNil(sitecoreTemplateId)) {\n baseTracking.additional[trackingConstants.SITECORE_TEMPLATE_ID] = sitecoreTemplateId;\n }\n\n if (!isNil(marqueeType)) {\n baseTracking.additional[trackingConstants.MARQUEE_TYPE] = marqueeType;\n }\n\n return baseTracking;\n }\n\n private getBaseTracking(config?: Readonly>): { [key: string]: string } {\n config = config || this.configAccessor.getWidget();\n const parentWidget = this.configAccessor.getParentWidget();\n\n let baseTracking = {};\n\n if (config) {\n baseTracking = Object.assign(baseTracking, config.trackingData, {\n [trackingConstants.COMPONENT_MODULE_NAME]: config.type,\n [trackingConstants.COMPONENT_MODULE_POSITION]: `${config.location}|${config.order}`,\n [trackingConstants.COMPONENT_MODULE_CUSTOM_NAME]: config.templateName,\n [trackingConstants.COMPONENT_MODULE_SOURCE]: isNil(parentWidget) ? 'standard module' : `composable|${parentWidget.templateName}`,\n });\n }\n\n const layoutTemplate = this.configAccessor.getLayoutTemplate();\n const page = this.configAccessor.getPage();\n if (layoutTemplate) {\n baseTracking = Object.assign(baseTracking, {\n [trackingConstants.COMPONENT_PAGE_LAYOUT]: this.buildPath(layoutTemplate.folder, page, layoutTemplate.name),\n });\n }\n\n return !isEmpty(baseTracking) ? baseTracking : super.getTracking();\n }\n\n private buildPath(...params: unknown[]): string {\n return params.filter(hasValue).join('/');\n }\n}\n","import { Nullable } from '@frontend/sports/common/core/utils/extended-types';\nimport { Odds } from '@frontend/sports/odds/feature';\nimport { Decimal } from 'decimal.js';\nimport { NumpadAction } from 'packages/sports/web/app/src/numpad/model';\n\nimport { PickId } from '../../core/picks/pick-id';\nimport { SlipResult } from '../betplacement/models';\nimport { BetPlacementError } from '../validation/errors/bet-placement-error';\nimport { BetslipError } from '../validation/errors/betslip-error';\n\nexport enum QuickBetActionButtonState {\n Login = 'Login',\n MakeDeposit = 'MakeDeposit',\n Place = 'Place',\n PlaceFreeBet = 'PlaceFreeBet',\n Placing = 'Placing',\n PlacingFreeBet = 'PlacingFreeBet',\n ProcessingDeposit = 'ProcessingDeposit',\n ProcessingDepositAndBet = 'ProcessingDepositAndBet',\n AcceptChanges = 'AcceptChanges',\n AcceptAndPlace = 'AcceptAndPlace',\n AcceptAndPlaceFreeBet = 'AcceptAndPlaceFreeBet',\n Deposit = 'Deposit',\n DepositAndPlaceBet = 'DepositAndPlaceBet',\n UpdateMinStake = 'UpdateMinStake',\n}\n\nexport enum QuickBetUiState {\n None = 'None',\n Loading = 'Loading',\n Place = 'Place',\n Success = 'Success',\n}\n\nexport interface IQuickBetActionButtonState {\n isDisabled: boolean;\n isProcessing: boolean;\n state: QuickBetActionButtonState;\n hasTaxation: boolean;\n possibleWinnings: Nullable;\n winningsBoost: Nullable;\n riskFreeAmount: Nullable;\n taxationAmount: Nullable;\n taxationLiability: Nullable;\n possibleWinningsNet: Nullable;\n}\n\nexport interface IQuickBetErrorsStatus {\n isLocked: boolean;\n isClosed: boolean;\n oddsChanged: boolean;\n oddsAccepted: boolean;\n hasBetslipErrors: boolean;\n hasStakeUpdateErrors: boolean;\n}\n\nexport interface IQuickBetWinningsState {\n hasTaxation: boolean;\n possibleWinnings: Nullable;\n winningsBoost: Nullable;\n riskFreeAmount: Nullable;\n taxationAmount: Nullable;\n taxationLiability: Nullable;\n taxationRate: Nullable;\n possibleWinningsNet: Nullable;\n originalWinnings?: Nullable;\n}\n\nexport const defaultQuickBetActionButtonState: IQuickBetActionButtonState = {\n isDisabled: false,\n isProcessing: false,\n state: QuickBetActionButtonState.Login,\n hasTaxation: false,\n possibleWinnings: null,\n winningsBoost: null,\n riskFreeAmount: null,\n taxationAmount: null,\n taxationLiability: null,\n possibleWinningsNet: null,\n};\n\nexport interface IQuickBetStakeState {\n stake: Nullable; // numpad-keys.component work with strings\n quickStakeUsed: boolean;\n numpadAction?: NumpadAction;\n}\n\nexport const defaultQuickBetStakeState: IQuickBetStakeState = {\n stake: null,\n quickStakeUsed: false,\n};\n\nexport interface IQuickBetPlaceResultWarning {\n disableButton: boolean;\n error: BetPlacementError;\n}\n\nexport interface IQuickBetPlaceResultSummaryState {\n stake: number;\n numberOfBets: number;\n currency?: string;\n possibleWinningsGross: number;\n possibleWinningsNet: number;\n}\n\nexport interface IQuickBetPlaceResultState {\n betNumber: string;\n slipResults: SlipResult;\n isSuccessful: boolean;\n errors: BetslipError[];\n warnings: IQuickBetPlaceResultWarning[];\n placedNewCustomerOffer: boolean;\n summary: IQuickBetPlaceResultSummaryState;\n isEachWay: boolean;\n acceptedOdds: Odds | null;\n boostedOdds: Odds | null;\n}\n\nexport const defaultQuickBetPlaceResultState: IQuickBetPlaceResultState = {\n betNumber: '',\n slipResults: {},\n isSuccessful: false,\n errors: [],\n warnings: [],\n placedNewCustomerOffer: false,\n summary: {},\n isEachWay: false,\n acceptedOdds: null,\n boostedOdds: null,\n};\n\nexport interface IQuickBetHelpState {\n isVisible: boolean;\n wasShown: boolean;\n}\n\nexport const defaultQuickBetHelpState: IQuickBetHelpState = {\n isVisible: false,\n wasShown: false,\n};\n\nexport interface IQuickBetState {\n uiState: QuickBetUiState;\n isDisabled: boolean;\n keepOpen: boolean;\n pickIds: PickId[];\n oddsChangedOnBetPlacement: boolean;\n pickLockedOnBetPlacement: boolean;\n displayHelper: QuickBetDisplayHelper;\n hasClosedPick: boolean;\n hasLockedPick: boolean;\n helpState: IQuickBetHelpState;\n stakeState: IQuickBetStakeState;\n actionButtonState: IQuickBetActionButtonState;\n betPlacedResult: IQuickBetPlaceResultState;\n inOverlay: boolean;\n inEditBetMode: boolean;\n}\n\nexport interface QuickBetDisplayHelper {\n addedToBetslip: boolean;\n //hidden => QB can be reopend if the user correct the error from betbar\n hiddenByError: boolean;\n //the user did not take care of the error we are closing QB\n closedByError: boolean;\n}\n\nexport const defaultQuickBetDisplayHelper: QuickBetDisplayHelper = {\n addedToBetslip: false,\n closedByError: false,\n hiddenByError: false,\n};\n\nexport const defaultQuickBetState: IQuickBetState = {\n uiState: QuickBetUiState.None,\n isDisabled: false,\n keepOpen: false,\n pickIds: [],\n displayHelper: defaultQuickBetDisplayHelper,\n oddsChangedOnBetPlacement: false,\n pickLockedOnBetPlacement: false,\n hasClosedPick: false,\n hasLockedPick: false,\n helpState: defaultQuickBetHelpState,\n stakeState: defaultQuickBetStakeState,\n actionButtonState: defaultQuickBetActionButtonState,\n betPlacedResult: defaultQuickBetPlaceResultState,\n inOverlay: false,\n inEditBetMode: false,\n};\n\nexport interface QuickBetStore {\n helpShown: boolean;\n}\n\nexport interface IQuickBetPlaceResultStorageState {\n betNumber: string;\n isSuccessful: boolean;\n errors: BetslipError[];\n warnings: IQuickBetPlaceResultWarning[];\n placedNewCustomerOffer: boolean;\n summary: IQuickBetPlaceResultSummaryState;\n isEachWay: boolean;\n acceptedOdds: Odds | null;\n boostedOdds: Odds | null;\n}\n\nexport interface IQuickBetSaveState {\n pickIds: string[];\n helpState: IQuickBetHelpState;\n keepOpen: boolean;\n stakeState: IQuickBetStakeState;\n actionButtonState: IQuickBetActionButtonState;\n betPlacedResult: IQuickBetPlaceResultStorageState;\n}\n","import { createSelector } from '@ngrx/store';\nimport { pickBy } from 'lodash-es';\n\nimport { BetslipType } from '../../core/betslip-type';\nimport { PickId } from '../../core/picks/pick-id';\nimport { isBetBuilderPick } from '../../core/utils';\nimport { betslipPicksListSelector, selectBetslipFlattenedPicksList, selectHasEachWayPick } from '../picks/selectors';\nimport { filterTypePicks } from '../picks/services/linear-betslip-pick.utils';\nimport { RewardTokenContext } from '../reward-tokens/reward-tokens.model';\nimport { selectIsRewardTokensSelectorVisible, selectRewardTokens } from '../reward-tokens/selectors';\nimport { betslipTypeStateSelector, selectBetBuilderPicksCount } from '../types/selectors';\nimport {\n selectBetslipTypeErrorsFactory,\n selectComboContainerModuleErrors,\n selectHasComboPreventionForTypeFactory,\n selectSlipErrorsForTypeFactory,\n} from '../validation/selectors';\n\nexport const comboBetStateSelector = createSelector(betslipTypeStateSelector, (typeState) => typeState.comboBet);\n\nexport const selectComboBetIsEachWay = createSelector(comboBetStateSelector, (comboBet) => comboBet.isEachWay);\n\nexport const comboBetStateStakeSelector = createSelector(comboBetStateSelector, (comboBet) => ({\n actualStake: comboBet.actualStake,\n stake: comboBet.stake,\n}));\n\nexport const comboBetPicksSelector = createSelector(comboBetStateSelector, (s) => s.picks);\n\nexport const comboBetPickSelectorFactory = (pickId: PickId) =>\n createSelector(comboBetPicksSelector, (picks) => {\n return picks[pickId.toString()];\n });\n\nexport const selectComboBetActualStake = createSelector(comboBetStateSelector, (state) => state.actualStake);\nexport const selectComboBetStake = createSelector(comboBetStateSelector, (state) => state.stake);\n\nexport const comboBetSelectedPicksSelector = createSelector(comboBetPicksSelector, (picks) => pickBy(picks, (pick) => pick.isSelected));\n\nexport const selectComboBetRewardTokenId = createSelector(comboBetStateSelector, (state) => state.rewardTokenId);\n\nexport const selectComboBetRewardToken = createSelector(selectComboBetRewardTokenId, selectRewardTokens, (tokenId, tokens) => {\n return tokenId ? tokens[tokenId] : null;\n});\n\nexport const selectComboBetPicks = createSelector(comboBetPicksSelector, selectBetslipFlattenedPicksList, (comboBetPicks, picksList) => {\n return picksList.filter((pick) => comboBetPicks[pick.id.toString()]);\n});\n\nexport const selectComboBetEachWay = createSelector(comboBetStateSelector, (comboState) => comboState.isEachWay);\n\nexport const selectComboBetComponentState = createSelector(comboBetStateSelector, selectComboBetPicks, (comboBetState, pickList) => ({\n comboBetState,\n pickList,\n}));\n\nexport const selectHasBetBuilderModuleErrors = createSelector(\n selectSlipErrorsForTypeFactory(BetslipType.BetBuilder),\n selectBetslipTypeErrorsFactory(BetslipType.BetBuilder),\n selectHasComboPreventionForTypeFactory(BetslipType.BetBuilder),\n (slipErrors, typeErrors, hasComboPrevention) => slipErrors.length > 0 || typeErrors.length > 0 || hasComboPrevention,\n);\n\nexport const selectPlaceableComboBetPicks = createSelector(comboBetPicksSelector, betslipPicksListSelector, (comboPicks, picksList) =>\n filterTypePicks(comboPicks, picksList, { isSelected: true, isLocked: false }),\n);\n\nexport const selectPlaceableComboBetBuilderPicksCount = createSelector(\n selectPlaceableComboBetPicks,\n (placeablePicks) => placeablePicks.filter(isBetBuilderPick).length,\n);\n\nexport const selectMainComboContainerState = (tokenContext: RewardTokenContext) =>\n createSelector(\n comboBetPicksSelector,\n selectComboBetEachWay,\n selectBetBuilderPicksCount,\n selectPlaceableComboBetPicks,\n selectPlaceableComboBetBuilderPicksCount,\n selectHasEachWayPick,\n selectIsRewardTokensSelectorVisible(tokenContext),\n selectComboContainerModuleErrors,\n selectHasComboPreventionForTypeFactory(BetslipType.Combo),\n (\n comboPicks,\n comboEachWay,\n betBuilderPicksCount,\n placeablePicks,\n placeableBetBuilderPicksCount,\n hasEachWayPick,\n isRewardSelectorVisible,\n moduleErrors,\n hasComboPrevention,\n ) => ({\n comboPicks,\n comboEachWay,\n betBuilderPicksCount,\n placeablePicks,\n placeableBetBuilderPicksCount,\n hasEachWayPick,\n isRewardSelectorVisible,\n moduleErrors,\n hasComboPrevention,\n }),\n );\n","import { Type } from '@angular/core';\n\nimport { BetslipError } from './betslip-error';\n\nexport interface INotifyUserError {\n userHasBeenNotified: boolean;\n\n markAsSeen(): void;\n}\n\nexport declare class NotifyUserError extends BetslipError implements INotifyUserError {\n userHasBeenNotified: boolean;\n markAsSeen(): void;\n}\n\nexport type ErrorConstructor = new (...args: any[]) => T;\n\nexport function NotifyUserErrorMixin<\n TBaseError extends BetslipError,\n TBaseErrorType extends ErrorConstructor = ErrorConstructor,\n>(\n // eslint-disable-next-line @typescript-eslint/naming-convention, no-underscore-dangle, id-blacklist, id-match\n BaseErrorType: TBaseErrorType,\n): Type {\n // @ts-ignore Constructor type https://github.com/Microsoft/TypeScript/issues/16390\n return class extends BaseErrorType implements INotifyUserError {\n constructor(...args: any[]) {\n super(...args);\n this.userHasBeenNotified = false;\n }\n\n userHasBeenNotified: boolean;\n\n markAsSeen(): void {\n this.userHasBeenNotified = true;\n }\n };\n}\n","import { PlacementErrorType } from '@frontend/sports/types/betslip';\n\nimport { BetPlacementErrorIcon } from '../bet-placement-error-icon';\nimport { BetslipError } from '../betslip-error';\nimport { NotifyUserErrorMixin } from '../notify-user-error';\nimport { ResultError } from './result-error';\n\nexport class PickInvisible extends NotifyUserErrorMixin(ResultError) {\n constructor(pickId: string) {\n super(pickId);\n this.icon = BetPlacementErrorIcon.Warning;\n this.priority = -999;\n this.hasClientValidation = true;\n this.type = PlacementErrorType.OptionInvisible;\n }\n\n override equals(error: BetslipError): boolean {\n return error instanceof PickInvisible && error.pickId === this.pickId;\n }\n}\n","import { PickInvisible } from '../result/pick-invisible';\nimport { IPickPreCheckError, PreCheckErrorMixin } from './pre-check-error';\n\n/**\n * Raised when market/option/price visibility is false.\n */\nexport class PickLockedPreCheckError extends PreCheckErrorMixin(PickInvisible) implements IPickPreCheckError {\n constructor(pickId: string) {\n super(pickId);\n }\n}\n","import { PlacementErrorType } from '@frontend/sports/types/betslip';\nimport { createSelector } from '@ngrx/store';\n\nimport { BetslipType } from '../../core/betslip-type';\nimport { BetslipUnknownPick } from '../../core/picks/betslip-unknown-pick';\nimport { getLegsCount } from '../../core/utils';\nimport { comboBetPicksSelector, comboBetSelectedPicksSelector } from '../combo-bet/selectors';\nimport { betslipPicksListSelector, selectBetslipFlattenedPicksList } from '../picks/selectors';\nimport { singleBetSelectedPicksSelector } from '../single-bet/selectors';\nimport { betslipCurrentTypeSelector } from '../types/base/selectors';\nimport { MinSelectionsBetbuilderError } from '../validation/errors/general/min-selections-betbuilder-error';\nimport { PickLockedPreCheckError } from '../validation/errors/pre-check/pick-locked-pre-check-error';\nimport { selectBetslipErrors, selectCurrentPicksErrors, selectPickErrorsState } from '../validation/selectors';\nimport { isPickStatusChangeError } from '../validation/services/utils/betslip-errors-utils';\n\nexport const selectedComboPicksSelector = createSelector(betslipPicksListSelector, comboBetSelectedPicksSelector, (pickList, selectedPicks) =>\n pickList.filter(({ id }) => !!selectedPicks[id.toString()]),\n);\n\n//include unselected picks\nexport const comboPickListCountSelector = createSelector(betslipPicksListSelector, comboBetPicksSelector, (pickList, comboBetPicks) => {\n const comboBetPick = pickList.filter(({ id }) => !!comboBetPicks[id.toString()]);\n\n return getLegsCount(comboBetPick);\n});\n\nexport const selectSelectedSinglePicks = createSelector(selectBetslipFlattenedPicksList, singleBetSelectedPicksSelector, (pickList, selectedPicks) =>\n pickList.filter(({ id }) => !!selectedPicks[id.toString()]),\n);\n\nexport const hasPickLockedPreCheckErrorSelector = createSelector(selectedComboPicksSelector, selectPickErrorsState, (selectedPicks, pickErrors) =>\n selectedPicks.some((pick) => {\n const errors = Object.values(pickErrors).flatMap((e) => e[pick.id.toString()] ?? []);\n\n return errors.some((e) => e instanceof PickLockedPreCheckError);\n }),\n);\n\nexport const hasComboPreventionErrorSelector = createSelector(selectedComboPicksSelector, selectCurrentPicksErrors, (selectedPicks, pickErrors) =>\n selectedPicks.some((pick) => {\n const errors = pickErrors[pick.id.toString()] ?? [];\n\n return errors.some((e) => e.type === PlacementErrorType.ComboPrevention);\n }),\n);\n\nexport const pickStatusChangeErrorSelector = createSelector(selectedComboPicksSelector, selectPickErrorsState, (selectedPicks, pickErrors) =>\n selectedPicks.some((pick) => {\n const errors = Object.values(pickErrors).flatMap((e) => e[pick.id.toString()] ?? []);\n\n return errors.some((e) => isPickStatusChangeError(e));\n }),\n);\n\nexport const singlePickCountSelector = createSelector(selectSelectedSinglePicks, (selectedPicks) => {\n return getLegsCount(selectedPicks);\n});\n\nexport const comboPickCountSelector = createSelector(selectedComboPicksSelector, (selectedPicks) => {\n return getLegsCount(selectedPicks);\n});\n\nexport const hasMinSelectionsBetbuilderError = createSelector(selectBetslipErrors, (betslipError) =>\n betslipError.some((e) => e instanceof MinSelectionsBetbuilderError),\n);\n\nexport const selectedPicksByCurrentTypeSelector = createSelector(\n betslipCurrentTypeSelector,\n selectedComboPicksSelector,\n selectSelectedSinglePicks,\n (currentType, selectedComboPicks, selectedSinglePicks) => {\n return currentType === BetslipType.Combo\n ? selectedComboPicks.filter((pick) => !BetslipUnknownPick.isPick(pick))\n : selectedSinglePicks.filter((pick) => !BetslipUnknownPick.isPick(pick));\n },\n);\n","import { createSelector } from '@ngrx/store';\n\nimport { betslipSelector } from '../../base/store/selectors';\n\nexport const settingsSelector = createSelector(betslipSelector, (s) => s.settings);\nexport const settingsOddsAcceptanceSelector = createSelector(settingsSelector, (s) => s.oddsAcceptance);\nexport const settingsNotificationsSelector = createSelector(settingsSelector, (s) => s.notifications);\n","import { createSelector } from '@ngrx/store';\n\nimport { getStakedTypes } from '../betplacement/services/stake/linear-stake-utils';\nimport { selectRewardTokens } from '../reward-tokens/selectors';\nimport { singleBetPicksGeneralStakeSelector } from '../single-bet/selectors';\nimport { GetSummaryStake } from '../stake/utils';\nimport { betslipTypeStateSelector, selectIsSingleBetSelected } from '../types/selectors';\nimport { selectCurrentStakeError } from '../validation/selectors';\n\nexport const selectSummaryStake = createSelector(betslipTypeStateSelector, (types) => GetSummaryStake(types));\n\nexport const selectCurrentStake = createSelector(\n selectSummaryStake,\n selectIsSingleBetSelected,\n singleBetPicksGeneralStakeSelector,\n (summaryStake, isSingleBet, singleBetStake) => (isSingleBet ? (singleBetStake?.actualStake ?? null) : summaryStake),\n);\n\nexport const selectStakeValidationState = createSelector(selectCurrentStake, selectCurrentStakeError, (stake, stakeError) => ({\n stake,\n stakeError,\n}));\n\nexport const selectStakedBetslipTypes = createSelector(betslipTypeStateSelector, selectRewardTokens, (types, tokens) =>\n getStakedTypes(types, tokens),\n);\n","export enum BetslipActionButtonTracking {\n NotApplicable = 'not applicable',\n Betslip = 'Betslip',\n QuickBet = 'QuickBet',\n EditMyBet = 'Edit My Bet',\n PlaceBet = 'Place bet',\n PlaceFreeBet = 'Place free bet',\n AcceptChanges = 'Accept changes',\n AcceptAndPlace = 'Accept&place bet',\n AcceptAndPlaceFreeBet = 'Accept&place freebet',\n LoginToBet = 'Login to bet',\n MakeDeposit = 'Make a deposit',\n Deposit = 'Deposit',\n DepositAndPlace = 'Deposit&place bet',\n Confirm = 'Confirm',\n SaveChanges = 'Save changes',\n}\n\nexport enum BetslipLinearTracking {\n NotApplicable = 'not applicable',\n Betslip = 'Betslip',\n FullBetslip = 'full betslip',\n Click = 'click',\n LinearCheckbox = 'linear checkbox',\n LinearNoCheckbox = 'linear no checkbox',\n Tabbed = 'tabbed',\n Combo = 'combo bet',\n Single = 'single bet',\n System = 'system bet',\n Teaser = 'teaser bet',\n EditBet = 'edit bet',\n Load = 'load',\n Icon = 'Icon',\n BetBuilder = 'betbuilder bet',\n SuccessMessage = 'success message',\n AddPromo = 'add promo',\n PromotionsPopup = 'rewards popup/active promotions',\n Close = 'close',\n SettingsIcon = 'settings icon',\n Settings = 'betslip settings',\n SettingsPopup = 'settings popup',\n}\n\nexport enum LinearCtaTrackingType {\n CloseCta = 'close cta',\n DepositCta = 'deposit cta',\n}\n\nexport enum PickBetTrackingTypes {\n Single = 'single',\n Parlay = 'parlay',\n Sgp = 'sgp',\n SgpPlus = 'sgp+',\n Teaser = 'teaser',\n System = 'system',\n}\n\nexport enum InfoTrackingContext {\n ComboBet = 'combo bet',\n Sgp = 'sgp',\n SgpPlus = 'sgp+',\n SystemBet = 'system bet',\n TeaserBet = 'teaser bet',\n}\n\nexport enum LinearCheckboxTrackingState {\n Selected = 'selected',\n Unselected = 'unselected',\n}\n\nexport enum LinearModuleExpansionTrackingState {\n Expand = 'expand',\n Collapse = 'collapse',\n}\n\nexport const BetslipTrackingLocation = 'Betslip';\nexport const LinearBetslipTrackingLocation = 'linear';\nexport const QuickBetTrackingLocation = 'Quick Bet';\n","import { createSelector } from '@ngrx/store';\n\nimport { betslipBaseSelector, betslipSelector, selectIsLinearBetslip } from '../../base/store/selectors';\nimport { BetslipType } from '../../core/betslip-type';\nimport { BetslipBetBuilderPick } from '../../core/picks/betslip-bet-builder-pick';\nimport { BetBuilderPickId, PickId } from '../../core/picks/pick-id';\nimport { getLegsCount, isBetBuilderPickId } from '../../core/utils';\nimport { comboPickCountSelector, singlePickCountSelector } from '../betslip-bar/selectors';\nimport { betslipPicksListSelector } from '../picks/selectors';\nimport { CriteriaType } from '../reward-tokens/reward-tokens.model';\nimport { selectTokensStateContext } from '../reward-tokens/selectors';\nimport { getSelectedTokenAndEligibilityForContext, getSelectedTokenForContext } from '../reward-tokens/services/linear-reward-tokens.utils';\nimport { isFreebetToken } from '../reward-tokens/services/reward-tokens.utils';\nimport { settingsOddsAcceptanceSelector } from '../settings/selectors';\nimport { selectCurrentStake } from '../summary/selectors';\nimport { PickBetTrackingTypes } from '../tracking/models';\nimport { betslipCurrentTypeSelector } from '../types/base/selectors';\nimport { betslipTypeStateSelector, selectBetBuilderPicksState } from '../types/selectors';\nimport { GroupWithClosedLegsError } from '../validation/errors/general/group-pick-error';\nimport {\n selectAllBetBuilderErrors,\n selectAllCurrentBetslipErrors,\n selectBetslipTypeErrorsFactory,\n selectCurrentBetslipTypeErrors,\n selectCurrentStakeError,\n selectPickErrorsState,\n} from '../validation/selectors';\nimport {\n isGroupPickOfferChange,\n isPickStatusClosed,\n isPickStatusLocked,\n isPickStatusOddsChangedPreCheck,\n isStakeError,\n isUnderMinimumStakeErrorPreCheck,\n} from '../validation/services/utils/betslip-errors-utils';\nimport { QuickBetUiState } from './quick-bet.state';\n\nexport const quickBetStateSelector = createSelector(betslipSelector, (state) => state.quickBet);\nexport const quickBetUiStateSelector = createSelector(quickBetStateSelector, (state) => state.uiState);\nexport const actionButtonStateSelector = createSelector(quickBetStateSelector, (state) => state.actionButtonState);\nexport const betPlacedResultSelector = createSelector(quickBetStateSelector, (state) => state.betPlacedResult);\nexport const showQuickBetHelpSelector = createSelector(quickBetStateSelector, (state) => state.helpState);\nexport const selectQuickBetPickIds = createSelector(quickBetStateSelector, (state) => state.pickIds);\nexport const selectQuickBetStakeState = createSelector(quickBetStateSelector, (state) => state.stakeState);\n\nexport const selectLinearQuickBetBuilderState = createSelector(\n quickBetStateSelector,\n selectIsLinearBetslip,\n (state, isLinear): { isLinearQuickBetBuilder: true; pickId: PickId } | { isLinearQuickBetBuilder: false; pickId: undefined } => {\n if (state.pickIds.length === 1 && isBetBuilderPickId(state.pickIds[0]) && isLinear) {\n return {\n isLinearQuickBetBuilder: true,\n pickId: state.pickIds[0],\n };\n }\n\n return {\n isLinearQuickBetBuilder: false,\n pickId: undefined,\n };\n },\n);\n\nexport const selectQuickBetContext = createSelector(\n betslipBaseSelector,\n selectQuickBetPickIds,\n selectIsLinearBetslip,\n (\n betslipBaseState,\n quickBetPickIds,\n isLinear,\n ): { betslipType: BetslipType.Combo } | { betslipType: BetslipType.BetBuilder | BetslipType.Single; pickId: PickId } => {\n //The betbuilder pick can come from the BB drawer OR from the QB itself\n const betBuilderPickId = quickBetPickIds.length === 1 && isBetBuilderPickId(quickBetPickIds[0]) ? quickBetPickIds[0] : null;\n if (betBuilderPickId) {\n // if it's a bet builder pick, and we're in linear mode - all bet builder picks are stored in BetBuilderState\n if (isLinear) {\n return { betslipType: BetslipType.BetBuilder, pickId: betBuilderPickId };\n }\n\n // if it's a Sportcast pick and isSportcastAsCombo is false then it is stored in Single state\n if (BetBuilderPickId.isId(betBuilderPickId) && !betslipBaseState.isSportcastAsComboEnabled) {\n return { betslipType: BetslipType.Single, pickId: betBuilderPickId };\n }\n\n // otherwise(it's GroupPick OR Sportcast with isSportcastAsCombo==true) then we should check the Combo state\n return { betslipType: BetslipType.Combo };\n }\n\n // if it's a \"regular\" single pick, then check the Single state\n if (quickBetPickIds.length === 1) {\n return { betslipType: BetslipType.Single, pickId: quickBetPickIds[0] };\n }\n\n // otherwise(multiple picks), check Combo state\n return { betslipType: BetslipType.Combo };\n },\n);\n\nexport const selectQuickBetRewardToken = createSelector(selectTokensStateContext, selectQuickBetContext, ({ tokens, types }, context) => {\n return getSelectedTokenForContext(context, tokens, types);\n});\n\nexport const selectIsLinearQuickBetBuilder = createSelector(selectLinearQuickBetBuilderState, (state) => state.isLinearQuickBetBuilder);\n\nexport const selectAllQuickBetErrors = createSelector(\n selectIsLinearQuickBetBuilder,\n selectAllCurrentBetslipErrors,\n selectAllBetBuilderErrors,\n (isLinearQuickBetBuilder, currentBetslipErrors, betbuilderErrors) => {\n return isLinearQuickBetBuilder ? betbuilderErrors : currentBetslipErrors;\n },\n);\n\nexport const selectIsQuickBetSuccess = createSelector(quickBetUiStateSelector, (state) => state === QuickBetUiState.Success);\nexport const selectIsQuickBetInOverlay = createSelector(quickBetStateSelector, (state) => state.inOverlay);\n\nexport const selectQuickBetTypeErrors = createSelector(\n selectIsLinearQuickBetBuilder,\n selectCurrentBetslipTypeErrors,\n selectBetslipTypeErrorsFactory(BetslipType.BetBuilder),\n (isLinearQuickBetBuilder, currentTypeErrors, betBuilderTypeErrors) => (isLinearQuickBetBuilder ? betBuilderTypeErrors : currentTypeErrors),\n);\n\nexport const selectQuickBetStakeError = createSelector(\n selectIsLinearQuickBetBuilder,\n selectCurrentStakeError,\n selectAllBetBuilderErrors,\n (isLinearQuickBetBuilder, currentStakeError, betBuilderErrors) =>\n isLinearQuickBetBuilder ? betBuilderErrors.find(isStakeError) : currentStakeError,\n);\n\nexport const selectQuickBetStake = createSelector(\n selectLinearQuickBetBuilderState,\n selectCurrentStake,\n selectBetBuilderPicksState,\n (state, currentStake, betBuilderPickState) =>\n state.isLinearQuickBetBuilder ? (betBuilderPickState.picks[state.pickId!.toString()]?.actualStake ?? null) : currentStake,\n);\n\nexport const selectQuickBetStakeValidationState = createSelector(selectQuickBetStake, selectQuickBetStakeError, (stake, stakeError) => ({\n stake,\n stakeError,\n}));\n\nexport const selectQuickBetPicksErrors = createSelector(\n selectPickErrorsState,\n betslipCurrentTypeSelector,\n selectIsLinearQuickBetBuilder,\n (pickErrors, type, isLinearQuickBetBuilder) => {\n if (isLinearQuickBetBuilder) {\n return pickErrors[BetslipType.BetBuilder];\n }\n\n return type ? pickErrors[type] : {};\n },\n);\n\nexport const selectQuickBetErrorsState = createSelector(\n selectAllQuickBetErrors,\n selectQuickBetPicksErrors,\n quickBetStateSelector,\n (betslipErrors, pickErrors, quickBetState) => {\n const pickIds = quickBetState.pickIds;\n const quickBetPickErrors = pickIds.flatMap((pickId) => pickErrors[pickId.toString()] ?? []);\n const oddsChangedError = quickBetPickErrors.find(isPickStatusOddsChangedPreCheck);\n\n return {\n hasBetslipErrors: betslipErrors.length > 0,\n isLocked: quickBetPickErrors.some((error) => isPickStatusLocked(error) || isGroupPickOfferChange(error)),\n isClosed: quickBetPickErrors.some((error) => isPickStatusClosed(error) || error instanceof GroupWithClosedLegsError),\n oddsChanged: !!oddsChangedError,\n oddsAccepted: !!oddsChangedError?.userHasBeenNotified,\n hasStakeUpdateErrors: betslipErrors.some(isUnderMinimumStakeErrorPreCheck),\n };\n },\n);\n\nexport const selectHasQuickBetRewardTokensErrors = createSelector(\n selectTokensStateContext,\n selectQuickBetContext,\n ({ eligibilityState, tokens, types }, rewardTokenContext) => {\n const [_, eligibility] = getSelectedTokenAndEligibilityForContext(rewardTokenContext, eligibilityState, tokens, types);\n\n if (!eligibility) {\n return false;\n }\n\n return [CriteriaType.SlipType, CriteriaType.MinimumLegs, CriteriaType.MaximumStake].some(\n // This needs to be checked against the boolean literal, as using \"!\" would also return \"true\"\n // when the criteria type is undefined. We only want to return \"true\" if the type is defined and false\n (criteriaType) => eligibility.softCriteriasValidity[criteriaType] === false,\n );\n },\n);\n\nexport const selectQuickBetErrorsStateWithRewardsTokens = createSelector(\n selectQuickBetErrorsState,\n selectHasQuickBetRewardTokensErrors,\n (state, hasRewardTokensError) => ({\n ...state,\n hasBetslipErrors: state.hasBetslipErrors || hasRewardTokensError,\n }),\n);\n\nexport const selectClosedLockedPickOverview = createSelector(\n betslipTypeStateSelector,\n betslipPicksListSelector,\n selectQuickBetPicksErrors,\n (typeState, pickList, comboPickErrors) => ({\n typeState,\n pickList,\n comboPickErrors,\n }),\n);\n\nexport const selectQuickBetLockedState = createSelector(selectQuickBetErrorsState, (state) => state.isLocked);\nexport const selectQuickBetLockedOrClosedState = createSelector(selectQuickBetErrorsState, (state) => {\n return { isLocked: state.isLocked, isClosed: state.isClosed };\n});\n\nexport const selectQuickBetActionButtonProcessing = createSelector(actionButtonStateSelector, (state) => state.isProcessing);\n\nexport const selectQuickBetPickNotificationState = createSelector(\n quickBetStateSelector,\n betslipPicksListSelector,\n settingsOddsAcceptanceSelector,\n selectQuickBetErrorsState,\n (quickBet, picksList, oddsAcceptance, errorState) => ({ quickBet, picksList, oddsAcceptance, errorState }),\n);\n\nexport const selectQuickBetStakeErrorState = createSelector(selectQuickBetStakeState, selectAllQuickBetErrors, (stakeState, errors) => ({\n stakeState,\n errors,\n}));\n\nexport const selectQuickBetPicks = createSelector(betslipPicksListSelector, selectQuickBetPickIds, (pickList, quickBetPickIds) => {\n const stringPickIds = quickBetPickIds.map((id) => id.toString());\n\n return pickList.filter((pick) => stringPickIds.includes(pick.id.toString()));\n});\n\nexport const selectQuickBetBetBuilderPicks = createSelector(selectQuickBetPickIds, betslipPicksListSelector, (quickBetPickIds, pickList) => {\n const betBuilderPickIds = [...quickBetPickIds].filter(BetBuilderPickId.isId).map((id) => id.toString());\n\n return pickList.filter((pick) => betBuilderPickIds.includes(pick.id.toString())) as BetslipBetBuilderPick[];\n});\n\nexport const selectQuickBetBetBuilderPicksCount = createSelector(selectQuickBetBetBuilderPicks, (betBuilderPicks) => betBuilderPicks.length);\n\nexport const selectQuickBetDisplayHelper = createSelector(quickBetStateSelector, (quickBetState) => quickBetState.displayHelper);\n\nexport const selectQuickBetLegsCount = createSelector(selectQuickBetPicks, (quickBetPicks) => getLegsCount(quickBetPicks));\n\nexport const selectedPickCountByCurrentType = createSelector(\n betslipCurrentTypeSelector,\n comboPickCountSelector,\n singlePickCountSelector,\n (currentType, comboCount, singleCount) => {\n if (currentType === BetslipType.Single) {\n return singleCount;\n }\n\n return comboCount;\n },\n);\n\nexport const selectQuickBetTrackingType = createSelector(betslipBaseSelector, selectQuickBetPickIds, (betslipBaseState, quickBetPickIds) => {\n const betBuilderPickId = getBetBuilderId(quickBetPickIds);\n if (betBuilderPickId) {\n if (BetBuilderPickId.isId(betBuilderPickId) && !betslipBaseState.isSportcastAsComboEnabled) {\n return PickBetTrackingTypes.Single;\n }\n\n return PickBetTrackingTypes.Sgp;\n }\n\n if (quickBetPickIds.length === 1) {\n return isBetBuilderPickId(quickBetPickIds[0]) ? PickBetTrackingTypes.Sgp : PickBetTrackingTypes.Single;\n }\n\n return quickBetPickIds.some(isBetBuilderPickId) ? PickBetTrackingTypes.SgpPlus : PickBetTrackingTypes.Parlay;\n});\n\nexport const selectTrackingDetails = createSelector(selectQuickBetTrackingType, selectedPickCountByCurrentType, (trackingType, pickCount) => {\n return {\n mode: trackingType,\n pickCount,\n };\n});\n\nexport const selectIsQuickBetActive = createSelector(quickBetUiStateSelector, (state) => state !== QuickBetUiState.None);\n\nexport const selectQuickBetPickIdsOrBetbuilderId = createSelector(selectQuickBetPickIds, (pickIds) => {\n return [...pickIds];\n});\n\nfunction getBetBuilderId(quickBetPickIds: PickId[]) {\n if (quickBetPickIds.length === 1 && isBetBuilderPickId(quickBetPickIds[0])) {\n return quickBetPickIds[0];\n }\n\n return null;\n}\n\nexport const selectQuickBetFreebetToken = createSelector(selectQuickBetRewardToken, (tokenInfo) => (isFreebetToken(tokenInfo) ? tokenInfo : null));\n\nexport const selectIsQuickBetOpen = createSelector(\n quickBetUiStateSelector,\n selectQuickBetDisplayHelper,\n (uiState, displayHelper) => uiState !== QuickBetUiState.None && !displayHelper?.hiddenByError,\n);\n\nexport const selectQuickBetPicksWithErrors = createSelector(selectQuickBetPicks, selectQuickBetPicksErrors, (pickList, pickErrors) =>\n pickList.map((pick) => ({\n pick,\n errors: pickErrors[pick.id.toString()] || [],\n })),\n);\nexport const selectQuickBetUnderMinStakeError = createSelector(\n selectAllQuickBetErrors,\n (betslipErrors) => betslipErrors.find(isUnderMinimumStakeErrorPreCheck) ?? null,\n);\n","import { DOCUMENT } from '@angular/common';\nimport { Injectable, Signal, inject } from '@angular/core';\n\nimport { ScrollContainerService } from '@frontend/sports/common/core/utils/scroll-container';\nimport { Store } from '@ngrx/store';\nimport { selectIsQuickBetSuccess } from 'packages/sports/common/betslip/modules/quick-bet/quick-bet.selectors';\nimport { IQuickBetState } from 'packages/sports/common/betslip/modules/quick-bet/quick-bet.state';\n\n@Injectable({\n providedIn: 'root',\n})\nexport class HideHeaderService {\n scrollLastPosition = 0;\n private quickBetUiState: Signal;\n\n private readonly _doc = inject(DOCUMENT);\n\n constructor(\n private scrollContainer: ScrollContainerService,\n private store: Store,\n ) {\n this.quickBetUiState = this.store.selectSignal(selectIsQuickBetSuccess);\n }\n\n onScroll() {\n if (this.quickBetUiState()) {\n return;\n }\n\n if (this.scrollContainer.scrollTop > 5 && this.scrollContainer.scrollTop >= this.scrollLastPosition) {\n const headerElement = this._doc.querySelector('.slot-header_bottom_items') as HTMLElement;\n const scrollHeight = headerElement.offsetTop;\n const scrollTop = (Math.abs(scrollHeight) * -1).toString();\n this._doc.querySelector('.slot-header')?.setAttribute('style', `top:${scrollTop}px`);\n } else {\n this._doc.querySelector('.slot-header')?.removeAttribute('style');\n }\n this.scrollLastPosition = this.scrollContainer.scrollTop;\n }\n\n removeHideHeader() {\n this._doc.querySelector('.slot-header')?.removeAttribute('style');\n }\n}\n","/**\r\n * A collection of shims that provide minimal functionality of the ES6 collections.\r\n *\r\n * These implementations are not meant to be used outside of the ResizeObserver\r\n * modules as they cover only a limited range of use cases.\r\n */\n/* eslint-disable require-jsdoc, valid-jsdoc */\nvar MapShim = function () {\n if (typeof Map !== 'undefined') {\n return Map;\n }\n /**\r\n * Returns index in provided array that matches the specified key.\r\n *\r\n * @param {Array} arr\r\n * @param {*} key\r\n * @returns {number}\r\n */\n function getIndex(arr, key) {\n var result = -1;\n arr.some(function (entry, index) {\n if (entry[0] === key) {\n result = index;\n return true;\n }\n return false;\n });\n return result;\n }\n return /** @class */function () {\n function class_1() {\n this.__entries__ = [];\n }\n Object.defineProperty(class_1.prototype, \"size\", {\n /**\r\n * @returns {boolean}\r\n */\n get: function () {\n return this.__entries__.length;\n },\n enumerable: true,\n configurable: true\n });\n /**\r\n * @param {*} key\r\n * @returns {*}\r\n */\n class_1.prototype.get = function (key) {\n var index = getIndex(this.__entries__, key);\n var entry = this.__entries__[index];\n return entry && entry[1];\n };\n /**\r\n * @param {*} key\r\n * @param {*} value\r\n * @returns {void}\r\n */\n class_1.prototype.set = function (key, value) {\n var index = getIndex(this.__entries__, key);\n if (~index) {\n this.__entries__[index][1] = value;\n } else {\n this.__entries__.push([key, value]);\n }\n };\n /**\r\n * @param {*} key\r\n * @returns {void}\r\n */\n class_1.prototype.delete = function (key) {\n var entries = this.__entries__;\n var index = getIndex(entries, key);\n if (~index) {\n entries.splice(index, 1);\n }\n };\n /**\r\n * @param {*} key\r\n * @returns {void}\r\n */\n class_1.prototype.has = function (key) {\n return !!~getIndex(this.__entries__, key);\n };\n /**\r\n * @returns {void}\r\n */\n class_1.prototype.clear = function () {\n this.__entries__.splice(0);\n };\n /**\r\n * @param {Function} callback\r\n * @param {*} [ctx=null]\r\n * @returns {void}\r\n */\n class_1.prototype.forEach = function (callback, ctx) {\n if (ctx === void 0) {\n ctx = null;\n }\n for (var _i = 0, _a = this.__entries__; _i < _a.length; _i++) {\n var entry = _a[_i];\n callback.call(ctx, entry[1], entry[0]);\n }\n };\n return class_1;\n }();\n}();\n\n/**\r\n * Detects whether window and document objects are available in current environment.\r\n */\nvar isBrowser = typeof window !== 'undefined' && typeof document !== 'undefined' && window.document === document;\n\n// Returns global object of a current environment.\nvar global$1 = function () {\n if (typeof global !== 'undefined' && global.Math === Math) {\n return global;\n }\n if (typeof self !== 'undefined' && self.Math === Math) {\n return self;\n }\n if (typeof window !== 'undefined' && window.Math === Math) {\n return window;\n }\n // eslint-disable-next-line no-new-func\n return Function('return this')();\n}();\n\n/**\r\n * A shim for the requestAnimationFrame which falls back to the setTimeout if\r\n * first one is not supported.\r\n *\r\n * @returns {number} Requests' identifier.\r\n */\nvar requestAnimationFrame$1 = function () {\n if (typeof requestAnimationFrame === 'function') {\n // It's required to use a bounded function because IE sometimes throws\n // an \"Invalid calling object\" error if rAF is invoked without the global\n // object on the left hand side.\n return requestAnimationFrame.bind(global$1);\n }\n return function (callback) {\n return setTimeout(function () {\n return callback(Date.now());\n }, 1000 / 60);\n };\n}();\n\n// Defines minimum timeout before adding a trailing call.\nvar trailingTimeout = 2;\n/**\r\n * Creates a wrapper function which ensures that provided callback will be\r\n * invoked only once during the specified delay period.\r\n *\r\n * @param {Function} callback - Function to be invoked after the delay period.\r\n * @param {number} delay - Delay after which to invoke callback.\r\n * @returns {Function}\r\n */\nfunction throttle(callback, delay) {\n var leadingCall = false,\n trailingCall = false,\n lastCallTime = 0;\n /**\r\n * Invokes the original callback function and schedules new invocation if\r\n * the \"proxy\" was called during current request.\r\n *\r\n * @returns {void}\r\n */\n function resolvePending() {\n if (leadingCall) {\n leadingCall = false;\n callback();\n }\n if (trailingCall) {\n proxy();\n }\n }\n /**\r\n * Callback invoked after the specified delay. It will further postpone\r\n * invocation of the original function delegating it to the\r\n * requestAnimationFrame.\r\n *\r\n * @returns {void}\r\n */\n function timeoutCallback() {\n requestAnimationFrame$1(resolvePending);\n }\n /**\r\n * Schedules invocation of the original function.\r\n *\r\n * @returns {void}\r\n */\n function proxy() {\n var timeStamp = Date.now();\n if (leadingCall) {\n // Reject immediately following calls.\n if (timeStamp - lastCallTime < trailingTimeout) {\n return;\n }\n // Schedule new call to be in invoked when the pending one is resolved.\n // This is important for \"transitions\" which never actually start\n // immediately so there is a chance that we might miss one if change\n // happens amids the pending invocation.\n trailingCall = true;\n } else {\n leadingCall = true;\n trailingCall = false;\n setTimeout(timeoutCallback, delay);\n }\n lastCallTime = timeStamp;\n }\n return proxy;\n}\n\n// Minimum delay before invoking the update of observers.\nvar REFRESH_DELAY = 20;\n// A list of substrings of CSS properties used to find transition events that\n// might affect dimensions of observed elements.\nvar transitionKeys = ['top', 'right', 'bottom', 'left', 'width', 'height', 'size', 'weight'];\n// Check if MutationObserver is available.\nvar mutationObserverSupported = typeof MutationObserver !== 'undefined';\n/**\r\n * Singleton controller class which handles updates of ResizeObserver instances.\r\n */\nvar ResizeObserverController = /** @class */function () {\n /**\r\n * Creates a new instance of ResizeObserverController.\r\n *\r\n * @private\r\n */\n function ResizeObserverController() {\n /**\r\n * Indicates whether DOM listeners have been added.\r\n *\r\n * @private {boolean}\r\n */\n this.connected_ = false;\n /**\r\n * Tells that controller has subscribed for Mutation Events.\r\n *\r\n * @private {boolean}\r\n */\n this.mutationEventsAdded_ = false;\n /**\r\n * Keeps reference to the instance of MutationObserver.\r\n *\r\n * @private {MutationObserver}\r\n */\n this.mutationsObserver_ = null;\n /**\r\n * A list of connected observers.\r\n *\r\n * @private {Array}\r\n */\n this.observers_ = [];\n this.onTransitionEnd_ = this.onTransitionEnd_.bind(this);\n this.refresh = throttle(this.refresh.bind(this), REFRESH_DELAY);\n }\n /**\r\n * Adds observer to observers list.\r\n *\r\n * @param {ResizeObserverSPI} observer - Observer to be added.\r\n * @returns {void}\r\n */\n ResizeObserverController.prototype.addObserver = function (observer) {\n if (!~this.observers_.indexOf(observer)) {\n this.observers_.push(observer);\n }\n // Add listeners if they haven't been added yet.\n if (!this.connected_) {\n this.connect_();\n }\n };\n /**\r\n * Removes observer from observers list.\r\n *\r\n * @param {ResizeObserverSPI} observer - Observer to be removed.\r\n * @returns {void}\r\n */\n ResizeObserverController.prototype.removeObserver = function (observer) {\n var observers = this.observers_;\n var index = observers.indexOf(observer);\n // Remove observer if it's present in registry.\n if (~index) {\n observers.splice(index, 1);\n }\n // Remove listeners if controller has no connected observers.\n if (!observers.length && this.connected_) {\n this.disconnect_();\n }\n };\n /**\r\n * Invokes the update of observers. It will continue running updates insofar\r\n * it detects changes.\r\n *\r\n * @returns {void}\r\n */\n ResizeObserverController.prototype.refresh = function () {\n var changesDetected = this.updateObservers_();\n // Continue running updates if changes have been detected as there might\n // be future ones caused by CSS transitions.\n if (changesDetected) {\n this.refresh();\n }\n };\n /**\r\n * Updates every observer from observers list and notifies them of queued\r\n * entries.\r\n *\r\n * @private\r\n * @returns {boolean} Returns \"true\" if any observer has detected changes in\r\n * dimensions of it's elements.\r\n */\n ResizeObserverController.prototype.updateObservers_ = function () {\n // Collect observers that have active observations.\n var activeObservers = this.observers_.filter(function (observer) {\n return observer.gatherActive(), observer.hasActive();\n });\n // Deliver notifications in a separate cycle in order to avoid any\n // collisions between observers, e.g. when multiple instances of\n // ResizeObserver are tracking the same element and the callback of one\n // of them changes content dimensions of the observed target. Sometimes\n // this may result in notifications being blocked for the rest of observers.\n activeObservers.forEach(function (observer) {\n return observer.broadcastActive();\n });\n return activeObservers.length > 0;\n };\n /**\r\n * Initializes DOM listeners.\r\n *\r\n * @private\r\n * @returns {void}\r\n */\n ResizeObserverController.prototype.connect_ = function () {\n // Do nothing if running in a non-browser environment or if listeners\n // have been already added.\n if (!isBrowser || this.connected_) {\n return;\n }\n // Subscription to the \"Transitionend\" event is used as a workaround for\n // delayed transitions. This way it's possible to capture at least the\n // final state of an element.\n document.addEventListener('transitionend', this.onTransitionEnd_);\n window.addEventListener('resize', this.refresh);\n if (mutationObserverSupported) {\n this.mutationsObserver_ = new MutationObserver(this.refresh);\n this.mutationsObserver_.observe(document, {\n attributes: true,\n childList: true,\n characterData: true,\n subtree: true\n });\n } else {\n document.addEventListener('DOMSubtreeModified', this.refresh);\n this.mutationEventsAdded_ = true;\n }\n this.connected_ = true;\n };\n /**\r\n * Removes DOM listeners.\r\n *\r\n * @private\r\n * @returns {void}\r\n */\n ResizeObserverController.prototype.disconnect_ = function () {\n // Do nothing if running in a non-browser environment or if listeners\n // have been already removed.\n if (!isBrowser || !this.connected_) {\n return;\n }\n document.removeEventListener('transitionend', this.onTransitionEnd_);\n window.removeEventListener('resize', this.refresh);\n if (this.mutationsObserver_) {\n this.mutationsObserver_.disconnect();\n }\n if (this.mutationEventsAdded_) {\n document.removeEventListener('DOMSubtreeModified', this.refresh);\n }\n this.mutationsObserver_ = null;\n this.mutationEventsAdded_ = false;\n this.connected_ = false;\n };\n /**\r\n * \"Transitionend\" event handler.\r\n *\r\n * @private\r\n * @param {TransitionEvent} event\r\n * @returns {void}\r\n */\n ResizeObserverController.prototype.onTransitionEnd_ = function (_a) {\n var _b = _a.propertyName,\n propertyName = _b === void 0 ? '' : _b;\n // Detect whether transition may affect dimensions of an element.\n var isReflowProperty = transitionKeys.some(function (key) {\n return !!~propertyName.indexOf(key);\n });\n if (isReflowProperty) {\n this.refresh();\n }\n };\n /**\r\n * Returns instance of the ResizeObserverController.\r\n *\r\n * @returns {ResizeObserverController}\r\n */\n ResizeObserverController.getInstance = function () {\n if (!this.instance_) {\n this.instance_ = new ResizeObserverController();\n }\n return this.instance_;\n };\n /**\r\n * Holds reference to the controller's instance.\r\n *\r\n * @private {ResizeObserverController}\r\n */\n ResizeObserverController.instance_ = null;\n return ResizeObserverController;\n}();\n\n/**\r\n * Defines non-writable/enumerable properties of the provided target object.\r\n *\r\n * @param {Object} target - Object for which to define properties.\r\n * @param {Object} props - Properties to be defined.\r\n * @returns {Object} Target object.\r\n */\nvar defineConfigurable = function (target, props) {\n for (var _i = 0, _a = Object.keys(props); _i < _a.length; _i++) {\n var key = _a[_i];\n Object.defineProperty(target, key, {\n value: props[key],\n enumerable: false,\n writable: false,\n configurable: true\n });\n }\n return target;\n};\n\n/**\r\n * Returns the global object associated with provided element.\r\n *\r\n * @param {Object} target\r\n * @returns {Object}\r\n */\nvar getWindowOf = function (target) {\n // Assume that the element is an instance of Node, which means that it\n // has the \"ownerDocument\" property from which we can retrieve a\n // corresponding global object.\n var ownerGlobal = target && target.ownerDocument && target.ownerDocument.defaultView;\n // Return the local global object if it's not possible extract one from\n // provided element.\n return ownerGlobal || global$1;\n};\n\n// Placeholder of an empty content rectangle.\nvar emptyRect = createRectInit(0, 0, 0, 0);\n/**\r\n * Converts provided string to a number.\r\n *\r\n * @param {number|string} value\r\n * @returns {number}\r\n */\nfunction toFloat(value) {\n return parseFloat(value) || 0;\n}\n/**\r\n * Extracts borders size from provided styles.\r\n *\r\n * @param {CSSStyleDeclaration} styles\r\n * @param {...string} positions - Borders positions (top, right, ...)\r\n * @returns {number}\r\n */\nfunction getBordersSize(styles) {\n var positions = [];\n for (var _i = 1; _i < arguments.length; _i++) {\n positions[_i - 1] = arguments[_i];\n }\n return positions.reduce(function (size, position) {\n var value = styles['border-' + position + '-width'];\n return size + toFloat(value);\n }, 0);\n}\n/**\r\n * Extracts paddings sizes from provided styles.\r\n *\r\n * @param {CSSStyleDeclaration} styles\r\n * @returns {Object} Paddings box.\r\n */\nfunction getPaddings(styles) {\n var positions = ['top', 'right', 'bottom', 'left'];\n var paddings = {};\n for (var _i = 0, positions_1 = positions; _i < positions_1.length; _i++) {\n var position = positions_1[_i];\n var value = styles['padding-' + position];\n paddings[position] = toFloat(value);\n }\n return paddings;\n}\n/**\r\n * Calculates content rectangle of provided SVG element.\r\n *\r\n * @param {SVGGraphicsElement} target - Element content rectangle of which needs\r\n * to be calculated.\r\n * @returns {DOMRectInit}\r\n */\nfunction getSVGContentRect(target) {\n var bbox = target.getBBox();\n return createRectInit(0, 0, bbox.width, bbox.height);\n}\n/**\r\n * Calculates content rectangle of provided HTMLElement.\r\n *\r\n * @param {HTMLElement} target - Element for which to calculate the content rectangle.\r\n * @returns {DOMRectInit}\r\n */\nfunction getHTMLElementContentRect(target) {\n // Client width & height properties can't be\n // used exclusively as they provide rounded values.\n var clientWidth = target.clientWidth,\n clientHeight = target.clientHeight;\n // By this condition we can catch all non-replaced inline, hidden and\n // detached elements. Though elements with width & height properties less\n // than 0.5 will be discarded as well.\n //\n // Without it we would need to implement separate methods for each of\n // those cases and it's not possible to perform a precise and performance\n // effective test for hidden elements. E.g. even jQuery's ':visible' filter\n // gives wrong results for elements with width & height less than 0.5.\n if (!clientWidth && !clientHeight) {\n return emptyRect;\n }\n var styles = getWindowOf(target).getComputedStyle(target);\n var paddings = getPaddings(styles);\n var horizPad = paddings.left + paddings.right;\n var vertPad = paddings.top + paddings.bottom;\n // Computed styles of width & height are being used because they are the\n // only dimensions available to JS that contain non-rounded values. It could\n // be possible to utilize the getBoundingClientRect if only it's data wasn't\n // affected by CSS transformations let alone paddings, borders and scroll bars.\n var width = toFloat(styles.width),\n height = toFloat(styles.height);\n // Width & height include paddings and borders when the 'border-box' box\n // model is applied (except for IE).\n if (styles.boxSizing === 'border-box') {\n // Following conditions are required to handle Internet Explorer which\n // doesn't include paddings and borders to computed CSS dimensions.\n //\n // We can say that if CSS dimensions + paddings are equal to the \"client\"\n // properties then it's either IE, and thus we don't need to subtract\n // anything, or an element merely doesn't have paddings/borders styles.\n if (Math.round(width + horizPad) !== clientWidth) {\n width -= getBordersSize(styles, 'left', 'right') + horizPad;\n }\n if (Math.round(height + vertPad) !== clientHeight) {\n height -= getBordersSize(styles, 'top', 'bottom') + vertPad;\n }\n }\n // Following steps can't be applied to the document's root element as its\n // client[Width/Height] properties represent viewport area of the window.\n // Besides, it's as well not necessary as the itself neither has\n // rendered scroll bars nor it can be clipped.\n if (!isDocumentElement(target)) {\n // In some browsers (only in Firefox, actually) CSS width & height\n // include scroll bars size which can be removed at this step as scroll\n // bars are the only difference between rounded dimensions + paddings\n // and \"client\" properties, though that is not always true in Chrome.\n var vertScrollbar = Math.round(width + horizPad) - clientWidth;\n var horizScrollbar = Math.round(height + vertPad) - clientHeight;\n // Chrome has a rather weird rounding of \"client\" properties.\n // E.g. for an element with content width of 314.2px it sometimes gives\n // the client width of 315px and for the width of 314.7px it may give\n // 314px. And it doesn't happen all the time. So just ignore this delta\n // as a non-relevant.\n if (Math.abs(vertScrollbar) !== 1) {\n width -= vertScrollbar;\n }\n if (Math.abs(horizScrollbar) !== 1) {\n height -= horizScrollbar;\n }\n }\n return createRectInit(paddings.left, paddings.top, width, height);\n}\n/**\r\n * Checks whether provided element is an instance of the SVGGraphicsElement.\r\n *\r\n * @param {Element} target - Element to be checked.\r\n * @returns {boolean}\r\n */\nvar isSVGGraphicsElement = function () {\n // Some browsers, namely IE and Edge, don't have the SVGGraphicsElement\n // interface.\n if (typeof SVGGraphicsElement !== 'undefined') {\n return function (target) {\n return target instanceof getWindowOf(target).SVGGraphicsElement;\n };\n }\n // If it's so, then check that element is at least an instance of the\n // SVGElement and that it has the \"getBBox\" method.\n // eslint-disable-next-line no-extra-parens\n return function (target) {\n return target instanceof getWindowOf(target).SVGElement && typeof target.getBBox === 'function';\n };\n}();\n/**\r\n * Checks whether provided element is a document element ().\r\n *\r\n * @param {Element} target - Element to be checked.\r\n * @returns {boolean}\r\n */\nfunction isDocumentElement(target) {\n return target === getWindowOf(target).document.documentElement;\n}\n/**\r\n * Calculates an appropriate content rectangle for provided html or svg element.\r\n *\r\n * @param {Element} target - Element content rectangle of which needs to be calculated.\r\n * @returns {DOMRectInit}\r\n */\nfunction getContentRect(target) {\n if (!isBrowser) {\n return emptyRect;\n }\n if (isSVGGraphicsElement(target)) {\n return getSVGContentRect(target);\n }\n return getHTMLElementContentRect(target);\n}\n/**\r\n * Creates rectangle with an interface of the DOMRectReadOnly.\r\n * Spec: https://drafts.fxtf.org/geometry/#domrectreadonly\r\n *\r\n * @param {DOMRectInit} rectInit - Object with rectangle's x/y coordinates and dimensions.\r\n * @returns {DOMRectReadOnly}\r\n */\nfunction createReadOnlyRect(_a) {\n var x = _a.x,\n y = _a.y,\n width = _a.width,\n height = _a.height;\n // If DOMRectReadOnly is available use it as a prototype for the rectangle.\n var Constr = typeof DOMRectReadOnly !== 'undefined' ? DOMRectReadOnly : Object;\n var rect = Object.create(Constr.prototype);\n // Rectangle's properties are not writable and non-enumerable.\n defineConfigurable(rect, {\n x: x,\n y: y,\n width: width,\n height: height,\n top: y,\n right: x + width,\n bottom: height + y,\n left: x\n });\n return rect;\n}\n/**\r\n * Creates DOMRectInit object based on the provided dimensions and the x/y coordinates.\r\n * Spec: https://drafts.fxtf.org/geometry/#dictdef-domrectinit\r\n *\r\n * @param {number} x - X coordinate.\r\n * @param {number} y - Y coordinate.\r\n * @param {number} width - Rectangle's width.\r\n * @param {number} height - Rectangle's height.\r\n * @returns {DOMRectInit}\r\n */\nfunction createRectInit(x, y, width, height) {\n return {\n x: x,\n y: y,\n width: width,\n height: height\n };\n}\n\n/**\r\n * Class that is responsible for computations of the content rectangle of\r\n * provided DOM element and for keeping track of it's changes.\r\n */\nvar ResizeObservation = /** @class */function () {\n /**\r\n * Creates an instance of ResizeObservation.\r\n *\r\n * @param {Element} target - Element to be observed.\r\n */\n function ResizeObservation(target) {\n /**\r\n * Broadcasted width of content rectangle.\r\n *\r\n * @type {number}\r\n */\n this.broadcastWidth = 0;\n /**\r\n * Broadcasted height of content rectangle.\r\n *\r\n * @type {number}\r\n */\n this.broadcastHeight = 0;\n /**\r\n * Reference to the last observed content rectangle.\r\n *\r\n * @private {DOMRectInit}\r\n */\n this.contentRect_ = createRectInit(0, 0, 0, 0);\n this.target = target;\n }\n /**\r\n * Updates content rectangle and tells whether it's width or height properties\r\n * have changed since the last broadcast.\r\n *\r\n * @returns {boolean}\r\n */\n ResizeObservation.prototype.isActive = function () {\n var rect = getContentRect(this.target);\n this.contentRect_ = rect;\n return rect.width !== this.broadcastWidth || rect.height !== this.broadcastHeight;\n };\n /**\r\n * Updates 'broadcastWidth' and 'broadcastHeight' properties with a data\r\n * from the corresponding properties of the last observed content rectangle.\r\n *\r\n * @returns {DOMRectInit} Last observed content rectangle.\r\n */\n ResizeObservation.prototype.broadcastRect = function () {\n var rect = this.contentRect_;\n this.broadcastWidth = rect.width;\n this.broadcastHeight = rect.height;\n return rect;\n };\n return ResizeObservation;\n}();\nvar ResizeObserverEntry = /** @class */function () {\n /**\r\n * Creates an instance of ResizeObserverEntry.\r\n *\r\n * @param {Element} target - Element that is being observed.\r\n * @param {DOMRectInit} rectInit - Data of the element's content rectangle.\r\n */\n function ResizeObserverEntry(target, rectInit) {\n var contentRect = createReadOnlyRect(rectInit);\n // According to the specification following properties are not writable\n // and are also not enumerable in the native implementation.\n //\n // Property accessors are not being used as they'd require to define a\n // private WeakMap storage which may cause memory leaks in browsers that\n // don't support this type of collections.\n defineConfigurable(this, {\n target: target,\n contentRect: contentRect\n });\n }\n return ResizeObserverEntry;\n}();\nvar ResizeObserverSPI = /** @class */function () {\n /**\r\n * Creates a new instance of ResizeObserver.\r\n *\r\n * @param {ResizeObserverCallback} callback - Callback function that is invoked\r\n * when one of the observed elements changes it's content dimensions.\r\n * @param {ResizeObserverController} controller - Controller instance which\r\n * is responsible for the updates of observer.\r\n * @param {ResizeObserver} callbackCtx - Reference to the public\r\n * ResizeObserver instance which will be passed to callback function.\r\n */\n function ResizeObserverSPI(callback, controller, callbackCtx) {\n /**\r\n * Collection of resize observations that have detected changes in dimensions\r\n * of elements.\r\n *\r\n * @private {Array}\r\n */\n this.activeObservations_ = [];\n /**\r\n * Registry of the ResizeObservation instances.\r\n *\r\n * @private {Map}\r\n */\n this.observations_ = new MapShim();\n if (typeof callback !== 'function') {\n throw new TypeError('The callback provided as parameter 1 is not a function.');\n }\n this.callback_ = callback;\n this.controller_ = controller;\n this.callbackCtx_ = callbackCtx;\n }\n /**\r\n * Starts observing provided element.\r\n *\r\n * @param {Element} target - Element to be observed.\r\n * @returns {void}\r\n */\n ResizeObserverSPI.prototype.observe = function (target) {\n if (!arguments.length) {\n throw new TypeError('1 argument required, but only 0 present.');\n }\n // Do nothing if current environment doesn't have the Element interface.\n if (typeof Element === 'undefined' || !(Element instanceof Object)) {\n return;\n }\n if (!(target instanceof getWindowOf(target).Element)) {\n throw new TypeError('parameter 1 is not of type \"Element\".');\n }\n var observations = this.observations_;\n // Do nothing if element is already being observed.\n if (observations.has(target)) {\n return;\n }\n observations.set(target, new ResizeObservation(target));\n this.controller_.addObserver(this);\n // Force the update of observations.\n this.controller_.refresh();\n };\n /**\r\n * Stops observing provided element.\r\n *\r\n * @param {Element} target - Element to stop observing.\r\n * @returns {void}\r\n */\n ResizeObserverSPI.prototype.unobserve = function (target) {\n if (!arguments.length) {\n throw new TypeError('1 argument required, but only 0 present.');\n }\n // Do nothing if current environment doesn't have the Element interface.\n if (typeof Element === 'undefined' || !(Element instanceof Object)) {\n return;\n }\n if (!(target instanceof getWindowOf(target).Element)) {\n throw new TypeError('parameter 1 is not of type \"Element\".');\n }\n var observations = this.observations_;\n // Do nothing if element is not being observed.\n if (!observations.has(target)) {\n return;\n }\n observations.delete(target);\n if (!observations.size) {\n this.controller_.removeObserver(this);\n }\n };\n /**\r\n * Stops observing all elements.\r\n *\r\n * @returns {void}\r\n */\n ResizeObserverSPI.prototype.disconnect = function () {\n this.clearActive();\n this.observations_.clear();\n this.controller_.removeObserver(this);\n };\n /**\r\n * Collects observation instances the associated element of which has changed\r\n * it's content rectangle.\r\n *\r\n * @returns {void}\r\n */\n ResizeObserverSPI.prototype.gatherActive = function () {\n var _this = this;\n this.clearActive();\n this.observations_.forEach(function (observation) {\n if (observation.isActive()) {\n _this.activeObservations_.push(observation);\n }\n });\n };\n /**\r\n * Invokes initial callback function with a list of ResizeObserverEntry\r\n * instances collected from active resize observations.\r\n *\r\n * @returns {void}\r\n */\n ResizeObserverSPI.prototype.broadcastActive = function () {\n // Do nothing if observer doesn't have active observations.\n if (!this.hasActive()) {\n return;\n }\n var ctx = this.callbackCtx_;\n // Create ResizeObserverEntry instance for every active observation.\n var entries = this.activeObservations_.map(function (observation) {\n return new ResizeObserverEntry(observation.target, observation.broadcastRect());\n });\n this.callback_.call(ctx, entries, ctx);\n this.clearActive();\n };\n /**\r\n * Clears the collection of active observations.\r\n *\r\n * @returns {void}\r\n */\n ResizeObserverSPI.prototype.clearActive = function () {\n this.activeObservations_.splice(0);\n };\n /**\r\n * Tells whether observer has active observations.\r\n *\r\n * @returns {boolean}\r\n */\n ResizeObserverSPI.prototype.hasActive = function () {\n return this.activeObservations_.length > 0;\n };\n return ResizeObserverSPI;\n}();\n\n// Registry of internal observers. If WeakMap is not available use current shim\n// for the Map collection as it has all required methods and because WeakMap\n// can't be fully polyfilled anyway.\nvar observers = typeof WeakMap !== 'undefined' ? new WeakMap() : new MapShim();\n/**\r\n * ResizeObserver API. Encapsulates the ResizeObserver SPI implementation\r\n * exposing only those methods and properties that are defined in the spec.\r\n */\nvar ResizeObserver = /** @class */function () {\n /**\r\n * Creates a new instance of ResizeObserver.\r\n *\r\n * @param {ResizeObserverCallback} callback - Callback that is invoked when\r\n * dimensions of the observed elements change.\r\n */\n function ResizeObserver(callback) {\n if (!(this instanceof ResizeObserver)) {\n throw new TypeError('Cannot call a class as a function.');\n }\n if (!arguments.length) {\n throw new TypeError('1 argument required, but only 0 present.');\n }\n var controller = ResizeObserverController.getInstance();\n var observer = new ResizeObserverSPI(callback, controller, this);\n observers.set(this, observer);\n }\n return ResizeObserver;\n}();\n// Expose public methods of ResizeObserver.\n['observe', 'unobserve', 'disconnect'].forEach(function (method) {\n ResizeObserver.prototype[method] = function () {\n var _a;\n return (_a = observers.get(this))[method].apply(_a, arguments);\n };\n});\nvar index = function () {\n // Export existing implementation if available.\n if (typeof global$1.ResizeObserver !== 'undefined') {\n return global$1.ResizeObserver;\n }\n return ResizeObserver;\n}();\nexport default index;","import { ElementRef, Injectable } from '@angular/core';\n\nimport ResizeObserver from 'resize-observer-polyfill';\nimport { Observable, Subject } from 'rxjs';\nimport { shareReplay } from 'rxjs/operators';\n\n@Injectable({\n providedIn: 'root',\n})\nexport class ResizeObserverService {\n private changes$ = new Subject();\n private elementChanges = new WeakMap, Observable>();\n private observer = new ResizeObserver((entries: ResizeObserverEntry[]) => this.changes$.next(entries));\n\n observe(element: ElementRef): Observable {\n let elementChanges$ = this.elementChanges.get(element);\n if (!elementChanges$) {\n this.observer.observe(element.nativeElement);\n\n elementChanges$ = new Observable((observer) => {\n const inner = this.changes$.subscribe((changes) => {\n const change = changes.find((e) => e.target === element.nativeElement);\n if (change) {\n observer.next(change);\n }\n });\n\n return () => {\n inner.unsubscribe();\n this.observer.unobserve(element.nativeElement);\n this.elementChanges.delete(element);\n };\n }).pipe(shareReplay({ bufferSize: 1, refCount: true }));\n\n this.elementChanges.set(element, elementChanges$);\n }\n\n return elementChanges$;\n }\n}\n","import { DestroyRef, ElementRef, Injectable, Optional, inject } from '@angular/core';\nimport { takeUntilDestroyed } from '@angular/core/rxjs-interop';\n\nimport { objectEntries } from '@frontend/sports/common/core/utils/collection';\nimport { ResizeObserverService } from '@frontend/sports/common/core/utils/dom';\nimport { GridBreakpointSize, GridLayout } from '@frontend/sports/grid/core/feature/model';\nimport { MediaQueryService } from '@frontend/vanilla/core';\nimport { isEqual } from 'lodash-es';\nimport { Observable } from 'rxjs';\nimport { distinctUntilChanged, map } from 'rxjs/operators';\n\ninterface ColumnsBreakpoint {\n query: string;\n min?: number;\n max?: number;\n columns: number;\n}\n\ninterface GridBreakpoint {\n [breakpoint: string]: number;\n}\n\nexport interface GridBreakpointOptions {\n calculateGridWidth: (containerWidth: number) => number;\n}\n\nconst DEFAULT_GRID_BREAKPOINTS_OPTIONS: GridBreakpointOptions = {\n calculateGridWidth: (containerWidth: number) => containerWidth,\n};\n\nconst DEFAULT_GRID_BREAKPOINTS: GridBreakpoint = {\n 'xs': 1,\n 'sm': 2,\n 'md': 3,\n 'gt-md': 4,\n};\n\nconst SIX_PACK_GRID_BREAKPOINTS: GridBreakpoint = {\n 'lt-md': 1,\n 'gt-sm': 2,\n};\n\n@Injectable()\nexport class GridBreakpointService {\n private static DEFAULT_BREAKPOINTS: ColumnsBreakpoint[];\n private static SIX_PACK_BREAKPOINTS: ColumnsBreakpoint[];\n\n private get defaultBreakpoints(): ColumnsBreakpoint[] {\n return (\n GridBreakpointService.DEFAULT_BREAKPOINTS ??\n (GridBreakpointService.DEFAULT_BREAKPOINTS = this.parseGridBreakpoints(DEFAULT_GRID_BREAKPOINTS))\n );\n }\n\n private get sixPackBreakpoints(): ColumnsBreakpoint[] {\n return (\n GridBreakpointService.SIX_PACK_BREAKPOINTS ??\n (GridBreakpointService.SIX_PACK_BREAKPOINTS = this.parseGridBreakpoints(SIX_PACK_GRID_BREAKPOINTS))\n );\n }\n\n private readonly destroyRef = inject(DestroyRef);\n\n constructor(\n private mediaObserver: MediaQueryService,\n private resizeObserver: ResizeObserverService,\n @Optional() private container?: ElementRef,\n ) {}\n\n forGrid(\n element: ElementRef,\n layout: GridLayout,\n options: GridBreakpointOptions = DEFAULT_GRID_BREAKPOINTS_OPTIONS,\n ): Observable {\n const containerWidth = this.container\n ? this.resizeObserver.observe(this.container).pipe(\n map((changes) => changes.contentRect.width),\n distinctUntilChanged(),\n map((width) => options.calculateGridWidth(width)),\n )\n : this.mediaObserver.observe().pipe(map(() => element.nativeElement.clientWidth));\n\n return containerWidth.pipe(\n map((width) => {\n const columnsBreakpoints = layout === GridLayout.SixPack ? this.sixPackBreakpoints : this.defaultBreakpoints;\n\n return {\n default: this.matchColumnsBreakpoint(this.defaultBreakpoints, width),\n columns: this.matchColumnsBreakpoint(columnsBreakpoints, width),\n };\n }),\n distinctUntilChanged(isEqual),\n takeUntilDestroyed(this.destroyRef),\n );\n }\n\n private matchColumnsBreakpoint(source: ColumnsBreakpoint[], width?: number): number {\n const matcher = width\n ? (item: ColumnsBreakpoint) => (item.min === undefined || item.min <= width) && (item.max === undefined || item.max >= width)\n : (item: ColumnsBreakpoint) => this.mediaObserver.isActive(item.query);\n\n for (const current of source) {\n if (matcher(current)) {\n return current.columns;\n }\n }\n\n return 1;\n }\n\n private parseGridBreakpoints(source: GridBreakpoint): ColumnsBreakpoint[] {\n return objectEntries(this.mediaObserver.breakpoints)\n .filter((point) => point.key in source)\n .map(({ key, value }) => this.parseColumnsBreakpoint(key, value, source))\n .filter((point) => point.min || point.max)\n .sort((first, second) => second.columns - first.columns);\n }\n\n private parseColumnsBreakpoint(name: string, query: string, source: GridBreakpoint): ColumnsBreakpoint {\n const regex = /(min|max)-width\\:\\s*(\\d+)px/gi;\n let match: RegExpExecArray | null;\n\n const result: ColumnsBreakpoint = { query: name, columns: source[name] };\n\n while ((match = regex.exec(query))) {\n //@ts-ignore using match[1] as key index for result doesn't work, add expect error to bypass compiler issues.\n result[match[1]] = parseInt(match[2]);\n }\n\n return result;\n }\n}\n","/* eslint-disable no-param-reassign */\n\n/* eslint-disable @typescript-eslint/no-unsafe-argument */\n\n/* eslint-disable no-prototype-builtins */\n\n/* eslint-disable @typescript-eslint/no-unsafe-member-access */\n\n/* eslint-disable @typescript-eslint/no-unsafe-call */\n\n/* eslint-disable @typescript-eslint/no-unsafe-return */\n\n/* eslint-disable @typescript-eslint/no-explicit-any */\nimport { ɵComponentType as ComponentType, ɵNG_COMP_DEF as NG_COMP_DEF } from '@angular/core';\n\nimport { capitalize } from 'lodash-es';\n\nconst WIREDUP: unique symbol = Symbol('__hooks__wiredup__');\nconst REGISTERED_WIREDUP: unique symbol = Symbol('__hooks__wiredup__registered__');\n\nfunction markWiredUp(type: any): void {\n // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, no-param-reassign\n type.prototype[WIREDUP] = true;\n}\n\nfunction isComponent(type: any): type is ComponentType {\n return type.hasOwnProperty(NG_COMP_DEF);\n}\n\nfunction wireUpComponent(type: ComponentType): void {\n type.prototype.ngOnInit = wireUp(type.prototype.ngOnInit, 'init');\n markWiredUp(type);\n}\n\n// eslint-disable-next-line @typescript-eslint/ban-types\nfunction wireUp(hook: Function | null | undefined, target: 'init'): Function {\n const pre = `preNgOn${capitalize(target)}`;\n const post = `postNgOn${capitalize(target)}`;\n\n return function (this: any): void {\n this[pre]?.();\n hook?.call(this);\n this[post]?.();\n };\n}\n\nexport function HooksWireup(): (target: any) => void {\n return function (target: any): void {\n if (!target.prototype[REGISTERED_WIREDUP]) {\n throw new Error(\n `Decorator @HooksWireup used in the class ${target.name} should only be used when class extends a hook implementation like OnRouteResolve.`,\n );\n }\n\n if (isComponent(target)) {\n wireUpComponent(target);\n } else {\n throw new Error(\n `Cannot wireup class ${target.name} as it is not decorated with @Component. Also check the order of the directives, as @HooksWireup should come prior to the other ones.`,\n );\n }\n };\n}\n\nexport abstract class Hook {\n get [REGISTERED_WIREDUP](): boolean {\n return true;\n }\n\n constructor() {\n // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment\n const prototype = Object.getPrototypeOf(this);\n\n if (!prototype[WIREDUP]) {\n throw new Error(`To make the custom hooks work correctly, the class ${prototype.constructor.name} should be decorated with @HooksWireup`);\n }\n }\n}\n","import { DestroyRef, inject } from '@angular/core';\nimport { takeUntilDestroyed } from '@angular/core/rxjs-interop';\nimport { ActivatedRoute } from '@angular/router';\n\nimport { Hook } from './hooks-wireup';\n\nexport abstract class OnRouteResolve extends Hook {\n protected readonly destroyRef = inject(DestroyRef);\n\n constructor(protected route: ActivatedRoute) {\n super();\n }\n\n preNgOnInit(): void {\n // eslint-disable-next-line @typescript-eslint/no-unsafe-argument\n this.route.data.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(({ model }) => this.onRouteResolved(model));\n }\n\n abstract onRouteResolved(model: TModel): void;\n}\n","import { hasValue } from '@frontend/sports/common/core/utils/extended-types';\nimport { Widget, WidgetType } from '@frontend/sports/types/components/widget';\nimport { ComposableWidgetPayload } from '@frontend/sports/types/components/widget/types';\nimport { flatMap, uniqBy } from 'lodash-es';\n\nexport function widgetsToWidgetTypes(widgets: Widget[] | undefined) {\n let widgetTypes = widgets?.map((widget) => widget.type);\n const composableWidget = widgets?.find((widget) => widget.type === WidgetType.Composable);\n if (composableWidget) {\n const composableWidgetPayload = (composableWidget.payload ?? {}) as ComposableWidgetPayload;\n const childWidgets = uniqBy(\n flatMap(composableWidgetPayload.items, (item) => item.children),\n (child) => child.type,\n ).map((child) => child.type);\n widgetTypes = [...childWidgets, ...widgetTypes!];\n }\n return widgetTypes?.filter(hasValue);\n}\n","import { Injectable } from '@angular/core';\n\nimport { WidgetConfig } from '@frontend/sports/common/client-config-data-access';\nimport { filterSportsEmitLast } from '@frontend/sports/host-app/sports-product/feature/utils';\nimport { Widget, WidgetLayoutTemplate, WidgetPage } from '@frontend/sports/types/components/widget';\nimport { Observable, ReplaySubject } from 'rxjs';\nimport { distinctUntilKeyChanged, filter } from 'rxjs/operators';\n\nimport ModuleLoaderService from '../../deferred/module-loader.service';\nimport { AdaptiveLayoutService } from '../../layout/adaptive-layout.service';\nimport { widgetsToWidgetTypes } from './widgets-to-widget-types.util';\n\nexport interface WidgetRightColumnContent {\n widgets: Widget[];\n page?: WidgetPage;\n layoutTemplate?: WidgetLayoutTemplate;\n}\n\n@Injectable({\n providedIn: 'root',\n})\nexport class WidgetRightColumnService {\n private widgets = new ReplaySubject(1);\n\n constructor(\n private config: WidgetConfig,\n private moduleLoader: ModuleLoaderService,\n layout: AdaptiveLayoutService,\n ) {\n layout.stateChange$\n .pipe(\n distinctUntilKeyChanged('hasRightColumn'),\n filterSportsEmitLast(),\n filter((current) => current.hasRightColumn),\n )\n .subscribe(() => this.resetContent());\n this.widgets.pipe(filter(({ widgets }) => widgets.length > 0)).subscribe(({ widgets }) => {\n const widgetTypes = widgetsToWidgetTypes(widgets);\n this.moduleLoader.lazyLoadFeatures(widgetTypes!);\n });\n this.setContent([]);\n }\n\n setContent(widgets: Widget[], page?: WidgetPage, layoutTemplate?: WidgetLayoutTemplate): void {\n if (!widgets.length) {\n widgets = this.config.defaultRightColumn;\n }\n\n this.widgets.next({\n widgets,\n page,\n layoutTemplate,\n });\n }\n\n resetContent(): void {\n this.setContent([]);\n }\n\n asObservable(): Observable {\n return this.widgets.asObservable();\n }\n}\n","import { Injectable } from '@angular/core';\n\nimport { TrackData, TrackUpdate, TrackingService } from '@frontend/sports/tracking/feature';\n\n/* eslint-disable no-restricted-imports */\nimport { TrackingService as VanillaTrackingService } from '@frontend/vanilla/core';\nimport { isEmpty } from 'lodash-es';\n\nimport { PickSourceProvider } from '../../option-pick/pick-source.provider';\n\n@Injectable()\nexport class ModularTrackingService extends TrackingService {\n constructor(\n private pickSourceProvider: PickSourceProvider,\n vanillaTracking: VanillaTrackingService,\n ) {\n super(vanillaTracking);\n }\n\n override track(event: string, data?: Partial, options?: any): void {\n super.track(event, this.updateData(data), options);\n }\n\n override update(data: Partial): void {\n super.update(this.updateData(data));\n }\n\n override triggerEventWithCleaning(event?: string, data?: Partial, options?: any): void {\n super.triggerEventWithCleaning(event, this.updateData(data), options);\n }\n\n private updateData(trackingObject: T): T {\n const baseTracking = this.pickSourceProvider.getTracking();\n\n return isEmpty(baseTracking)\n ? trackingObject\n : {\n ...trackingObject,\n ...baseTracking,\n };\n }\n}\n","import { Injectable } from '@angular/core';\n\nimport { Widget, WidgetContext } from '@frontend/sports/types/components/widget';\nimport { Observable } from 'rxjs';\n\nimport { WidgetContextService } from './widget-context.service';\n\n@Injectable({\n providedIn: 'root',\n})\nexport class WidgetContextRefreshProcessor {\n constructor(private context: WidgetContextService) {}\n\n refresh(contexts: Partial[]): Observable[] | undefined> {\n return this.context.getWidgetBatch(contexts);\n }\n}\n","import { Injectable } from '@angular/core';\nimport { ActivationEnd } from '@angular/router';\n\nimport { CDNCachingConfig, WidgetConfig } from '@frontend/sports/common/client-config-data-access';\nimport { RouteTag } from '@frontend/sports/common/core/data-access/route';\nimport { ofType } from '@frontend/sports/common/core/utils/extended-types';\nimport { RouterEventsService } from '@frontend/sports/common/core/utils/router-events';\nimport { filterSports } from '@frontend/sports/host-app/sports-product/feature/utils';\nimport { WidgetPage } from '@frontend/sports/types/components/widget';\nimport { EMPTY, Observable, Subscription, interval, map, noop } from 'rxjs';\n\nimport { WidgetContextRefreshProcessor } from '../common/widget-context-refresh-processor';\nimport { WidgetComponent } from './widget-registry.service';\n\nexport abstract class WidgetRefreshTrigger {\n constructor(private config: WidgetConfig) {}\n\n asObservable(): Observable {\n const time = this.config.pageRefreshInterval\n .filter(({ page }) => page === this.getPage())\n .map((page) => page.interval)\n .pop();\n\n if (time) {\n return interval(time * 1000).pipe(map(noop));\n }\n\n return EMPTY;\n }\n\n abstract getPage(): WidgetPage;\n}\n\n@Injectable({\n providedIn: 'root',\n})\nexport class WidgetRefreshService {\n private components = new Map, undefined>();\n private triggers = new Map();\n\n private refresh?: Subscription;\n\n constructor(\n router: RouterEventsService,\n private processor: WidgetContextRefreshProcessor,\n private cdnCacheConfig: CDNCachingConfig,\n ) {\n router.currentActivationEnd.pipe(filterSports(), ofType(ActivationEnd)).subscribe((route) => this.registerRefresh(route.snapshot.data.tag));\n }\n\n deregisterComponent(widgets: WidgetComponent[]): void {\n for (const widget of widgets) {\n this.components.delete(widget);\n }\n }\n\n registerComponent(widgets: WidgetComponent[]): void {\n for (const widget of widgets) {\n this.components.set(widget, undefined);\n }\n }\n\n registerTrigger(tag: RouteTag, trigger: WidgetRefreshTrigger): void {\n this.triggers.set(tag, trigger);\n }\n\n private registerRefresh(tag: RouteTag): void {\n const trigger = this.triggers.get(tag);\n\n this.refresh?.unsubscribe();\n this.refresh = trigger?.asObservable().subscribe(() => {\n const components = [...this.components.keys()].filter((component) => component.config.refreshable);\n const contexts = components\n .filter((component) => !this.cdnCacheConfig.widgets.includes(component.config.type))\n .map((component) => component.getContext());\n\n if (contexts.length) {\n this.processor.refresh(contexts).subscribe((widgets) => {\n for (const widget of widgets!) {\n const component = components.find((current) => current.config.id === widget.id);\n\n if (component) {\n component.onResolve(widget, undefined, true, true);\n }\n }\n });\n }\n components\n .filter((component) => this.cdnCacheConfig.widgets.includes(component.config.type) && component.config.refreshable)\n .forEach((component) => component.onResolve(component.config, undefined, true, true));\n });\n }\n}\n","import { Injectable, Injector, ViewContainerRef, inject } from '@angular/core';\n\nimport { Widget, WidgetLocation } from '@frontend/sports/types/components/widget';\nimport { Observable, Subject, combineLatest, filter, iif, map, of, shareReplay, take, takeUntil } from 'rxjs';\n\nimport { WidgetLoaderService } from './widget-loader.service';\nimport { WidgetRegistryService } from './widget-registry.service';\nimport { WidgetSkeletonData } from './widget-skeleton.model';\n\n@Injectable({ providedIn: 'root' })\nexport class WidgetSkeletonRenderer {\n private readonly componentRegistry = inject(WidgetRegistryService);\n private readonly widgetLoaderService = inject(WidgetLoaderService);\n\n private readonly widgetSkeletons = new Map void>();\n\n maybeRenderSkeleton({\n widget,\n index,\n injector: parentInjector,\n allWidgets,\n containerRef,\n }: {\n widget: Widget;\n index: number;\n injector: Injector;\n allWidgets: Widget[];\n containerRef: ViewContainerRef;\n }): void {\n const skeletonConfig = this.componentRegistry.widgetSkeletons[widget.type];\n // if we have a skeleton configured and the slot is empty\n if (skeletonConfig && !this.widgetSkeletons.get(widget.id)) {\n const widgetsBefore = allWidgets.filter((w) => w.location === widget.location && w.order < widget.order);\n const topWidgetsLoaded$ = iif(\n () => widget.location === WidgetLocation.Left || widget.location === WidgetLocation.Center,\n this.widgetLoaderService.allTopLocationWidgetsLoaded$.pipe(filter(Boolean)),\n of(true),\n );\n let widgetsBeforeLoaded$: Observable = topWidgetsLoaded$;\n if (widgetsBefore.length > 0) {\n widgetsBeforeLoaded$ = combineLatest([\n this.widgetLoaderService.widgetLoadedDetails$(widget.location).pipe(\n map((widgetDetails) => {\n const sortedOrder = widgetDetails.find(({ widgetId }) => widgetId === widget.id)!.order;\n\n return widgetDetails.filter((widgetData) => widgetData.order < sortedOrder).every((widgetData) => widgetData.isLoaded);\n }),\n ),\n topWidgetsLoaded$,\n ]).pipe(\n map(([widgetsBeforeLoaded, topWidgetsLoaded]) => widgetsBeforeLoaded && topWidgetsLoaded),\n shareReplay({ refCount: true, bufferSize: 1 }),\n );\n }\n const dispose = new Subject();\n const injector: Injector = Injector.create({\n parent: parentInjector,\n providers: [\n {\n provide: WidgetSkeletonData,\n useValue: {\n widget,\n widgetsBeforeLoaded$,\n config: skeletonConfig.config,\n },\n },\n ],\n });\n const loader = containerRef.createComponent(skeletonConfig.component, { index, injector });\n loader.changeDetectorRef.detectChanges();\n loader.instance.showSkeleton$.pipe(filter(Boolean), take(1), takeUntil(dispose)).subscribe(() => {\n this.widgetLoaderService.setwidgetLoaded(`${widget.location}-${widget.id}`);\n });\n this.widgetSkeletons.set(widget.id, () => {\n dispose.next();\n containerRef.remove(containerRef.indexOf(loader.hostView));\n this.widgetSkeletons.delete(widget.id);\n });\n }\n }\n\n removeSkeleton(id: string) {\n this.widgetSkeletons.get(id)?.();\n }\n}\n","import {\n Component,\n ComponentRef,\n ElementRef,\n HostBinding,\n Injectable,\n Injector,\n Input,\n OnChanges,\n OnDestroy,\n Optional,\n SkipSelf,\n ViewChild,\n ViewContainerRef,\n} from '@angular/core';\nimport { takeUntilDestroyed } from '@angular/core/rxjs-interop';\n\nimport { LoggerFactory, SportsRemoteLogger } from '@frontend/sports/common/core/feature/logging';\nimport { GridBreakpointSize, GridLayout } from '@frontend/sports/grid/core/feature/model';\nimport { Modal } from '@frontend/sports/modal/feature';\nimport { TrackingService } from '@frontend/sports/tracking/feature';\nimport { Widget, WidgetLayoutTemplate, WidgetLocation, WidgetPage } from '@frontend/sports/types/components/widget';\nimport { isNumber, sortBy } from 'lodash-es';\nimport { EMPTY, Observable } from 'rxjs';\n\nimport { GridBreakpointOptions, GridBreakpointService } from '../../grid/grid-breakpoint.service';\nimport { PickSourceProvider } from '../../option-pick/pick-source.provider';\nimport { ModularConfigAccessorService } from './modular-config-accessor.service';\nimport { ModularPickSourceService } from './modular-pick-source.service';\nimport { ModularTrackingService } from './modular-tracking.service';\nimport { WidgetData, WidgetLoaderService } from './widget-loader.service';\nimport { WidgetRefreshService } from './widget-refresh.service';\nimport { WidgetComponent, WidgetRegistryService } from './widget-registry.service';\nimport { WidgetSkeletonRenderer } from './widget-skeleton-renderer.service';\n\nabstract class WidgetRef extends ComponentRef> {}\n\ninterface ReusableWidgetRef {\n config: Widget;\n configIndex?: number;\n component?: WidgetRef;\n componentIndex?: number;\n}\n\ninterface ReusedWidgetRef extends ReusableWidgetRef {\n configIndex: number;\n component: WidgetRef;\n componentIndex: number;\n}\n\n@Injectable()\nexport class WidgetSlotGridBreakpointService implements Pick {\n private calculateGridWidth: (containerWidth: number) => number;\n\n constructor(@SkipSelf() @Optional() private gridBreakpoint?: GridBreakpointService) {\n this.forRatio(1);\n }\n\n forGrid(element: ElementRef, layout: GridLayout, options?: GridBreakpointOptions): Observable {\n if (!this.gridBreakpoint) {\n return EMPTY;\n }\n\n return this.gridBreakpoint.forGrid(element, layout, { ...options, calculateGridWidth: this.calculateGridWidth });\n }\n\n forRatio(ratio: number): void {\n if (ratio === 1) {\n this.calculateGridWidth = (containerWidth) => containerWidth;\n } else {\n this.calculateGridWidth = (containerWidth) => (containerWidth - 16) * ratio;\n }\n }\n}\n\n@Component({\n selector: 'ms-widget-slot',\n template: '',\n providers: [WidgetSlotGridBreakpointService, { provide: GridBreakpointService, useExisting: WidgetSlotGridBreakpointService }],\n})\nexport class WidgetSlotComponent implements OnChanges, OnDestroy {\n @Input() widget: Widget[];\n @Input() parent?: Widget;\n @Input() page?: WidgetPage;\n @Input() ratio?: number;\n @Input() layoutTemplate?: WidgetLayoutTemplate;\n\n @HostBinding('class') className = 'widget-slot';\n @ViewChild('container', { read: ViewContainerRef, static: true }) containerRef: ViewContainerRef;\n\n private components: WidgetRef[] = [];\n\n private logger: SportsRemoteLogger;\n\n constructor(\n private componentRefresh: WidgetRefreshService,\n private componentRegistry: WidgetRegistryService,\n private gridBreakpoint: WidgetSlotGridBreakpointService,\n private injector: Injector,\n private widgetLoaderService: WidgetLoaderService,\n private widgetSkeletonRenderer: WidgetSkeletonRenderer,\n loggerFactory: LoggerFactory,\n ) {\n this.logger = loggerFactory.getLogger('WidgetSlotComponent');\n\n // onchanges is triggered 2 times on page load becasue of lazy widgets -> careful with lazyloading data in onResolve\n componentRegistry.lazyWidgets$.pipe(takeUntilDestroyed()).subscribe(() => this.createOrUpdateWidgetComponent(true));\n }\n\n ngOnChanges(): void {\n this.createOrUpdateWidgetComponent();\n }\n\n createOrUpdateWidgetComponent(isLazy: boolean = false) {\n this.gridBreakpoint.forRatio(this.ratio ?? 1);\n\n const reused: WidgetRef[] = [];\n\n const ordered: ReusableWidgetRef[] = sortBy(this.widget, (config) => config.order).map((config, index) => {\n const existing = this.components.findIndex((component) => component.instance.config.type === config.type && !reused.includes(component));\n\n if (existing === -1) {\n return { config };\n }\n\n reused.push(this.components[existing]);\n\n return {\n config,\n configIndex: index,\n component: this.components[existing],\n componentIndex: existing,\n };\n });\n\n this.normalizeReused(ordered, 'configIndex');\n this.normalizeReused(ordered, 'componentIndex');\n\n this.destroyUnused(reused);\n if (!isLazy) {\n ordered.forEach(({ config }, index) => {\n this.widgetLoaderService.setSlotWidgets(`${config.location}-${config.id}`, {\n isLoaded: config.location === WidgetLocation.Right,\n order: index,\n slot: config.location,\n widgetId: config.id,\n parentWidgetId: this.parent?.id,\n } as WidgetData);\n });\n }\n\n ordered\n .filter((widget) => this.componentRegistry.get(widget.config.type) || this.componentRegistry.widgetSkeletons[widget.config.type])\n .forEach(({ config, component }, index) => {\n try {\n if (component) {\n this.updateComponent(config, component, !isLazy);\n\n this.orderReused(ordered, index);\n } else {\n this.createComponent(config, index);\n }\n } catch (error: any) {\n this.log(error);\n }\n });\n\n this.componentRefresh.registerComponent(this.components.map((component) => component.instance));\n }\n\n ngOnDestroy(): void {\n this.componentRefresh.deregisterComponent(this.components.map((component) => component.instance));\n }\n\n private createComponent(config: Widget, index: number): void {\n const registeredComponent = this.componentRegistry.get(config.type);\n if (!registeredComponent) {\n if (config.payload) {\n this.widgetSkeletonRenderer.maybeRenderSkeleton({\n widget: config,\n allWidgets: this.widget,\n index,\n containerRef: this.containerRef,\n injector: this.injector,\n });\n }\n\n return;\n }\n\n const injector: Injector = Injector.create({\n providers: [\n { provide: ModularConfigAccessorService, useClass: ModularConfigAccessorService },\n { provide: PickSourceProvider, useClass: ModularPickSourceService },\n { provide: TrackingService, useClass: ModularTrackingService },\n { provide: Modal, useClass: Modal },\n ],\n parent: registeredComponent.injector ?? this.injector,\n });\n\n this.widgetSkeletonRenderer.removeSkeleton(config.id);\n const component = this.containerRef.createComponent(registeredComponent.componentType, { index, injector });\n this.updateComponent(config, component, true);\n }\n\n private isReused(reused: ReusableWidgetRef): reused is ReusedWidgetRef {\n return reused.component !== undefined;\n }\n\n private updateComponent(config: Widget, widgetComponent: ComponentRef>, shouldCallOnData: boolean): void {\n try {\n const widget = { ...config, order: this.components.length };\n const accessor = widgetComponent.injector.get(ModularConfigAccessorService);\n\n accessor.setWidget(widget);\n accessor.setPage(this.page);\n accessor.setLayoutTemplate(this.layoutTemplate);\n accessor.setParentWidget(this.parent);\n if (shouldCallOnData) {\n widgetComponent.instance.hidden = config.location !== WidgetLocation.Right;\n }\n widgetComponent.instance.onResolve(widget, this.parent, shouldCallOnData);\n } catch (error: any) {\n this.log(error);\n widgetComponent.instance.hasData = false;\n this.widgetLoaderService.setwidgetLoaded(`${config.location}-${config.id}`);\n }\n // this is important: update the widgets bindings\n widgetComponent.hostView.detectChanges();\n this.components.push(widgetComponent);\n }\n\n private orderReused(reused: ReusableWidgetRef[], index: number): void {\n const currentWidget = reused[index];\n\n if (!this.isReused(currentWidget) || currentWidget.configIndex === currentWidget.componentIndex) {\n return;\n }\n\n this.containerRef.move(currentWidget.component.hostView, index);\n currentWidget.componentIndex = currentWidget.configIndex;\n\n reused\n .filter(\n (current) =>\n this.isReused(current) &&\n current.configIndex > currentWidget.configIndex &&\n current.componentIndex < currentWidget.componentIndex,\n )\n .forEach((current) => {\n if (this.isReused(current)) {\n current.componentIndex++;\n }\n });\n }\n\n private destroyUnused(reused: ComponentRef>[]): void {\n const unusedWidget = this.components.filter((component) => !reused.includes(component));\n\n this.componentRefresh.deregisterComponent(unusedWidget.map((current) => current.instance));\n this.components = [];\n\n unusedWidget.forEach((component) => component.destroy());\n }\n\n private normalizeReused(source: ReusableWidgetRef[], target: 'configIndex' | 'componentIndex'): void {\n sortBy(\n source.filter((config) => isNumber(config[target])),\n (config) => config[target],\n ).forEach((config, index) => {\n config[target] = index;\n });\n }\n\n private log(error: Error): void {\n console.error(error);\n this.logger.error(error);\n }\n}\n","import { Injectable } from '@angular/core';\n\nimport { hasValue } from '@frontend/sports/common/core/utils/extended-types';\nimport { NOT_APPLICABLE, TrackingService, trackingConstants } from '@frontend/sports/tracking/feature';\nimport { Widget, WidgetLayoutResponse, WidgetType } from '@frontend/sports/types/components/widget';\nimport { LinkListPayload } from '@frontend/sports/types/components/widget/types';\n\n@Injectable({ providedIn: 'root' })\nexport class WidgetTrackingService {\n constructor(private trackingService: TrackingService) {}\n\n trackWidgetsLoaded({ widgets, template, page }: WidgetLayoutResponse): void {\n const linkListWidget = widgets?.find((x) => x.type === WidgetType.LinkList) as Widget | undefined;\n if (linkListWidget?.payload) {\n for (let x = 0; x < linkListWidget?.payload?.links!.length; x++) {\n widgets?.push({\n type: linkListWidget.type,\n location: linkListWidget.location,\n id: linkListWidget.id,\n order: linkListWidget.order,\n refreshable: linkListWidget.refreshable,\n payload: [],\n templateName: linkListWidget.templateName,\n trackingData: linkListWidget.trackingData,\n visible: linkListWidget.visible,\n sitecoreId: linkListWidget?.payload.links?.[x].sitecoreId ?? '',\n userSegmentGroups: linkListWidget?.payload.links?.[x].userSegmentGroups ?? '',\n });\n }\n }\n\n const widgetTrackingData = widgets?.map((w) => {\n return {\n ...w.trackingData,\n [trackingConstants.COMPONENT_MODULE_NAME]: w.type,\n [trackingConstants.COMPONENT_MODULE_POSITION]: `${w.location}|${w.order}`,\n [trackingConstants.COMPONENT_PAGE_LAYOUT]: [template.folder, page, template.name].filter(hasValue).join('/'),\n [trackingConstants.COMPONENT_MODULE_CUSTOM_NAME]: w.templateName,\n [trackingConstants.COMPONENT_MODULE_SOURCE]: 'standard module',\n [trackingConstants.COMPONENT_SITECORE_ID]: w.sitecoreId,\n [trackingConstants.COMPONENT_USER_SEGMENTS]: w.userSegmentGroups || NOT_APPLICABLE,\n };\n });\n\n this.trackingService.track(trackingConstants.SHOW_MODULE, {\n [trackingConstants.SHOW_MODULE]: widgetTrackingData,\n });\n }\n}\n","import { WidgetPage } from '@frontend/sports/types/components/widget';\n\nexport abstract class WidgetLayoutHook {\n constructor(readonly target: WidgetPage) {}\n\n abstract onResolve(): void;\n abstract onDestroy(): void;\n}\n","\n\n\n","import { Component, ElementRef, HostBinding, Inject, OnDestroy, OnInit, inject } from '@angular/core';\nimport { takeUntilDestroyed } from '@angular/core/rxjs-interop';\nimport { ActivatedRoute } from '@angular/router';\n\nimport { AutomationService } from '@frontend/sports/automation/core-feature';\nimport { ScrollingSizeConfig } from '@frontend/sports/common/client-config-data-access';\nimport { HooksWireup, OnRouteResolve } from '@frontend/sports/common/core/utils/hooks';\nimport { ScrollContainerService } from '@frontend/sports/common/core/utils/scroll-container';\nimport { Widget, WidgetLayoutResponse, WidgetLayoutTemplate, WidgetLocation, WidgetPage } from '@frontend/sports/types/components/widget';\nimport { MediaQueryService } from '@frontend/vanilla/core';\nimport { kebabCase } from 'lodash-es';\nimport { Subscription } from 'rxjs';\n\nimport { HideHeaderService } from '../../common/hide-header.service';\nimport ModuleLoaderService from '../../deferred/module-loader.service';\nimport { WidgetLayoutHook } from './widget-layout-hook';\nimport { WidgetLoaderService } from './widget-loader.service';\nimport { WidgetPageService } from './widget-page.service';\nimport { WidgetRightColumnService } from './widget-right-column.service';\nimport { WidgetTrackingService } from './widget-tracking.service';\nimport { MEDIUM_LAYOUT_BREAKPOINT, forSlot } from './widget.constants';\nimport { widgetsToWidgetTypes } from './widgets-to-widget-types.util';\n\n@HooksWireup()\n@Component({\n selector: 'ms-widget-layout',\n templateUrl: './widget-layout.component.html',\n})\nexport class WidgetLayoutComponent extends OnRouteResolve implements OnDestroy, OnInit {\n @HostBinding('class') className: string;\n\n page: WidgetPage;\n layoutTemplate: WidgetLayoutTemplate;\n\n top: Widget[] = [];\n left: Widget[] = [];\n center: Widget[] = [];\n\n leftVisible = true;\n private scrollSubscription?: Subscription;\n private widgetPageService = inject(WidgetPageService);\n\n constructor(\n route: ActivatedRoute,\n private media: MediaQueryService,\n private column: WidgetRightColumnService,\n private elementRef: ElementRef,\n private automationService: AutomationService,\n private widgetTrackingService: WidgetTrackingService,\n private moduleLoader: ModuleLoaderService,\n private scrollContainer: ScrollContainerService,\n private hideHeaderService: HideHeaderService,\n private scrollingSizeConfig: ScrollingSizeConfig,\n private widgetLoaderService: WidgetLoaderService,\n @Inject(WidgetLayoutHook) private hooks: WidgetLayoutHook[],\n ) {\n super(route);\n\n media\n .observe()\n .pipe(takeUntilDestroyed())\n .subscribe(() => this.setLeftVisibility());\n\n this.setClass();\n }\n\n ngOnInit(): void {\n if (this.scrollingSizeConfig.hideHeaderOnScroll.includes(this.page)) {\n this.scrollSubscription = this.scrollContainer.scroll.subscribe(() => {\n this.hideHeaderService.onScroll();\n });\n }\n }\n\n ngOnDestroy(): void {\n this.scrollSubscription?.unsubscribe();\n this.widgetLoaderService.cleanWidgetsLoaded();\n this.column.resetContent();\n this.destroyHooks();\n this.widgetPageService.reset();\n this.hideHeaderService.removeHideHeader();\n }\n\n onRouteResolved(model: WidgetLayoutResponse): void {\n if (this.page !== model.page) {\n this.destroyHooks();\n }\n\n this.page = model.page;\n\n this.layoutTemplate = model.template;\n this.resolveHooks();\n this.widgetLoaderService.cleanWidgetsLoaded();\n\n //on demand lazy load widgets using vanilla event strategy approach.\n const widgetTypes = widgetsToWidgetTypes(model.widgets);\n this.moduleLoader.lazyLoadFeatures(widgetTypes!);\n\n this.top = forSlot(model, WidgetLocation.Top);\n this.left = forSlot(model, WidgetLocation.Left);\n this.center = forSlot(model, WidgetLocation.Center);\n\n this.widgetPageService.setActivePage({\n page: this.page,\n top: this.top,\n left: this.left,\n center: this.center,\n });\n\n this.column.setContent(forSlot(model, WidgetLocation.Right), this.page, this.layoutTemplate);\n this.automationService.addAttr(this.elementRef, { widgetPage: model.page, widgetPageId: model.id });\n\n this.setLeftVisibility();\n this.setClass(model.page);\n this.widgetTrackingService.trackWidgetsLoaded(model);\n }\n\n private setLeftVisibility(): void {\n this.leftVisible = this.media.isActive(MEDIUM_LAYOUT_BREAKPOINT) && this.left.length > 0;\n }\n\n private setClass(page?: string): void {\n this.className = ['widget-layout', 'row', kebabCase(page)].filter((current) => current).join(' ');\n }\n\n private resolveHooks(): void {\n this.getHooks().forEach((hook) => hook.onResolve());\n }\n\n private destroyHooks(): void {\n this.getHooks().forEach((hook) => hook.onDestroy());\n }\n\n private getHooks(): WidgetLayoutHook[] {\n return this.hooks.filter((hook) => hook.target === this.page);\n }\n}\n","import { ElementRef } from '@angular/core';\n\nexport class ElementProvider {\n constructor(private elementRef: ElementRef) {}\n\n get element(): ElementRef {\n return this.elementRef;\n }\n}\n","import { Directive, ElementRef } from '@angular/core';\n\nimport { ElementProvider } from '@frontend/sports/common/core/utils/element-provider';\n\nimport { MODAL_DIALOG_CONTAINER } from './modal-dialog.model';\n\n@Directive({\n selector: '[msModalDialogContainer]',\n providers: [\n { provide: MODAL_DIALOG_CONTAINER, deps: [ElementRef], useFactory: (element: ElementRef) => new ElementProvider(element) },\n ],\n standalone: true,\n})\nexport class ModalDialogContainerDirective {}\n","\n","import { Component, ElementRef, OnInit } from '@angular/core';\n\nimport { ScrollContainerService } from '@frontend/sports/common/core/utils/scroll-container';\nimport { Observable } from 'rxjs';\n\nimport { ColumnLayoutService } from '../../layout/column-layout.service';\nimport { LayoutColumn } from '../../layout/layout.model';\nimport { WidgetRightColumnContent, WidgetRightColumnService } from './widget-right-column.service';\n\n@Component({\n selector: 'ms-widget-column',\n templateUrl: './widget-column.component.html',\n providers: [ScrollContainerService],\n})\nexport class WidgetColumnComponent implements OnInit {\n content$: Observable;\n\n constructor(\n service: WidgetRightColumnService,\n private scrollContainer: ScrollContainerService,\n private elementRef: ElementRef,\n private columnLayout: ColumnLayoutService,\n ) {\n this.content$ = service.asObservable();\n }\n\n ngOnInit(): void {\n this.columnLayout.register({\n component: WidgetColumnComponent,\n location: LayoutColumn.Right,\n });\n\n this.scrollContainer.setTarget(this.elementRef.nativeElement);\n }\n}\n","import { CommonModule } from '@angular/common';\nimport { NgModule } from '@angular/core';\n\nimport { ModalDialogContainerDirective } from '@frontend/sports/modal/feature';\n\nimport { WidgetColumnComponent } from './widget-column.component';\nimport { WidgetLayoutComponent } from './widget-layout.component';\nimport { WidgetSlotComponent } from './widget-slot.component';\n\n@NgModule({\n imports: [CommonModule, ModalDialogContainerDirective],\n declarations: [WidgetLayoutComponent, WidgetSlotComponent, WidgetColumnComponent],\n exports: [WidgetLayoutComponent, WidgetSlotComponent, WidgetColumnComponent],\n})\nexport class WidgetCoreModule {}\n","import { EventEmitter, Injectable, Type } from '@angular/core';\n\n//eslint-disable-next-line no-restricted-imports\nimport { SportsUserSettings } from '@frontend/sports/common/client-config-data-access';\nimport { LoggerFactory } from '@frontend/sports/common/core/feature/logging';\nimport { BetslipDigitalModule } from 'packages/sports/common/betslip/digital/betslip-digital.module';\n\nimport { ComponentLoaderService, ComponentProxy } from '../deferred/component-loader.service';\nimport ModuleLoaderService from '../deferred/module-loader.service';\n\nexport interface RightColumnBinding {\n inColumn: boolean;\n}\n\nexport interface EditMyBetAddPicksContainerMobileComponentBinding {\n picksContainerBottomOffset: EventEmitter;\n}\n\nexport interface BetslipBarComponentBindings {\n barClick: EventEmitter;\n myBetsClick: EventEmitter;\n}\n\n@Injectable({ providedIn: 'root' })\nexport class BetslipDigitalComponentLoaderService extends ComponentLoaderService {\n moduleName = 'BetslipDigitalModule';\n\n constructor(\n moduleLoader: ModuleLoaderService,\n loggerFactory: LoggerFactory,\n private sportsUserSettings: SportsUserSettings,\n ) {\n super(moduleLoader, loggerFactory);\n }\n\n getBetslipComponent(): ComponentProxy {\n return this.getComponentProxy((module) => module.betslipComponent, undefined, this.sportsUserSettings);\n }\n\n getEditBetComponent(): ComponentProxy {\n return this.getComponentProxy((module) => module.editBetComponent, undefined, this.sportsUserSettings);\n }\n\n getQuickBetComponent(): ComponentProxy {\n return this.getComponentProxy((module) => module.quickBetContainerComponent, undefined, this.sportsUserSettings);\n }\n\n getEditBetAddPicksContainerMobileComponent(): ComponentProxy {\n return this.getComponentProxy((module) => module.editBetAddPicksContainerMobileComponent, undefined, this.sportsUserSettings);\n }\n\n getModule(): Promise> {\n return import(/* webpackChunkName: \"betslip-digital\" */ 'packages/sports/common/betslip/digital/betslip-digital.module').then(\n (m) => m.BetslipDigitalModule,\n );\n }\n\n getRewardTokensComponent(): ComponentProxy {\n return this.getComponentProxy((module) => module.rewardTokensBannerComponent, undefined, this.sportsUserSettings);\n }\n\n getBetslipReturnsMessageComponent(): ComponentProxy {\n return this.getComponentProxy((module) => module.betslipReturnsMessagesComponent, undefined, this.sportsUserSettings);\n }\n\n getBetslipBarComponent(): ComponentProxy {\n return this.getComponentProxy((module) => module.betslipBarComponent, undefined, this.sportsUserSettings);\n }\n}\n","import { Injectable } from '@angular/core';\n\nimport { Fixture } from '@cds/betting-offer';\nimport { BettingInsightsModel } from '@cds/betting-offer/betting-insights';\nimport { FixtureView, SplitFixture } from '@cds/betting-offer/domain-specific';\nimport { BetBuilderFixture } from '@cds/betting-offer/domain-specific/bet-builder';\nimport { EnhancedFixtureViewGroupingConfiguration } from '@frontend/sports/betting-offer/feature/model';\nimport { OfferGroupingService } from '@frontend/sports/betting-offer/feature/offer-grouping';\nimport { firstValueFrom } from 'rxjs';\n\nimport { BettingOfferApi } from './betting-offer-api.service';\n\nexport interface FixtureViewModel {\n fixture: Fixture;\n splitFixtures: SplitFixture[];\n preCreatedOffer?: BetBuilderFixture;\n grouping: EnhancedFixtureViewGroupingConfiguration;\n bettingInsights?: BettingInsightsModel;\n}\n\n@Injectable({ providedIn: 'root' })\nexport class BettingOfferService {\n constructor(\n private bettingOfferApi: BettingOfferApi,\n private offerGroupingService: OfferGroupingService,\n ) {}\n\n async getFixtureViewModel(\n id: string,\n excludeMarkets: boolean,\n statsMode: string,\n includePrecreated: boolean,\n isBettingInsightsEnabled = false,\n includeRelatedFixtures?: boolean,\n marketGroupIds?: string,\n ): Promise {\n const fixtureView = await this.bettingOfferApi.getFixtureView(\n id,\n statsMode,\n excludeMarkets,\n includePrecreated,\n isBettingInsightsEnabled,\n undefined,\n undefined,\n undefined,\n undefined,\n marketGroupIds,\n includeRelatedFixtures,\n );\n\n return this.createViewModel(fixtureView);\n }\n\n private async createViewModel(fixtureView?: FixtureView): Promise {\n if (!fixtureView?.fixture) {\n return;\n }\n const fixture = fixtureView.fixture;\n\n const grouping = await firstValueFrom(this.offerGroupingService.getFixtureGrouping(fixture.sport.id, fixtureView.groupingVersion));\n\n if (!grouping) {\n throw new Error('Unable to load fixture grouping');\n }\n\n return {\n fixture,\n splitFixtures: fixtureView.splitFixtures,\n preCreatedOffer: fixtureView.preCreatedOffer,\n grouping,\n bettingInsights: fixtureView.bettingInsights,\n };\n }\n}\n","import { Injectable } from '@angular/core';\n\nimport { Option, OptionMarket, Price } from '@cds/betting-offer';\nimport { BetBuilderFixture, BetBuilderMarketInfo } from '@cds/betting-offer/domain-specific/bet-builder';\nimport { BetBuilderConfig } from '@frontend/sports/common/client-config-data-access';\nimport { NativePrice } from '@frontend/sports/odds/feature/native-price';\n\nimport { BetBuilderMarket, BetBuilderOption, BetBuilderPrecreatedModel, OptionPrice } from '../model/bet-builder.model';\n\n@Injectable({ providedIn: 'root' })\nexport class BetBuilderMapperService {\n constructor(private config: BetBuilderConfig) {}\n\n mapFixture(fixture?: BetBuilderFixture): BetBuilderPrecreatedModel | undefined {\n if (!fixture || !fixture.sourceFixture) {\n return;\n }\n\n const markets = this.mapMarkets(fixture);\n const fixtureId = fixture.sourceFixture.id;\n\n return {\n fixtureId,\n hasMarkets: fixture.sourceFixture.optionMarkets.length !== 0,\n ambassadorsMarkets: markets.filter((m) => m.ambassador && m.visibleOnBetBuilderTab),\n promotedMarkets: markets.filter((m) => !m.ambassador && m.visibleOnBetBuilderTab),\n themedTabMarkets: markets.filter((m) => m.themedTab),\n };\n }\n\n private mapMarkets(response: BetBuilderFixture): BetBuilderMarket[] {\n if (!response.sourceFixture) {\n return [];\n }\n let markets = response.sourceFixture.optionMarkets.map((market) => this.mapGeneralMarket(market));\n markets = this.mapPrecreatedMarketLongId(response.precreatedMarketsInfo, markets);\n\n return this.mapAmbassador(response.precreatedMarketsInfo, markets);\n }\n\n private mapPrecreatedMarketLongId(precreatedMarketsInfo: BetBuilderMarketInfo[], markets: BetBuilderMarket[]): BetBuilderMarket[] {\n return markets.map((market) => {\n market.longIds = precreatedMarketsInfo.find((marketInfo) => marketInfo.id === market.id)!.selections;\n\n return market;\n });\n }\n\n private mapAmbassador(precreatedMarketsInfo: BetBuilderMarketInfo[], markets: BetBuilderMarket[]): BetBuilderMarket[] {\n precreatedMarketsInfo.forEach((marketInfo) => {\n markets.find((market) => market.id === marketInfo.id)!.visibleOnBetBuilderTab = marketInfo.visibleOnBetBuilderTab;\n if (!marketInfo.ambassadorKey) {\n return;\n }\n\n const ambassador = this.config.ambassadors.find((a) => a.key === marketInfo.ambassadorKey);\n if (!ambassador) {\n return;\n }\n markets.find((market) => market.id === marketInfo.id)!.ambassador = {\n image: ambassador.image,\n name: ambassador.name,\n singleAmbassadorTitle: ambassador.singleAmbassadorTitle,\n };\n });\n\n return markets;\n }\n\n private mapGeneralMarket(responseMarket: OptionMarket): BetBuilderMarket {\n return {\n id: responseMarket.id,\n name: responseMarket.name.value,\n nameSign: responseMarket.name.sign,\n option: this.mapOption(responseMarket.options[0]),\n };\n }\n\n private mapOption(responseOption: Option): BetBuilderOption {\n return {\n id: responseOption.id,\n optionPrice: this.mapOptionPrice(responseOption.price),\n };\n }\n\n private mapOptionPrice(optionPrice: Price): OptionPrice {\n return {\n id: optionPrice.id,\n nativePrice: NativePrice.fromNativePrice(optionPrice),\n };\n }\n}\n","import { Injectable } from '@angular/core';\n\nimport { BetBuilderFixture } from '@cds/betting-offer/domain-specific/bet-builder';\nimport { VirtualCompetition, VirtualCompetitionGroup } from '@cds/betting-offer/tags';\nimport { StatisticsMode } from '@cds/query-objects';\nimport { DetailedFixtureFactory, DetailedGroupingFactory } from '@frontend/sports/betting-offer/feature/fixture-factories';\nimport {\n EnhancedFixtureViewGroupingConfiguration,\n EventModel,\n EventOptionGroup,\n EventOptionGroupType,\n EventOptionSet,\n EventOptionSetHeaderTab,\n ExtendedDisplayType,\n MyMarketModel,\n OptionGroup,\n PRECREATED_BAB_EMPTY_THEME_ID,\n ParticipantInfo,\n SetTab,\n getPitcherByParticipantType,\n getRankByParticipantType,\n getWinLossStatsByParticipantType,\n} from '@frontend/sports/betting-offer/feature/model';\nimport { BettingOfferService } from '@frontend/sports/betting-offer/feature/offer-service';\nimport { BetBuilderConfig, BettingInsightsConfiguration } from '@frontend/sports/common/client-config-data-access';\nimport { LocalStoreService, NativeAppService } from '@frontend/vanilla/core';\nimport { includes, isEmpty, maxBy, sumBy, uniq, uniqBy } from 'lodash-es';\n\nimport { BetBuilderMapperService } from '../betbuilder/services/betbuilder-mapper.service';\nimport { StatisticsConfigService } from '../statistics/statistics-config.service';\nimport { PrepareOptionGroupsParams, ResultEventModel, VirtualCompetitionDetails } from './event-details-common.models';\n\n@Injectable({ providedIn: 'root' })\nexport class EventDetailsService {\n private storageKey = 'sia_my_markets';\n participantInfos: ParticipantInfo[];\n teamRecordsEnabled = false;\n teamRanksEnabled = false;\n teamPitchersEnabled = false;\n\n constructor(\n private bettingOffer: BettingOfferService,\n private fixtureFactory: DetailedFixtureFactory,\n private groupingFactory: DetailedGroupingFactory,\n private betBuilderMapper: BetBuilderMapperService,\n private betBuilderConfig: BetBuilderConfig,\n private bettingInsightsConfig: BettingInsightsConfiguration,\n public statisticsConfigService: StatisticsConfigService,\n private localStore: LocalStoreService,\n private nativeAppService: NativeAppService,\n ) {}\n\n async getEvent(\n id: string,\n excludeMarkets: boolean = false,\n includeRelatedFixtures?: boolean,\n marketGroupIds?: string,\n ): Promise {\n const cdsEntity = await this.bettingOffer.getFixtureViewModel(\n id,\n excludeMarkets,\n StatisticsMode.None,\n false,\n this.bettingInsightsConfig.isEnabled,\n includeRelatedFixtures,\n marketGroupIds,\n );\n if (!cdsEntity) {\n return;\n }\n\n return this.fixtureFactory.create({\n fixture: cdsEntity.fixture,\n splitFixtures: cdsEntity.splitFixtures,\n grouping: cdsEntity.grouping,\n loadEventSitemap: true,\n bettingInsights: cdsEntity.bettingInsights,\n });\n }\n\n async getModel(id: string): Promise {\n const statsMode = this.statisticsConfigService.getStatisticsModesBySportId();\n const cdsEntity = await this.bettingOffer.getFixtureViewModel(\n id,\n false,\n statsMode,\n this.betBuilderConfig.isPrecreatedBetBuilderEnabled,\n this.bettingInsightsConfig.isEnabled,\n );\n if (!cdsEntity) {\n return;\n }\n\n this.addBetbuilderToGrouping(cdsEntity.grouping, cdsEntity.preCreatedOffer);\n\n return {\n model: this.fixtureFactory.create({\n fixture: cdsEntity.fixture,\n splitFixtures: cdsEntity.splitFixtures,\n grouping: cdsEntity.grouping,\n precreated: cdsEntity.preCreatedOffer,\n loadEventSitemap: true,\n bettingInsights: cdsEntity.bettingInsights,\n excludePriceboostedMarketGrouping: !this.nativeAppService.isTerminal,\n }),\n preCreated: this.betBuilderMapper.mapFixture(cdsEntity.preCreatedOffer),\n virtualCompetitionDetails: this.mapVirtualCompetitionDetails(\n cdsEntity.fixture.virtualCompetition,\n cdsEntity.fixture.virtualCompetitionGroup,\n ),\n fixture: cdsEntity.fixture,\n betBuilderFixture: cdsEntity.preCreatedOffer,\n };\n }\n\n private mapVirtualCompetitionDetails(\n virtualCompetition?: VirtualCompetition,\n virtualCompetitionGroup?: VirtualCompetitionGroup,\n ): VirtualCompetitionDetails | undefined {\n if (!virtualCompetition) {\n return;\n }\n\n return {\n id: virtualCompetition.id,\n name: virtualCompetition.name.value,\n group: virtualCompetitionGroup ? { id: virtualCompetitionGroup.id, name: virtualCompetitionGroup.name.value } : undefined,\n };\n }\n\n addBetbuilderToGrouping(\n grouping: EnhancedFixtureViewGroupingConfiguration,\n preCreatedOffer?: BetBuilderFixture,\n ): EnhancedFixtureViewGroupingConfiguration {\n const themedTabs = uniq(preCreatedOffer?.precreatedMarketsInfo.map((x) => x.themedTab?.toUpperCase()));\n const sportSpecificThemedTabs = this.betBuilderConfig.themedTabs[grouping.sportId] || [];\n const themedTabNames = sportSpecificThemedTabs.filter((i) => includes(themedTabs, i.key?.toUpperCase().trim())).map((s) => s.name);\n const marketTabId = maxBy(grouping.marketTabs.map((x) => x.id));\n if (marketTabId) {\n let count = marketTabId + 1;\n if (themedTabNames?.length) {\n themedTabNames.forEach((tabs) => {\n grouping.marketTabs.push({ id: count, name: tabs || '', sortOrder: count });\n count++;\n });\n }\n\n grouping.marketTabs.push({ id: count, name: '', sortOrder: count });\n }\n\n return grouping;\n }\n\n prepareEventOptionGroups(params: PrepareOptionGroupsParams): EventOptionGroup[] {\n const { event, groupedSetsIds, tag, setTags, isSgp } = params;\n const activeTab = params.activeTab ?? SetTab.All;\n let optionGroups = [];\n\n if (activeTab === SetTab.MyMarkets) {\n optionGroups = this.getOptionGroupForMyMarkets(event);\n } else if (groupedSetsIds && groupedSetsIds.length > 0) {\n optionGroups = this.getMultipleOptionGroups(event.optionGroups, groupedSetsIds);\n\n if (setTags && setTags.length >= 1) {\n optionGroups = optionGroups.filter(\n (group) =>\n group.detailedGrouping?.tags &&\n group.detailedGrouping.tags.length >= setTags.length &&\n setTags.every((t, index) => t === group.detailedGrouping.tags![index].key) &&\n (isSgp ? group.isBetBuilder : true),\n );\n }\n } else {\n optionGroups =\n activeTab === SetTab.All\n ? event.optionGroups\n : event.optionGroups.filter((group) => group.detailedGrouping && group.detailedGrouping.marketSet === activeTab);\n\n if (tag) {\n optionGroups = optionGroups.filter((group) => group.detailedGrouping?.tags?.length && group.detailedGrouping.tags[0].key === tag);\n }\n\n if (isSgp) {\n optionGroups = optionGroups.filter((group) => group.isBetBuilder);\n }\n }\n\n optionGroups = this.distinctOptionGroups(optionGroups);\n\n if (!params.isPrecreatedBABTab) {\n optionGroups = this.filterBetBuilderThemedOptionGroups(optionGroups);\n }\n\n if (params.isPrecreatedBABTab) {\n if (params.event.precreatedOptionGroups?.length) {\n return this.groupingFactory.transformEventGroupsPerPrecreatedGroup(optionGroups, params.event.precreatedOptionGroups);\n }\n\n return this.groupingFactory.groupMarkets(optionGroups, true, true);\n }\n\n return this.groupingFactory.groupMarkets(optionGroups);\n }\n\n private filterBetBuilderThemedOptionGroups(optionGroups: EventOptionGroup[]): EventOptionGroup[] {\n const isThemeTabAssigned = optionGroups.some(\n (og) =>\n og.detailedGrouping.displayType === ExtendedDisplayType.BetBuilder &&\n og.detailedGrouping.marketTabId !== PRECREATED_BAB_EMPTY_THEME_ID,\n );\n if (isThemeTabAssigned)\n return optionGroups.filter((optionGroup) => optionGroup.detailedGrouping.marketTabId !== PRECREATED_BAB_EMPTY_THEME_ID);\n\n return optionGroups;\n }\n\n isPreCreatedBABTab(event: EventModel, activeTab: string | SetTab = SetTab.All): boolean {\n return event.optionSets.find((t) => t.id === Number(activeTab))?.isPrecreated!;\n }\n\n getOptionGroupForMyMarkets(event: EventModel): EventOptionGroup[] {\n const myMarkets = this.localStore.get(this.storageKey) as MyMarketModel;\n if ((event.live && myMarkets?.live) || (!event.live && myMarkets?.prematch)) {\n //@ts-ignore allow dynamic accessor property, please don't use this, instead, use typing.\n const sportMarkets = event.live ? myMarkets.live[event.sport.id] : myMarkets.prematch[event.sport.id];\n\n if (sportMarkets) {\n return event.optionGroups.filter((group) => {\n if (group.detailedGrouping) {\n const market = sportMarkets.find(\n (sMarket: OptionGroup) =>\n sMarket.market_group_id === group.detailedGrouping.marketSet &&\n sMarket.tv1_market_group_item === group.detailedGrouping.marketOrder,\n );\n if (market) {\n group.position = market.position;\n\n return true;\n }\n\n return false;\n }\n\n return false;\n });\n }\n }\n\n return [];\n }\n\n prepareEventOptionSetHeaderTab(set: EventOptionSet, optionGroups: EventOptionGroup[]): EventOptionSetHeaderTab {\n return {\n id: set.id,\n name: set.name,\n count: optionGroups.length,\n optionGroups,\n };\n }\n\n calculateEventOptionGroupsCount(event: EventModel): number {\n const optionGroups = this.prepareEventOptionGroups({ event });\n\n return sumBy(optionGroups, (value) => value.getSubGroups(EventOptionGroupType.Period).length || 1);\n }\n\n private distinctOptionGroups(optionGroups: EventOptionGroup[]): EventOptionGroup[] {\n return uniqBy(\n optionGroups,\n (item) =>\n `${item.id}-${item.templateId}-${item.detailedGrouping.displayType}${item.detailedGrouping.sixPackGridId ? '-' + item.detailedGrouping.sixPackGridId : ''}`,\n );\n }\n\n private getMultipleOptionGroups(optionGroups: EventOptionGroup[], groupIds: number[]): EventOptionGroup[] {\n const groups: EventOptionGroup[] = [];\n groupIds.forEach((g) => {\n groups.push(...optionGroups.filter((group) => group.detailedGrouping && group.detailedGrouping.marketSet === g));\n });\n\n return groups;\n }\n\n setParticipantInfos(event: EventModel): ParticipantInfo[] {\n this.teamPitchersEnabled = !!this.statisticsConfigService.pitcherEnabledSports[event.sport.id];\n this.teamRecordsEnabled = !isEmpty(this.statisticsConfigService.teamRecordsFormat[event.sport.id]);\n this.teamRanksEnabled = !!this.statisticsConfigService.teamRankEnabledSports[event.sport.id];\n\n return (this.participantInfos = event.participants.map((participant) => {\n const rank = this.teamRanksEnabled ? getRankByParticipantType(event, participant.type) : undefined;\n const pitcher = !event.scoreboard.started && this.teamPitchersEnabled ? getPitcherByParticipantType(event, participant.type) : undefined;\n const teamRecordsFormat = this.statisticsConfigService.teamRecordsFormat[event.sport.id];\n const winLossStats =\n !event.scoreboard.started && this.teamRecordsEnabled\n ? getWinLossStatsByParticipantType(event, participant.type!, teamRecordsFormat)\n : undefined;\n\n return {\n participant,\n rank,\n winLossStats,\n pitcher,\n } as ParticipantInfo;\n }));\n }\n}\n","export enum ParticipantPickType {\n Win = 1,\n Forecast = 2,\n CombinationForecast = 3,\n Tricast = 4,\n CombinationTricast = 5,\n Freeform = 6,\n}\n\nexport enum SourceOfPick {\n Upsell = 'Upsell',\n OutrightsGrid = 'OutrightsGrid',\n Other = 'Other',\n BetStationMarquee = 'BetStationMarquee',\n EventDetails = 'EventDetails',\n LineSwitcher = 'LineSwitcher',\n MultiGenerator = 'MultiGenerator',\n WorldCupHub = 'WorldCupHub',\n EmbeddedBetBuilder = 'EmbeddedBetBuilder',\n OverlayBetBuilder = 'OverlayBetBuilder',\n TeamGrouping = 'TeamGrouping',\n}\n","import { ChangeDetectorRef, ElementRef, inject } from '@angular/core';\nimport { takeUntilDestroyed } from '@angular/core/rxjs-interop';\n\nimport { type Observable, debounceTime, filter, fromEvent, merge, of, switchMap, tap } from 'rxjs';\n\nexport function rxHostListener(event: string): Observable {\n const cdr = inject(ChangeDetectorRef);\n\n // Listen to event\n return fromEvent(inject(ElementRef).nativeElement, event).pipe(\n debounceTime(0),\n tap(() => cdr.markForCheck()), // Trigger CD like @HostListener would\n takeUntilDestroyed(), // Unsubscribe\n );\n}\n\n/*\n * @internal\n **/\nexport function rxHostPressedListener(): Observable {\n return merge(\n rxHostListener('click'),\n rxHostListener('keyup').pipe(\n switchMap((x) => {\n return x.code === 'Space' || x.code === 'Enter' ? of(true) : of(null);\n }),\n filter(Boolean),\n ),\n ).pipe(debounceTime(0));\n}\n","/**\n * @license Angular v18.2.6\n * (c) 2010-2024 Google LLC. https://angular.io/\n * License: MIT\n */\n\nimport * as i0 from '@angular/core';\nimport { Directive, InjectionToken, forwardRef, Optional, Inject, ɵisPromise, ɵisSubscribable, ɵRuntimeError, Self, computed, signal, untracked, EventEmitter, Input, Host, SkipSelf, booleanAttribute, ChangeDetectorRef, Output, Injectable, inject, NgModule, Version } from '@angular/core';\nimport { ɵgetDOM } from '@angular/common';\nimport { from, forkJoin, Subject } from 'rxjs';\nimport { map } from 'rxjs/operators';\n\n/**\n * Base class for all ControlValueAccessor classes defined in Forms package.\n * Contains common logic and utility functions.\n *\n * Note: this is an *internal-only* class and should not be extended or used directly in\n * applications code.\n */\nlet BaseControlValueAccessor = /*#__PURE__*/(() => {\n class BaseControlValueAccessor {\n constructor(_renderer, _elementRef) {\n this._renderer = _renderer;\n this._elementRef = _elementRef;\n /**\n * The registered callback function called when a change or input event occurs on the input\n * element.\n * @nodoc\n */\n this.onChange = _ => {};\n /**\n * The registered callback function called when a blur event occurs on the input element.\n * @nodoc\n */\n this.onTouched = () => {};\n }\n /**\n * Helper method that sets a property on a target element using the current Renderer\n * implementation.\n * @nodoc\n */\n setProperty(key, value) {\n this._renderer.setProperty(this._elementRef.nativeElement, key, value);\n }\n /**\n * Registers a function called when the control is touched.\n * @nodoc\n */\n registerOnTouched(fn) {\n this.onTouched = fn;\n }\n /**\n * Registers a function called when the control value changes.\n * @nodoc\n */\n registerOnChange(fn) {\n this.onChange = fn;\n }\n /**\n * Sets the \"disabled\" property on the range input element.\n * @nodoc\n */\n setDisabledState(isDisabled) {\n this.setProperty('disabled', isDisabled);\n }\n static {\n this.ɵfac = function BaseControlValueAccessor_Factory(__ngFactoryType__) {\n return new (__ngFactoryType__ || BaseControlValueAccessor)(i0.ɵɵdirectiveInject(i0.Renderer2), i0.ɵɵdirectiveInject(i0.ElementRef));\n };\n }\n static {\n this.ɵdir = /* @__PURE__ */i0.ɵɵdefineDirective({\n type: BaseControlValueAccessor\n });\n }\n }\n return BaseControlValueAccessor;\n})();\n/*#__PURE__*/(() => {\n (typeof ngDevMode === \"undefined\" || ngDevMode) && void 0;\n})();\n/**\n * Base class for all built-in ControlValueAccessor classes (except DefaultValueAccessor, which is\n * used in case no other CVAs can be found). We use this class to distinguish between default CVA,\n * built-in CVAs and custom CVAs, so that Forms logic can recognize built-in CVAs and treat custom\n * ones with higher priority (when both built-in and custom CVAs are present).\n *\n * Note: this is an *internal-only* class and should not be extended or used directly in\n * applications code.\n */\nlet BuiltInControlValueAccessor = /*#__PURE__*/(() => {\n class BuiltInControlValueAccessor extends BaseControlValueAccessor {\n static {\n this.ɵfac = /* @__PURE__ */(() => {\n let ɵBuiltInControlValueAccessor_BaseFactory;\n return function BuiltInControlValueAccessor_Factory(__ngFactoryType__) {\n return (ɵBuiltInControlValueAccessor_BaseFactory || (ɵBuiltInControlValueAccessor_BaseFactory = i0.ɵɵgetInheritedFactory(BuiltInControlValueAccessor)))(__ngFactoryType__ || BuiltInControlValueAccessor);\n };\n })();\n }\n static {\n this.ɵdir = /* @__PURE__ */i0.ɵɵdefineDirective({\n type: BuiltInControlValueAccessor,\n features: [i0.ɵɵInheritDefinitionFeature]\n });\n }\n }\n return BuiltInControlValueAccessor;\n})();\n/*#__PURE__*/(() => {\n (typeof ngDevMode === \"undefined\" || ngDevMode) && void 0;\n})();\n/**\n * Used to provide a `ControlValueAccessor` for form controls.\n *\n * See `DefaultValueAccessor` for how to implement one.\n *\n * @publicApi\n */\nconst NG_VALUE_ACCESSOR = /*#__PURE__*/new InjectionToken(ngDevMode ? 'NgValueAccessor' : '');\nconst CHECKBOX_VALUE_ACCESSOR = {\n provide: NG_VALUE_ACCESSOR,\n useExisting: /*#__PURE__*/forwardRef(() => CheckboxControlValueAccessor),\n multi: true\n};\n/**\n * @description\n * A `ControlValueAccessor` for writing a value and listening to changes on a checkbox input\n * element.\n *\n * @usageNotes\n *\n * ### Using a checkbox with a reactive form.\n *\n * The following example shows how to use a checkbox with a reactive form.\n *\n * ```ts\n * const rememberLoginControl = new FormControl();\n * ```\n *\n * ```\n * \n * ```\n *\n * @ngModule ReactiveFormsModule\n * @ngModule FormsModule\n * @publicApi\n */\nlet CheckboxControlValueAccessor = /*#__PURE__*/(() => {\n class CheckboxControlValueAccessor extends BuiltInControlValueAccessor {\n /**\n * Sets the \"checked\" property on the input element.\n * @nodoc\n */\n writeValue(value) {\n this.setProperty('checked', value);\n }\n static {\n this.ɵfac = /* @__PURE__ */(() => {\n let ɵCheckboxControlValueAccessor_BaseFactory;\n return function CheckboxControlValueAccessor_Factory(__ngFactoryType__) {\n return (ɵCheckboxControlValueAccessor_BaseFactory || (ɵCheckboxControlValueAccessor_BaseFactory = i0.ɵɵgetInheritedFactory(CheckboxControlValueAccessor)))(__ngFactoryType__ || CheckboxControlValueAccessor);\n };\n })();\n }\n static {\n this.ɵdir = /* @__PURE__ */i0.ɵɵdefineDirective({\n type: CheckboxControlValueAccessor,\n selectors: [[\"input\", \"type\", \"checkbox\", \"formControlName\", \"\"], [\"input\", \"type\", \"checkbox\", \"formControl\", \"\"], [\"input\", \"type\", \"checkbox\", \"ngModel\", \"\"]],\n hostBindings: function CheckboxControlValueAccessor_HostBindings(rf, ctx) {\n if (rf & 1) {\n i0.ɵɵlistener(\"change\", function CheckboxControlValueAccessor_change_HostBindingHandler($event) {\n return ctx.onChange($event.target.checked);\n })(\"blur\", function CheckboxControlValueAccessor_blur_HostBindingHandler() {\n return ctx.onTouched();\n });\n }\n },\n features: [i0.ɵɵProvidersFeature([CHECKBOX_VALUE_ACCESSOR]), i0.ɵɵInheritDefinitionFeature]\n });\n }\n }\n return CheckboxControlValueAccessor;\n})();\n/*#__PURE__*/(() => {\n (typeof ngDevMode === \"undefined\" || ngDevMode) && void 0;\n})();\nconst DEFAULT_VALUE_ACCESSOR = {\n provide: NG_VALUE_ACCESSOR,\n useExisting: /*#__PURE__*/forwardRef(() => DefaultValueAccessor),\n multi: true\n};\n/**\n * We must check whether the agent is Android because composition events\n * behave differently between iOS and Android.\n */\nfunction _isAndroid() {\n const userAgent = ɵgetDOM() ? ɵgetDOM().getUserAgent() : '';\n return /android (\\d+)/.test(userAgent.toLowerCase());\n}\n/**\n * @description\n * Provide this token to control if form directives buffer IME input until\n * the \"compositionend\" event occurs.\n * @publicApi\n */\nconst COMPOSITION_BUFFER_MODE = /*#__PURE__*/new InjectionToken(ngDevMode ? 'CompositionEventMode' : '');\n/**\n * The default `ControlValueAccessor` for writing a value and listening to changes on input\n * elements. The accessor is used by the `FormControlDirective`, `FormControlName`, and\n * `NgModel` directives.\n *\n *\n * @usageNotes\n *\n * ### Using the default value accessor\n *\n * The following example shows how to use an input element that activates the default value accessor\n * (in this case, a text field).\n *\n * ```ts\n * const firstNameControl = new FormControl();\n * ```\n *\n * ```\n * \n * ```\n *\n * This value accessor is used by default for `` and `