{"version":3,"file":"chunk-967bma6x.js","sources":["packages/sports/common/betslip/core/betslip-state.ts","packages/sports/common/betslip/base/store/state.ts","packages/sports/common/betslip/base/store/selectors.ts","packages/sports/libs/odds/feature/src/odds/fraction.ts","packages/sports/libs/odds/feature/src/odds/odds.ts","packages/sports/libs/odds/feature/src/odds/odds-converter.ts","packages/sports/libs/odds/feature/src/odds/operations.ts","packages/sports/common/betslip/core/picks/betslip-pick.ts","packages/sports/common/betslip/core/picks/betslip-v2-pick.ts","packages/sports/common/betslip/core/picks/betslip-v2-option-market-pick.ts","packages/sports/common/betslip/core/picks/betslip-bet-builder-pick.ts","packages/sports/common/betslip/modules/validation/picks/betslip-pick-change-info.ts","packages/sports/common/betslip/core/picks/combo-prevention.ts","packages/sports/common/betslip/core/picks/minimum-combination.ts","packages/sports/common/betslip/core/picks/betslip-v1-pick.ts","packages/sports/common/betslip/core/picks/betslip-v2-standard-pick.ts","packages/sports/common/betslip/core/price.ts","packages/sports/common/betslip/core/picks/betslip-v2-participant-pick.ts","packages/sports/common/betslip/core/picks/betslip-v2-win-participant-pick.ts","packages/sports/common/betslip/core/picks/sport-specific/betslip-v2-golf-picks.ts","packages/sports/common/betslip/core/picks/sport-specific/betslip-v2-horse-race-picks.ts","packages/sports/common/betslip/modules/picks/services/betslip-pick-storage-loader.ts","packages/sports/common/betslip/core/group-pick-helpers.ts","packages/sports/common/betslip/core/picks/group-leg-state.ts","packages/sports/common/betslip/core/picks/betslip-group-pick.ts","packages/sports/common/betslip/core/picks/betslip-unknown-pick.ts","packages/sports/common/betslip/core/utils.ts","packages/sports/common/betslip/core/betslip-type.ts","packages/sports/common/betslip/model/over-ask/over-ask.ts","packages/sports/common/betslip/modules/picks/services/betslip-pick-helpers.ts","packages/sports/common/betslip/modules/picks/selectors.ts","packages/sports/common/betslip/modules/types/selectors.ts","packages/sports/common/betslip/modules/validation/state.ts","packages/sports/web/app/src/store-persist/storage-persister.ts","packages/sports/common/betslip/core/external-betslip-actions.ts","packages/sports/common/betslip/modules/types/base/selectors.ts","packages/sports/common/betslip/modules/validation/errors/bet-placement-error-icon.ts","packages/sports/common/betslip/modules/validation/errors/betslip-error.ts","packages/sports/common/betslip/modules/validation/errors/bet-placement-error.ts","packages/sports/common/betslip/modules/validation/errors/user/user-error.ts","packages/sports/common/betslip/modules/validation/errors/stake-error.ts","packages/sports/common/betslip/modules/validation/errors/general/under-minimum-stake.ts","packages/sports/common/betslip/modules/validation/errors/pre-check/pre-check-error.ts","packages/sports/common/betslip/modules/validation/errors/pre-check/under-minimum-stake-pre-check-error.ts","packages/sports/common/betslip/modules/stake/utils.ts","packages/sports/common/betslip/modules/single-bet/selectors.ts","packages/sports/common/betslip/modules/bet-generation/betslip-system-type.ts","packages/sports/common/betslip/modules/system-bet/models.ts","packages/sports/common/betslip/modules/bet-generation/slip-type.ts","packages/sports/common/betslip/modules/bet-generation/services/slip.utils.ts","packages/sports/common/betslip/modules/linear-bet-builder/selectors.ts","packages/sports/common/betslip/modules/stake/selectors.ts","packages/sports/common/betslip/modules/system-bet/selectors.ts","packages/sports/common/betslip/modules/validation/errors/general/min-selections-betbuilder-error.ts","packages/sports/common/betslip/modules/validation/errors/result/result-error.ts","packages/sports/common/betslip/modules/validation/services/utils/betslip-errors-state-utils.ts","packages/sports/common/betslip/modules/reward-tokens/reward-tokens.model.ts","packages/sports/common/betslip/modules/reward-tokens/services/reward-tokens.utils.ts","packages/sports/common/betslip/modules/system-bet/services/system-bet.constants.ts","packages/sports/common/betslip/modules/betplacement/services/stake/stake-utils.ts","packages/sports/common/betslip/modules/betplacement/services/stake/linear-stake-utils.ts","packages/sports/common/betslip/modules/picks/services/linear-betslip-pick.utils.ts","packages/sports/common/betslip/modules/validation/errors/client-error-type.ts","packages/sports/common/betslip/modules/validation/errors/general/betandget/reward-tokens-bet-builder-error.ts","packages/sports/common/betslip/modules/validation/errors/general/betandget/reward-tokens-min-stake-error.ts","packages/sports/common/betslip/modules/validation/errors/general/betandget/reward-tokens-selection-level-odds-error.ts","packages/sports/common/betslip/modules/validation/errors/general/betandget/reward-tokens-total-odds-error.ts","packages/sports/common/betslip/modules/validation/errors/general/group-pick-error.ts","packages/sports/common/betslip/modules/validation/errors/general/reward-tokens-forbidden-error.ts","packages/sports/common/betslip/modules/validation/errors/general/reward-tokens-game-type-error.ts","packages/sports/common/betslip/modules/validation/errors/general/reward-tokens-max-stake-error.ts","packages/sports/common/betslip/modules/validation/errors/general/reward-tokens-max-total-odds-error.ts","packages/sports/common/betslip/modules/validation/errors/general/reward-tokens-min-legs-error.ts","packages/sports/common/betslip/modules/validation/errors/general/reward-tokens-selection-level-max-odds-error.ts","packages/sports/common/betslip/modules/validation/errors/general/reward-tokens-slip-type-error.ts","packages/sports/common/betslip/modules/validation/errors/general/technical-error.ts","packages/sports/common/betslip/modules/validation/errors/general/incorrect-bet-count.ts","packages/sports/common/betslip/modules/validation/errors/pre-check/incorrect-options-count-pre-check-error.ts","packages/sports/common/betslip/modules/validation/errors/pre-check/no-betbuilder-system-pre-check-error.ts","packages/sports/common/betslip/modules/validation/errors/user/not-enough-money.ts","packages/sports/common/betslip/modules/validation/errors/user/overall-max-win-per-user.ts","packages/sports/common/betslip/modules/validation/services/utils/stake-limit-errors-info-utils.ts","packages/sports/common/betslip/modules/validation/services/utils/betslip-errors-utils.ts","packages/sports/common/betslip/modules/validation/selectors.ts","packages/sports/common/betslip/modules/reward-tokens/services/linear-reward-tokens.utils.ts","packages/sports/common/betslip/modules/reward-tokens/services/betstation-reward-tokens.utils.ts","packages/sports/common/betslip/modules/reward-tokens/selectors.ts","packages/sports/web/app/src/acca-boost-base/acca-boost-utils.ts","packages/sports/common/betslip/modules/betslip-bar/services/betslip-bar-visibility.service.ts","packages/sports/common/betslip/modules/hidden-market/hidden-market.selectors.ts","packages/sports/common/betslip/base/store/actions.ts","packages/sports/common/betslip/modules/settings/actions.ts","packages/sports/common/betslip/modules/edit-bet/actions.ts","packages/sports/common/betslip/integration/sports-injection-services.ts","packages/sports/common/betslip/integration/betslip-integration.service.ts","packages/sports/web/app/src/competition-list/competition-route.service.ts","node_modules/@angular/material/fesm2022/button.mjs","node_modules/@angular/material/fesm2022/snack-bar.mjs","packages/sports/libs/toaster/feature/src/toaster.component.ts","packages/sports/libs/toaster/feature/src/toaster.service.ts","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/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/event-list-shared/sport/tournament-groups-participants.component.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.component.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.component.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.component.html","packages/sports/web/app/src/event-list-shared/sport/fixture-list-header.component.ts","packages/sports/web/app/src/event-list-shared/sport/virtual-competition.service.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/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/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/store/src/grid.actions.ts","packages/sports/libs/common/core/feature/money/src/lib/currency.pipe.ts","packages/sports/libs/grid/core/feature/store/src/grid.state.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/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/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/libs/tracking/feature/performance-tracker/src/performance-profiling.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/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/libs/common/core/utils/rxjs/src/lib/buffer-with-size.ts","packages/sports/web/app/src/statistics/statistics-config.service.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/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/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/competition-header-widget.component.html","packages/sports/web/app/src/event-list-shared/sport/competition-header-widget.component.ts","packages/sports/web/app/src/event-list-shared/sport/competition-teams-widget.component.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/my-bets-base/summary-api.service.ts","packages/sports/web/app/src/my-bets-base/my-bets-summary.service.ts","packages/sports/web/app/src/bottom-nav/bottom-nav-actions.service.ts","packages/sports/web/app/src/router/router.exports.ts","packages/sports/web/app/src/navigation-core/navigation-core.models.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.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/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/vanilla/lib/features/launch-darkly/src/launch-darkly.client-config.ts","node_modules/launchdarkly-js-client-sdk/dist/ldclient.es.js","packages/vanilla/lib/features/launch-darkly/src/launch-darkly.service.ts","packages/vanilla/lib/features/launch-darkly/src/launch-darkly-bootstrap.service.ts","packages/vanilla/lib/features/launch-darkly/src/launch-darkly.feature.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-tracking.service.ts","packages/sports/web/app/src/widget/core/widget-layout-hook.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-base/models/pick-models.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/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/src/account-menu-onboarding.service.ts","packages/vanilla/lib/shared/account-menu/src/account-menu-data.service.ts","packages/vanilla/lib/shared/account-menu/src/account-menu.models.ts","packages/vanilla/lib/shared/account-menu/src/account-menu-tasks.service.ts","packages/vanilla/lib/shared/account-menu/src/account-menu-router.ts","packages/vanilla/lib/shared/account-menu/src/account-menu.html","packages/vanilla/lib/shared/account-menu/src/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.component.ts","packages/vanilla/lib/shared/confirm-popup/src/confirm-popup.html","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","packages/sports/web/app/src/router/router.resolver.ts","packages/sports/web/app/src/sitemap/sitemap-routing.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"],"sourcesContent":["export enum BetslipState {\n // ADDING/REMOVING picks\n Edit = 'Edit',\n // After place bet button, waiting for result from server\n Placing = 'Placing',\n // Place bet result display state\n Result = 'Result',\n}\n","import { BetslipDisplayMode } from '@frontend/sports/types/components/bet-placement';\nimport { IUiRootState } from 'packages/sports/web/app/src/ui-manager/ui-manager.state';\n\nimport { BetslipState } from '../../core/betslip-state';\nimport { IBetslipSourcePageState } from '../../model/edit-mybet/edit-bet-init';\nimport { IAffiliateState } from '../../modules/affiliates/state/affiliate.state';\nimport { IBetslipPlaceBetState, IBetslipPlaceButtonState } from '../../modules/betplacement/state';\nimport { IHiddenMarketState } from '../../modules/hidden-market/hidden-market.state';\nimport { IInfoMessagesState } from '../../modules/info-messages//state';\nimport { ILineSwitcherState } from '../../modules/line-switcher/state';\nimport { IBetslipNotificationState } from '../../modules/notifications/state';\nimport { OddsPreferencePopupState } from '../../modules/odds-preference-popup/odds-preference-state.model';\nimport { IBetslipOverAskState } from '../../modules/over-ask/state';\nimport { IBetslipPickState } from '../../modules/picks/state';\nimport { IQuickBetState } from '../../modules/quick-bet/quick-bet.state';\nimport { IQuickDepositState } from '../../modules/quick-deposit/quick-deposit.state';\nimport { IBetslipResultState } from '../../modules/result/state';\nimport { IRewardTokensState } from '../../modules/reward-tokens/state';\nimport { ISettingsState } from '../../modules/settings/state';\nimport { ITrackingState } from '../../modules/tracking/state';\nimport { IBetslipTypeState } from '../../modules/types/state';\nimport { IBetslipErrorsState } from '../../modules/validation/state';\n\nexport interface IBetslipState {\n base: IBetslipBaseState;\n picks: IBetslipPickState;\n types: IBetslipTypeState;\n errors: IBetslipErrorsState;\n rewardTokens: IRewardTokensState;\n placeBet: IBetslipPlaceBetState;\n result: IBetslipResultState;\n settings: ISettingsState;\n placeButton: IBetslipPlaceButtonState;\n overAskState: IBetslipOverAskState;\n lineSwitcher: ILineSwitcherState;\n notifications: IBetslipNotificationState;\n affiliateState: IAffiliateState;\n quickDeposit: IQuickDepositState;\n quickBet: IQuickBetState;\n oddsPreferencePopupState: OddsPreferencePopupState;\n hiddenMarket: IHiddenMarketState;\n sourcePageState: IBetslipSourcePageState;\n infoMessages: IInfoMessagesState;\n tracking: ITrackingState;\n}\n\nexport interface IBetslipBaseState {\n state: BetslipState;\n isBetslipKeypadOpened: boolean;\n displayMode: BetslipDisplayMode;\n linearModeCheckboxesEnabled: boolean;\n isSportcastAsComboEnabled: boolean;\n}\n\nexport const defaultBetslipBaseState: IBetslipBaseState = {\n state: BetslipState.Edit,\n isBetslipKeypadOpened: false,\n displayMode: BetslipDisplayMode.Tabbed,\n linearModeCheckboxesEnabled: true,\n isSportcastAsComboEnabled: false,\n};\n\nexport const betslipFeatureKey = 'betslip';\n\nexport interface IBetslipRootState extends IUiRootState {\n betslip: IBetslipState;\n}\n","import { BetslipDisplayMode } from '@frontend/sports/types/components/bet-placement';\nimport { createFeatureSelector, createSelector } from '@ngrx/store';\n\nimport { BetslipState } from '../../core/betslip-state';\nimport { IBetslipState, betslipFeatureKey } from './state';\n\nexport const betslipSelector = createFeatureSelector(betslipFeatureKey);\n\nexport const betslipBaseSelector = createSelector(betslipSelector, (b) => b.base);\nexport const betslipBaseStateSelector = createSelector(betslipSelector, (b) => b.base.state);\nexport const betslipDisplayModeSelector = createSelector(betslipSelector, (b) => b.base.displayMode);\nexport const selectIsLinearBetslip = createSelector(betslipDisplayModeSelector, (b) => b === BetslipDisplayMode.Linear);\nexport const selectIsLinearCheckboxesEnabled = createSelector(betslipSelector, (b) => b.base.linearModeCheckboxesEnabled);\n\nexport const selectIsPlacingBet = createSelector(betslipSelector, (b) => b.base.state === BetslipState.Placing);\n\nexport const betslipStageSelector = createSelector(betslipBaseSelector, (baseState) => baseState.state);\n","import { Decimal } from 'decimal.js';\n\nexport class Fraction {\n private static numberEps = 0.0000001;\n numerator: Decimal;\n denominator: Decimal;\n isSimplified = false;\n\n constructor(n: number | string | Decimal, d: number | string | Decimal = 1, isSimplified: boolean = false) {\n this.numerator = new Decimal(n);\n this.denominator = new Decimal(d);\n this.isSimplified = this.denominator.equals(1) || isSimplified;\n }\n\n static create(val: { numerator: number; denominator: number } | Fraction): Fraction {\n if (val instanceof Fraction) {\n return val.copy();\n }\n\n return Fraction.fromJSON(val);\n }\n\n static fromJSON(val: { numerator: number; denominator: number }): Fraction {\n return new Fraction(val.numerator, val.denominator);\n }\n\n static toCommonDenominator(\n first: Fraction,\n second: Fraction,\n ): {\n firstNumerator: Decimal;\n secondNumerator: Decimal;\n denominator: Decimal;\n } {\n if (first.denominator.equals(second.denominator)) {\n return {\n firstNumerator: first.numerator,\n secondNumerator: second.numerator,\n denominator: first.denominator,\n };\n } else {\n const firstNumerator = first.numerator.times(second.denominator);\n const secondNumerator = second.numerator.times(first.denominator);\n const resultDenominator = first.denominator.times(second.denominator);\n\n return {\n firstNumerator,\n secondNumerator,\n denominator: resultDenominator,\n };\n }\n }\n\n private static isNumber(value: Fraction | Decimal | number): value is number {\n return typeof value === 'number';\n }\n\n private static toInt(value: number): number {\n if (Math.ceil(value) - value < Fraction.numberEps) {\n // When value is like 1.99999999 => 2\n // 0.2+0.7+0.1 = 0.99999999 => 1\n return Math.ceil(value);\n }\n if (value - Math.floor(value) < Fraction.numberEps) {\n // When value is like 2.00000001 => 2\n return Math.floor(value);\n }\n\n return NaN;\n }\n\n private static numberToFraction(value: number): Fraction {\n const intVal = Fraction.toInt(value);\n if (!isNaN(intVal)) {\n return new Fraction(intVal);\n }\n const strValue = value.toString(10);\n const dotIndex = strValue.indexOf('.');\n const decimalLength = strValue.substring(dotIndex + 1).length;\n const denominator = Math.pow(10, decimalLength);\n const numerator = +strValue.replace('.', '');\n\n return new Fraction(numerator, denominator).simplify();\n }\n\n private static getCommonDivisor(a: Decimal, b: Decimal): Decimal {\n let temp: Decimal;\n while (!b.equals(0)) {\n temp = b;\n b = a.modulo(b);\n a = temp;\n }\n\n return a;\n }\n\n static empty(): Fraction {\n return new Fraction(0, 0, true);\n }\n\n static toFraction(value: Decimal | number | null): Fraction {\n if (value == null) {\n return Fraction.empty();\n }\n if (Fraction.isNumber(value)) {\n // number path.\n return Fraction.numberToFraction(value);\n } else {\n if (value.lessThanOrEqualTo(0)) {\n return Fraction.empty();\n }\n const dp = value.decimalPlaces();\n const denominator = Math.pow(10, dp);\n\n return new Fraction(value.times(denominator), denominator).simplify();\n }\n }\n\n static fromString(value: string): Fraction {\n if (value.indexOf('/') === -1) {\n return Fraction.empty();\n }\n const values = value.split('/');\n const numerator = new Decimal(values[0]);\n const denominator = new Decimal(values[1]);\n\n return new Fraction(numerator, denominator);\n }\n\n add(other: Fraction | number): Fraction {\n if (this.isEmpty()) {\n return Fraction.empty();\n }\n if (Fraction.isNumber(other)) {\n const intVal = Fraction.toInt(other);\n if (!isNaN(intVal)) {\n // Go to fast path (no common denominator) where if we have an integer value.\n // We multiply the value with the denominator and add to the numerator\n // 2/3 + 2 = 2/3 + 6 / 3 = (2 + 6) / 3\n return new Fraction(this.numerator.add(this.denominator.times(intVal)), this.denominator);\n }\n other = Fraction.numberToFraction(other);\n }\n if (other.isEmpty()) {\n return Fraction.empty();\n }\n const convert = Fraction.toCommonDenominator(this, other);\n\n return new Fraction(convert.firstNumerator.plus(convert.secondNumerator), convert.denominator);\n }\n\n subtract(other: Fraction | number): Fraction {\n if (this.isEmpty()) {\n return Fraction.empty();\n }\n if (Fraction.isNumber(other)) {\n const intVal = Fraction.toInt(other);\n if (!isNaN(intVal)) {\n // Go to fast path (no common denominator) where if we have an integer value.\n // We multiply the value with the denominator and subtract from the numerator\n // 2/3 - 2 = 2/3 - 6 / 3 = (2 - 6) / 3\n return new Fraction(this.numerator.minus(this.denominator.times(intVal)), this.denominator);\n }\n other = Fraction.numberToFraction(other);\n }\n if (other.isEmpty()) {\n return Fraction.empty();\n }\n const convert = Fraction.toCommonDenominator(this, other);\n\n return new Fraction(convert.firstNumerator.minus(convert.secondNumerator), convert.denominator);\n }\n\n multiply(other: Fraction | number): Fraction {\n if (this.isEmpty()) {\n return Fraction.empty();\n }\n if (Fraction.isNumber(other)) {\n other = Fraction.numberToFraction(other);\n }\n if (other.isEmpty()) {\n return Fraction.empty();\n }\n\n return new Fraction(this.numerator.times(other.numerator), this.denominator.times(other.denominator));\n }\n\n divide(other: Fraction | number): Fraction {\n if (this.isEmpty()) {\n return Fraction.empty();\n }\n if (Fraction.isNumber(other)) {\n other = Fraction.numberToFraction(other);\n }\n if (other.isEmpty()) {\n return Fraction.empty();\n }\n\n return new Fraction(this.numerator.times(other.denominator), this.denominator.times(other.numerator));\n }\n\n getValue(): Decimal {\n if (this.isEmpty()) {\n return new Decimal(0);\n }\n const n = new Decimal(this.numerator);\n const d = new Decimal(this.denominator);\n\n return n.dividedBy(d);\n }\n\n toString(): string {\n return `${this.numerator}/${this.denominator}`;\n }\n\n simplify(): Fraction {\n if (this.isSimplified) {\n return this;\n } else {\n const greatestCommonDivisor = Fraction.getCommonDivisor(this.numerator, this.denominator);\n\n return new Fraction(this.numerator.dividedBy(greatestCommonDivisor), this.denominator.dividedBy(greatestCommonDivisor), true);\n }\n }\n\n /**\n * Compares fraction to another fraction or number\n *\n * @returns -1 if lower, 0 if equal, 1 if greater\n */\n compare(other: Fraction | number): number {\n const o = Fraction.isNumber(other) ? Fraction.toFraction(other) : other;\n const eq = Fraction.toCommonDenominator(this, o);\n\n return eq.firstNumerator.lessThan(eq.secondNumerator) ? -1 : eq.firstNumerator.greaterThan(eq.secondNumerator) ? 1 : 0;\n }\n\n lessThan(other: Fraction | number): boolean {\n return this.compare(other) === -1;\n }\n\n greaterThan(other: Fraction | number): boolean {\n return this.compare(other) === 1;\n }\n\n lessOrEqualThan(other: Fraction | number): boolean {\n return this.compare(other) <= 0;\n }\n\n greaterOrEqualThan(other: Fraction | number): boolean {\n return this.compare(other) >= 0;\n }\n\n equal(other: Fraction | number): boolean {\n return this.compare(other) === 0;\n }\n\n isEmpty(): boolean {\n return this.denominator.equals(0);\n }\n\n toJSON(): { numerator: number; denominator: number } {\n return {\n numerator: this.numerator.toNumber(),\n denominator: this.denominator.toNumber(),\n };\n }\n\n copy(): Fraction {\n return new Fraction(this.numerator, this.denominator, this.isSimplified);\n }\n}\n","import { Decimal } from 'decimal.js';\n\nimport { Fraction } from './fraction';\n\nexport interface Odds {\n decimals: number;\n fractional: { numerator: number; denominator: number };\n moneyline: number;\n}\n\nexport interface CalculatedOdds {\n decimals: Decimal;\n fractional: Fraction;\n moneyline: Decimal;\n}\n\nexport const emptyOdds: Odds = Object.freeze({\n decimals: 0,\n fractional: { numerator: 0, denominator: 0 },\n moneyline: 0,\n});\nexport const emptyCalculatedOdds: CalculatedOdds = {\n decimals: new Decimal(0),\n fractional: Fraction.empty(),\n moneyline: new Decimal(0),\n};\n","import { decimalOddsToAmerican } from '@frontend/sports/odds/feature/converters';\nimport { OddsDisplayFormat } from '@frontend/sports/types/models/user-settings';\nimport { Decimal } from 'decimal.js';\n\nimport { Fraction } from './fraction';\nimport type { CalculatedOdds, Odds } from './odds';\n\n/**\n * There are 3 main types of odds\n * 1. Decimals or European odds - a win based Odds * Stake = Total Possible payout\n * 2. Fractional or UK odds - a profit based Odds * Stake = Possible Profit ( Possible payout - Stake )\n * 3. Moneyline or US odds - a money based odds. Can have possitive or negative values.\n * When positive defines how much money will be the profit if the stake is 100.\n * When negative defines what should be the stake to have 100 profit\n */\nexport class OddsConverter {\n /**\n * Converting decimal to fration odds becomes by extracting 1 ( remove stake from the win) and convert to fraction\n *\n * @param decimals Decimal odds\n */\n static decimalToFraction(decimals: Decimal): Fraction {\n return Fraction.toFraction(decimals.minus(1)).simplify();\n }\n\n /**\n * Convert decimal to US by removing 1 ( to get the profit ) and if the result is:\n * - Greater or equal than 1 then multiply by 100\n * - Less than 1 then -100 / value\n *\n * @param decimals\n * @param precision\n */\n static decimalToUS(decimals: Decimal, precision: number = 10): Decimal {\n return decimalOddsToAmerican(decimals.toDecimalPlaces(precision));\n }\n\n /**\n * Convert fractional odds to decimal is by adding 1 ( add the stake ) and convert to decimal value\n *\n * @param fractionOdds\n */\n static fractionToDecimal(fractionOdds: Fraction): Decimal {\n return fractionOdds.add(1).getValue();\n }\n\n /**\n * Convert fractional to moneyline:\n * When fractional greater or equal to 1: Multiply by 100 and convert to decimals\n * When fractional less than 1 then we divide -100 to the fraction and convert to decimals\n *\n * @param fractionOdds\n * @param precision\n */\n static fractionToUS(fractionOdds: Fraction, precision: number = 2): Decimal {\n const us = fractionOdds.greaterOrEqualThan(1)\n ? fractionOdds.multiply(100).getValue()\n : new Fraction(100, 1).divide(fractionOdds).multiply(-1).getValue();\n\n return us.toDecimalPlaces(precision);\n }\n\n static usToDecimal(usOdds: Decimal, precision: number = 2): Decimal {\n return usOdds.lessThan(0)\n ? new Decimal(1).minus(new Decimal(100).dividedBy(usOdds).toDecimalPlaces(precision))\n : usOdds.dividedBy(100).add(1);\n }\n\n static usToFraction(usOdds: Decimal, precision: number = 2): Fraction {\n return usOdds.lessThan(0)\n ? Fraction.toFraction(new Decimal(-100).dividedBy(usOdds).toDecimalPlaces(precision))\n : Fraction.toFraction(usOdds.dividedBy(100));\n }\n\n /**\n * Convert decimal odds to the decimal value of the EW\n *\n * @param decimals\n * @param eachWay\n */\n static decimalToEachWay(decimals: Decimal, eachWay: Fraction): Decimal {\n return OddsConverter.decimalToFraction(decimals).multiply(eachWay).add(1).getValue();\n }\n\n /**\n * Convert decimal odds to the fractional value of the EW\n *\n * @param fracion\n * @param eachWay\n */\n static fractionToEachWay(fraction: Fraction, eachWay: Fraction): Fraction {\n return fraction.multiply(eachWay).simplify();\n }\n\n /**\n * Convert us odds to US value of each way\n */\n static usToEachWay(us: Decimal, eachWay: Fraction): Decimal {\n const fraction = OddsConverter.usToFraction(us, 7).multiply(eachWay).simplify();\n\n return OddsConverter.fractionToUS(fraction);\n }\n\n static convertOdds(\n targetFormat: OddsDisplayFormat,\n sourceFormat: OddsDisplayFormat,\n odds: Odds | CalculatedOdds,\n ): Partial | Partial {\n if (targetFormat === sourceFormat) {\n return odds;\n }\n switch (targetFormat) {\n case OddsDisplayFormat.EU: {\n if (sourceFormat === OddsDisplayFormat.UK) {\n return {\n decimals: OddsConverter.fractionToDecimal(Fraction.create(odds.fractional)).toNumber(),\n };\n }\n if (sourceFormat === OddsDisplayFormat.US) {\n return {\n decimals: OddsConverter.usToDecimal(new Decimal(odds.moneyline)).toNumber(),\n };\n }\n break;\n }\n case OddsDisplayFormat.UK: {\n if (sourceFormat === OddsDisplayFormat.EU) {\n return {\n fractional: OddsConverter.decimalToFraction(new Decimal(odds.decimals)).simplify().toJSON(),\n };\n }\n if (sourceFormat === OddsDisplayFormat.US) {\n return {\n fractional: OddsConverter.usToFraction(new Decimal(odds.moneyline)).simplify().toJSON(),\n };\n }\n break;\n }\n case OddsDisplayFormat.US: {\n if (sourceFormat === OddsDisplayFormat.EU) {\n return {\n moneyline: OddsConverter.decimalToUS(new Decimal(odds.decimals)).toNumber(),\n };\n }\n if (sourceFormat === OddsDisplayFormat.UK) {\n return {\n moneyline: OddsConverter.fractionToUS(Fraction.create(odds.fractional)).toNumber(),\n };\n }\n }\n }\n throw new Error('Invalid odds formats');\n }\n}\n","import { Odds as BposOdds, OddsFormat } from '@bpos';\nimport { Odds as BposCommonOdds } from '@bpos/common/bet-placement';\nimport { OddsDisplayFormat } from '@frontend/sports/types/models/user-settings';\nimport { Decimal } from 'decimal.js';\nimport { isArray } from 'lodash-es';\n\nimport { Fraction } from './fraction';\nimport { CalculatedOdds, Odds, emptyCalculatedOdds, emptyOdds } from './odds';\nimport { OddsConverter } from './odds-converter';\n\nexport class OddsOperations {\n static readonly OddsOne = OddsOperations.fromDecimalValue(new Decimal(1));\n\n static createOdds(price: { odds?: number; numerator?: number; denominator?: number; americanOdds?: number }): Odds {\n if (!price.odds && !price.numerator && !price.denominator && !price.americanOdds) {\n return emptyOdds;\n }\n\n return {\n decimals: price.odds || 0,\n fractional:\n price.numerator != null && price.denominator != null\n ? { numerator: price.numerator, denominator: price.denominator }\n : { numerator: 0, denominator: 0 },\n moneyline: price.americanOdds || 0,\n };\n }\n\n static createOdds2(odds: BposCommonOdds | undefined, oddsFormat: OddsFormat): Odds {\n if (!odds) return emptyOdds;\n switch (oddsFormat) {\n case OddsFormat.European: {\n return OddsOperations.createOdds({ odds: odds.european });\n }\n case OddsFormat.British: {\n return OddsOperations.createOdds({ numerator: odds.british.numerator, denominator: odds.british.denominator });\n }\n case OddsFormat.American: {\n return OddsOperations.createOdds({ americanOdds: odds.american });\n }\n default:\n return emptyOdds;\n }\n }\n\n static createOdds3(odds: BposOdds | undefined): Odds {\n switch (odds?.oddsFormat) {\n case OddsFormat.European: {\n return OddsOperations.createOdds({ odds: odds.european });\n }\n case OddsFormat.British: {\n return OddsOperations.createOdds({ numerator: odds.british.numerator, denominator: odds.british.denominator });\n }\n case OddsFormat.American: {\n return OddsOperations.createOdds({ americanOdds: odds.american });\n }\n default:\n return emptyOdds;\n }\n }\n\n static isOddsEmpty(odds: Odds | CalculatedOdds): boolean {\n return odds === emptyOdds || odds === emptyCalculatedOdds;\n }\n\n static isOddsValid(odds: Odds | CalculatedOdds): boolean {\n if (OddsOperations.isOddsEmpty(odds)) {\n return false;\n }\n\n return (\n OddsOperations.isDecimalOddsValid(odds.decimals) ||\n OddsOperations.isFractionalOddsValid(odds.fractional) ||\n OddsOperations.isMoneyLineOddsValid(odds.moneyline)\n );\n }\n\n static isDecimalOddsValid(decimals: number | Decimal): boolean {\n return typeof decimals === 'number' ? decimals > 1 : decimals.greaterThan(1);\n }\n\n static isFractionalOddsValid(fractional: { numerator: number; denominator: number } | Fraction): boolean {\n return OddsConverter.fractionToDecimal(Fraction.create(fractional)).greaterThan(1);\n }\n\n static isMoneyLineOddsValid(moneyline: number | Decimal): boolean {\n if (typeof moneyline === 'number') {\n return moneyline >= 100 || (moneyline < -100 && moneyline >= -100000); // I think -100000 is quite smaller number we don't have so small odds\n }\n\n return moneyline.greaterThanOrEqualTo(100) || (moneyline.lessThan(-100) && moneyline.greaterThan(-100000));\n }\n\n static sum(odds: CalculatedOdds[]): CalculatedOdds {\n if (odds.length === 1) {\n return OddsOperations.isOddsEmpty(odds[0]) ? emptyCalculatedOdds : odds[0];\n }\n\n let decimals = new Decimal(0);\n const initialValue = new Fraction(0, 1);\n let fractional = initialValue;\n let moneyline = new Decimal(0);\n for (const odd of odds) {\n if (OddsOperations.isOddsEmpty(odd)) {\n return emptyCalculatedOdds;\n }\n\n decimals = decimals.add(odd.decimals);\n fractional = fractional === initialValue ? odd.fractional : fractional.add(1).add(odd.fractional.add(1)).subtract(1);\n moneyline = moneyline.add(OddsConverter.usToDecimal(odd.moneyline, 10));\n }\n\n return {\n decimals,\n fractional,\n moneyline: OddsConverter.decimalToUS(moneyline),\n };\n }\n\n static getTotalOdds(odds: CalculatedOdds[] | CalculatedOdds, round: boolean): CalculatedOdds {\n return OddsOperations.multiply(isArray(odds) ? odds : [odds, this.OddsOne], round);\n }\n\n static multiply(odds: CalculatedOdds[], round: boolean): CalculatedOdds {\n if (odds.length === 1) {\n return OddsOperations.isOddsEmpty(odds[0]) ? emptyCalculatedOdds : odds[0];\n }\n\n let decimals = new Decimal(1);\n let fractional = new Fraction(0, 1);\n let moneyline = new Decimal(1);\n for (const odd of odds) {\n if (OddsOperations.isOddsEmpty(odd)) {\n return emptyCalculatedOdds;\n }\n\n decimals = decimals.times(odd.decimals);\n fractional = fractional.add(1).multiply(odd.fractional.add(1)).subtract(1);\n moneyline = moneyline.times(OddsConverter.usToDecimal(odd.moneyline, 10));\n }\n\n return {\n decimals: round ? decimals.toDecimalPlaces(2, Decimal.ROUND_HALF_UP) : decimals,\n fractional,\n moneyline: OddsConverter.decimalToUS(moneyline),\n };\n }\n\n static equal(first: Odds, second: Odds, oddsFormat: OddsFormat = OddsFormat.None): boolean {\n switch (oddsFormat) {\n case OddsFormat.British:\n return Fraction.fromJSON(first.fractional).equal(Fraction.fromJSON(second.fractional));\n case OddsFormat.European:\n return first.decimals === second.decimals;\n case OddsFormat.American:\n return first.moneyline === second.moneyline;\n default:\n return (\n first.decimals === second.decimals &&\n Fraction.fromJSON(first.fractional).equal(Fraction.fromJSON(second.fractional)) &&\n first.moneyline === second.moneyline\n );\n }\n }\n\n static equal2(first: CalculatedOdds, second: CalculatedOdds): boolean {\n return first.decimals.equals(second.decimals) && first.fractional.equal(second.fractional) && first.moneyline.equals(second.moneyline);\n }\n\n static toCalculatedOdds(nativeOdds: Odds | undefined): CalculatedOdds {\n if (!nativeOdds) {\n return emptyCalculatedOdds;\n }\n\n const calculatedOdds = {\n decimals: nativeOdds.decimals ? new Decimal(nativeOdds.decimals) : new Decimal(0),\n fractional: nativeOdds.fractional ? Fraction.fromJSON(nativeOdds.fractional) : Fraction.empty(),\n moneyline: nativeOdds.moneyline ? new Decimal(nativeOdds.moneyline) : new Decimal(0),\n };\n\n //Return emptyCalculatedOdds if the calculated odds is equal to it to make future comparison accurate\n //e.g. isOddsEmpty should return true in this case\n if (this.equal2(calculatedOdds, emptyCalculatedOdds)) {\n return emptyCalculatedOdds;\n }\n\n return calculatedOdds;\n }\n\n static toDecimalValue(odds: CalculatedOdds, format?: OddsDisplayFormat): Decimal {\n switch (format) {\n case OddsDisplayFormat.UK:\n return OddsConverter.fractionToDecimal(odds.fractional);\n case OddsDisplayFormat.US:\n return OddsConverter.usToDecimal(odds.moneyline, 10);\n default:\n return odds.decimals;\n }\n }\n\n static fromDecimalValue(decimals: Decimal): CalculatedOdds {\n return {\n decimals,\n fractional: OddsConverter.decimalToFraction(decimals),\n moneyline: OddsConverter.decimalToUS(decimals),\n };\n }\n\n static fromDecimalsToOdds(value: number): Odds {\n return OddsOperations.createOdds({\n odds: value,\n numerator: new Decimal(value).minus(1).toNumber(),\n denominator: 1,\n });\n }\n\n static convertOddsFormat(oddsFormat: OddsFormat): OddsDisplayFormat {\n switch (oddsFormat) {\n case OddsFormat.American:\n return OddsDisplayFormat.US;\n case OddsFormat.British:\n return OddsDisplayFormat.UK;\n case OddsFormat.European:\n return OddsDisplayFormat.EU;\n case OddsFormat.None:\n return OddsDisplayFormat.EU;\n }\n }\n\n static convertToBposOddsFormat(oddsFormat: OddsDisplayFormat = OddsDisplayFormat.EU): OddsFormat {\n switch (oddsFormat) {\n case OddsDisplayFormat.EU:\n return OddsFormat.European;\n case OddsDisplayFormat.UK:\n return OddsFormat.British;\n case OddsDisplayFormat.US:\n return OddsFormat.American;\n }\n }\n\n static toOdds(nativeOdds: CalculatedOdds): Odds {\n return {\n decimals: nativeOdds.decimals ? nativeOdds.decimals.toNumber() : 0,\n\n fractional: nativeOdds.fractional\n ? { numerator: nativeOdds.fractional.numerator.toNumber(), denominator: nativeOdds.fractional.denominator.toNumber() }\n : { numerator: 0, denominator: 0 },\n\n moneyline: nativeOdds.moneyline ? nativeOdds.moneyline.toNumber() : 0,\n };\n }\n\n static lessThan(first: CalculatedOdds, second: CalculatedOdds, oddsFormat: OddsFormat = OddsFormat.None): boolean {\n switch (oddsFormat) {\n case OddsFormat.British:\n return first.fractional.lessThan(second.fractional);\n case OddsFormat.American:\n return first.moneyline.lessThan(second.moneyline);\n default:\n return first.decimals.lessThan(second.decimals);\n }\n }\n\n static greaterThan(first: CalculatedOdds, second: CalculatedOdds, oddsFormat: OddsFormat = OddsFormat.None): boolean {\n switch (oddsFormat) {\n case OddsFormat.British:\n return first.fractional.greaterThan(second.fractional);\n case OddsFormat.American:\n return first.moneyline.greaterThan(second.moneyline);\n default:\n return first.decimals.greaterThan(second.decimals);\n }\n }\n}\n","import { FixtureViewType, TotalsPrefix } from '@cds/betting-offer';\nimport { EventParticipant } from '@frontend/sports/betting-offer/feature/model';\nimport { SportConstant } from '@frontend/sports/common/core/data-access/constants';\nimport { OddsOperations } from '@frontend/sports/odds/feature';\n\nimport { BetslipGroupInformation } from '../groups/betslip-group-information';\nimport { PickId, pickIdFactory } from './pick-id';\nimport {\n IAcceptedPrice,\n IBetslipPickMarket,\n IBetslipPickStorage,\n IBetslipV2Option,\n IBoostedPrice,\n IPickTracking,\n IPrice,\n PickOddsState,\n PickSubType,\n PriceType,\n ResultStatus,\n} from './pick-models';\nimport { SignedName } from './signed-name.model';\n\nexport abstract class BetslipPick {\n id: PickId;\n\n // is the pick in the betting offer\n isAvailable = true;\n\n /**\n * Pick result status. It can be set to something else after trying to do bet placement and the pick is resulted.\n * When result status is !== of OPEN then pick is treated as closed.\n */\n resultStatus: ResultStatus = ResultStatus.Open;\n\n priceType: PriceType;\n\n /* Accepted odds from the customer, when current price type is Fixed when price is not fixed accepted Odds are undefined. */\n acceptedPrice: IAcceptedPrice;\n\n boostedPriceDetails: IBoostedPrice | undefined;\n\n market: IBetslipPickMarket;\n\n priceHistory: IPrice[];\n\n subscriptionContext: string[] = [];\n\n tracking: IPickTracking;\n\n contextualInfo?: string;\n\n liveAlert?: boolean;\n\n parentLinkedEventId?: string;\n\n siblingOption?: IBetslipV2Option;\n\n /**\n * Indicates that the pick is part of the new customer offer (hidden market)\n */\n isNewCustomerOfferPick: boolean;\n\n highlighted?: boolean;\n\n totalsPrefix?: TotalsPrefix;\n fromHybridFixture?: boolean;\n protected _groupInfo?: BetslipGroupInformation;\n\n get groupInfo() {\n return this._groupInfo;\n }\n\n protected constructor() {\n this.priceHistory = [];\n }\n\n protected abstract isPriceVisible(): boolean;\n\n abstract isMarketVisible(): boolean;\n\n private isVisible(): boolean {\n return this.isAvailable && this.isPriceVisible() && this.isMarketVisible();\n }\n\n protected isOpen(): boolean {\n return this.resultStatus === ResultStatus.Open;\n }\n\n get currentPrice(): IPrice | undefined {\n return this.prices[0];\n }\n\n get oddsState(): PickOddsState {\n if (!this.isOpen()) {\n return PickOddsState.Closed;\n }\n\n if (!this.isVisible()) {\n return PickOddsState.Locked;\n }\n\n return PickOddsState.Open;\n }\n\n abstract get isLive(): boolean;\n\n abstract copy(): BetslipPick;\n\n // abstract updateFromMessage(m: MessageEnvelope): void;\n\n abstract get prices(): IPrice[];\n\n abstract get eventName(): SignedName;\n\n abstract get marketName(): SignedName;\n\n abstract get optionName(): SignedName;\n\n abstract get sportId(): SportConstant;\n\n abstract get isVirtual(): boolean;\n\n abstract get eventDate(): Date;\n\n abstract get regionName(): SignedName;\n\n abstract get competitionName(): SignedName;\n\n abstract get competitionId(): number;\n\n abstract get sportName(): SignedName;\n\n abstract get leagueName(): SignedName;\n\n abstract get partitionId(): number | undefined;\n\n abstract get eventParticipants(): EventParticipant[] | undefined;\n\n abstract get eventViewType(): FixtureViewType | undefined;\n\n betCount(): number {\n return 1;\n }\n\n /**\n * toJSON func should do a deep copy of the object to storage, we need that because when we do copy we get toJSON result and init pick from it.,\n */\n toJSON(): IBetslipPickStorage {\n return {\n id: this.id.toString(),\n priceHistory: this.priceHistory,\n priceType: this.priceType,\n acceptedPrice: this.acceptedPrice,\n isNewCustomerOffer: this.isNewCustomerOfferPick,\n market: this.market\n ? {\n id: this.market.id,\n isVisible: this.market.isVisible,\n isBetBuilderEnabled: this.market.isBetBuilderEnabled,\n }\n : null,\n subscriptionContext: this.subscriptionContext,\n pickSubType: PickSubType.None,\n tracking: this.tracking,\n boostedPriceDetails: this.boostedPriceDetails,\n liveAlert: this.liveAlert,\n groupInfo: this.groupInfo,\n parentLinkedEventId: this.parentLinkedEventId ?? this.id.parentLinkedEventId,\n totalsPrefix: this.totalsPrefix,\n fromHybridFixture: this.fromHybridFixture,\n };\n }\n\n protected initPropertiesFromJSON(value: IBetslipPickStorage): void {\n this.id = pickIdFactory(value.id);\n this.priceHistory = value.priceHistory;\n this.priceType = value.priceType;\n this.acceptedPrice = value.acceptedPrice;\n this.isNewCustomerOfferPick = value.isNewCustomerOffer;\n this.subscriptionContext = value.subscriptionContext;\n if (value.market) {\n this.market = {\n id: value.market.id,\n isVisible: value.market.isVisible,\n isBetBuilderEnabled: value.market.isBetBuilderEnabled,\n };\n }\n this.tracking = value.tracking ?? {};\n this.boostedPriceDetails = value.boostedPriceDetails;\n this.liveAlert = value.liveAlert;\n this._groupInfo = value.groupInfo;\n\n this.parentLinkedEventId = value.parentLinkedEventId;\n this.totalsPrefix = value.totalsPrefix;\n this.fromHybridFixture = value.fromHybridFixture;\n }\n\n getOddsAcceptance(): boolean {\n if (this.currentPrice?.nativeOdds === this.acceptedPrice.price?.nativeOdds) {\n return true;\n } else if (!this.currentPrice?.nativeOdds || !this.acceptedPrice.price?.nativeOdds) {\n return false;\n }\n\n return OddsOperations.equal(this.currentPrice?.nativeOdds, this.acceptedPrice.price?.nativeOdds);\n }\n\n setGroupInfo(groupInfo: BetslipGroupInformation) {\n this._groupInfo = groupInfo;\n }\n}\n","import { getPartition } from '@frontend/sports/betting-offer/feature/model';\nimport { SportConstant } from '@frontend/sports/common/core/data-access/constants';\n\nimport { BetslipPick } from './betslip-pick';\nimport { V2PickId } from './pick-id';\nimport { IBetslipV2PickFixture, IBetslipV2PickStorage, PriceType } from './pick-models';\nimport { SignedName } from './signed-name.model';\n\nexport abstract class BetslipV2Pick extends BetslipPick {\n override id: V2PickId;\n fixture: IBetslipV2PickFixture;\n\n static isPick(pick: BetslipPick): pick is BetslipV2Pick {\n return pick instanceof BetslipV2Pick;\n }\n\n protected constructor() {\n super();\n this.priceType = PriceType.Fixed;\n }\n\n isMarketVisible(): boolean {\n return this.market.isVisible;\n }\n\n protected isPriceVisible(): boolean {\n return true;\n }\n\n protected override initPropertiesFromJSON(value: IBetslipV2PickStorage): void {\n super.initPropertiesFromJSON(value);\n this.fixture = {\n ...value.fixture,\n eventDate: new Date(value.fixture.eventDate),\n cutOffDate: new Date(value.fixture.cutOffDate),\n league: value.fixture.league,\n region: value.fixture.region,\n };\n }\n\n get leagueId(): number {\n if (this.fixture.league !== null) {\n return this.fixture.league.id;\n }\n\n return 0;\n }\n\n get leagueName(): SignedName {\n return (\n this.fixture.league?.name || {\n name: '',\n signature: '',\n }\n );\n }\n\n get regionId(): number {\n if (this.fixture.region !== null) {\n return this.fixture.region.id;\n }\n\n return 0;\n }\n\n get regionName(): SignedName {\n return (\n this.fixture.region?.name || {\n name: '',\n signature: '',\n }\n );\n }\n\n override isOpen(): boolean {\n if (!super.isOpen()) {\n return false;\n }\n\n return this.fixture.cutOffDate > new Date();\n }\n\n get eventName(): SignedName {\n return this.fixture.name;\n }\n\n get sportId(): SportConstant {\n return this.fixture.sportId;\n }\n\n get partitionId(): number | undefined {\n return getPartition(this.fixture.fixtureId);\n }\n\n get sportName(): SignedName {\n return this.fixture.sportName;\n }\n\n get eventDate(): Date {\n return this.fixture.eventDate;\n }\n\n get competitionName(): SignedName {\n return this.leagueName;\n }\n\n get competitionId(): number {\n return this.leagueId;\n }\n\n abstract override get isLive(): boolean;\n\n get isVirtual(): boolean {\n return this.fixture.virtual;\n }\n\n override toJSON(): IBetslipV2PickStorage {\n const base = super.toJSON();\n\n return {\n ...base,\n fixture: {\n ...this.fixture,\n eventDate: this.fixture.eventDate.toISOString(),\n cutOffDate: this.fixture.cutOffDate.toISOString(),\n league: this.fixture.league && {\n id: this.fixture.league.id,\n name: { ...this.fixture.league.name },\n realCompetitionId: this.fixture.league.realCompetitionId,\n },\n region: this.fixture.region && {\n id: this.fixture.region.id,\n name: { ...this.fixture.region.name },\n },\n },\n };\n }\n\n setMarketInvisible(): void {\n this.market.isVisible = false;\n }\n}\n","import { FixtureStage, FixtureViewType } from '@cds/betting-offer';\nimport { EventParticipant } from '@frontend/sports/betting-offer/feature/model';\nimport { cloneDeep } from 'lodash-es';\n\nimport { BetslipPick } from './betslip-pick';\nimport { BetslipV2Pick } from './betslip-v2-pick';\nimport { V2OptionMarketPickId } from './pick-id';\nimport { IBetslipV2Option, IBetslipV2OptionMarket, IBetslipV2OptionMarketPickStorage, IPlaceTerms, IPrice, ParticipantPickType } from './pick-models';\nimport { SignedName } from './signed-name.model';\n\n/**\n * General Option Market Pick\n */\nexport abstract class BetslipV2OptionMarketPick extends BetslipV2Pick {\n override market: IBetslipV2OptionMarket;\n option: IBetslipV2Option;\n options: IBetslipV2Option[];\n override id: V2OptionMarketPickId;\n isFromNewCustomerOffer: boolean;\n override siblingOption?: IBetslipV2Option;\n pickType?: ParticipantPickType;\n isFavouritePick?: boolean;\n\n constructor() {\n super();\n }\n\n override get eventParticipants(): EventParticipant[] | undefined {\n return this.fixture.participants;\n }\n\n override get eventViewType(): FixtureViewType | undefined {\n return this.fixture.viewType;\n }\n\n static override isPick(pick: BetslipPick): pick is BetslipV2OptionMarketPick {\n return pick instanceof BetslipV2OptionMarketPick;\n }\n\n protected override initPropertiesFromJSON(value: IBetslipV2OptionMarketPickStorage): void {\n super.initPropertiesFromJSON(value);\n this.market = value.market;\n this.isFavouritePick = value.isFavouritePick;\n this.isFromNewCustomerOffer = value.isFromNewCustomerOffer || false;\n\n this.option = {\n ...value.option,\n prices: cloneDeep(value.option.prices),\n isDraw: value.option.isDraw,\n };\n this.pickType = value.pickType;\n this.siblingOption = value.siblingOption;\n }\n\n get optionId(): number {\n return this.option.id;\n }\n\n get optionName(): SignedName {\n return this.option.name;\n }\n\n get marketName(): SignedName {\n return this.market.name;\n }\n\n get isLive(): boolean {\n return this.fixture.stage === FixtureStage.Live;\n }\n\n get isEachWay(): boolean {\n return this.market.isEachWay;\n }\n\n get placeTerms(): IPlaceTerms | null {\n return this.market.placeTerms;\n }\n\n get realCompetitionId(): number | undefined {\n return this.fixture.league?.realCompetitionId;\n }\n\n override isPriceVisible(): boolean {\n return this.option.isVisible && !!this.currentPrice && this.currentPrice.isVisible;\n }\n\n override isMarketVisible(): boolean {\n return this.market.isVisible;\n }\n\n override isOpen(): boolean {\n if (!super.isOpen() || this.market.isClosed) {\n return false;\n }\n\n return this.fixture.cutOffDate > new Date();\n }\n\n override toJSON(): IBetslipV2OptionMarketPickStorage {\n const base = super.toJSON();\n\n return {\n ...base,\n id: this.id.toString(),\n isFromNewCustomerOffer: this.isFromNewCustomerOffer,\n market: {\n ...this.market,\n },\n isFavouritePick: this.isFavouritePick,\n option: {\n ...this.option,\n },\n pickType: this.pickType,\n siblingOption: this.siblingOption!,\n };\n }\n\n get prices(): IPrice[] {\n return this.option.prices.filter((pr) => pr.marketId === this.market.id);\n }\n\n abstract override copy(): BetslipV2OptionMarketPick;\n}\n","import { FixtureViewType } from '@cds/betting-offer';\nimport { EventParticipant } from '@frontend/sports/betting-offer/feature/model';\n\nimport { BetslipPick } from './betslip-pick';\nimport { BetslipV2OptionMarketPick } from './betslip-v2-option-market-pick';\nimport { BetBuilderPickId } from './pick-id';\nimport { IBetslipBetBuilderPickStorage, IEventData, PickSubType } from './pick-models';\nimport { SignedName } from './signed-name.model';\n\nexport interface SportcastOption {\n name: string;\n longId: string;\n}\n\nexport class BetslipBetBuilderPick extends BetslipV2OptionMarketPick {\n override get eventParticipants(): EventParticipant[] | undefined {\n return this.event.event.participants;\n }\n\n override get eventViewType(): FixtureViewType | undefined {\n return this.event.event.viewType;\n }\n\n override id: BetBuilderPickId;\n eventId: string;\n event: IEventData;\n sportcastOptions: SportcastOption[];\n sportcastId?: number;\n betgeniusId?: number;\n betSlipUid?: string;\n useV2Key?: boolean;\n longIds?: string[];\n\n static override isPick(pick: BetslipPick): pick is BetslipBetBuilderPick {\n return pick instanceof BetslipBetBuilderPick;\n }\n\n static isEntainUiBetBuilderPick(pick: BetslipPick): pick is BetslipBetBuilderPick & boolean {\n return pick instanceof BetslipBetBuilderPick && !!pick.sportcastOptions?.length;\n }\n\n getLegCount(): number {\n return this.sportcastOptions?.length ? this.sportcastOptions.length : this.market.name.name.split(';').length;\n }\n\n static fromJSON(value: IBetslipBetBuilderPickStorage): BetslipBetBuilderPick {\n const pick = new BetslipBetBuilderPick();\n pick.initPropertiesFromJSON(value);\n\n return pick;\n }\n\n protected override initPropertiesFromJSON(value: IBetslipBetBuilderPickStorage): void {\n super.initPropertiesFromJSON(value);\n this.eventId = value.eventId;\n this.event = value.event && {\n ...value.event,\n event: {\n ...value.event.event,\n date: new Date(value.event.event.date),\n },\n };\n this.sportcastOptions = value.sportcastOptions;\n this.betgeniusId = value.betgeniusId;\n this.sportcastId = value.sportcastId;\n this.betSlipUid = value.betSlipUid;\n this.useV2Key = value.useV2key;\n this.longIds = value.longIds;\n }\n\n getLegs(): string[] {\n return this.market.name.name.split(';');\n }\n\n override betCount(): number {\n return 1;\n }\n\n override toJSON(): IBetslipBetBuilderPickStorage {\n const base = super.toJSON();\n\n return {\n ...base,\n eventId: this.eventId,\n event: this.event,\n pickSubType: PickSubType.BetBuilderPick,\n sportcastOptions: this.sportcastOptions,\n betgeniusId: this.betgeniusId,\n sportcastId: this.sportcastId,\n betSlipUid: this.betSlipUid,\n useV2key: this.useV2Key,\n longIds: this.longIds,\n };\n }\n\n copy(): BetslipBetBuilderPick {\n const copy = this.toJSON();\n\n return BetslipBetBuilderPick.fromJSON(copy);\n }\n\n override get regionName(): SignedName {\n return this.event.region.name;\n }\n\n override get leagueName(): SignedName {\n return this.event.league.name;\n }\n\n override get eventDate(): Date {\n return this.event.event.date;\n }\n}\n","export enum BetslipPickChangeType {\n OddsChange,\n VisibilityChange,\n LockChange,\n}\n\nexport interface BetslipPickChangeInfo {\n changeType?: BetslipPickChangeType;\n}\n","import { ComboPreventionType } from '@cds/betting-offer';\n\nexport enum CombinationPrevention {\n /// \n /// No value was chosen for ComboPrevention.\n /// \n Default = 0,\n\n /// \n /// This bet can be combined with any other bet.\n /// \n AllCombo = 1,\n\n /// \n /// This bet cannot be combined with any other bet.\n /// \n NoCombo = 2,\n\n /// \n /// This bet cannot be combined with a bet from the same sport.\n /// \n NoSportCombo = 3,\n\n /// \n /// This bet cannot be combined with a bet from the same league.\n /// \n NoLeagueCombo = 4,\n\n /// \n /// This bet cannot be combined with a bet from the same event.\n /// \n NoEventCombo = 6,\n\n /// \n /// This bet can only be combined with a bet from the same league but a different event.\n /// \n OnlySameLeagueDifferentEvent = 7,\n\n /// \n /// This bet can only be combined with live bets\n /// \n OnlyLiveEvents = 8,\n\n /// \n /// This bet can only be combined with live bets from a different event.\n /// \n OnlyDifferentLiveEvents = 9,\n\n /// \n /// This bet cannot be combined with a bet from the same competition.\n /// \n NoCompetitionCombo = 10,\n\n /// \n /// This bet cannot be combined with a bet from the same fixture.\n /// \n NoFixtureCombo = 11,\n\n /// \n /// This bet cannot be combined with a bet from the same competition group.\n /// \n NoGroupCombo = 12,\n}\n\nexport function toComboPrevention(status: ComboPreventionType): CombinationPrevention {\n switch (status) {\n case ComboPreventionType.AllCombo:\n return CombinationPrevention.AllCombo;\n case ComboPreventionType.AllLiveEvents:\n return CombinationPrevention.OnlyLiveEvents;\n case ComboPreventionType.NoCombo:\n return CombinationPrevention.NoCombo;\n case ComboPreventionType.NoEventCombo:\n return CombinationPrevention.NoEventCombo;\n case ComboPreventionType.NoFixtureCombo:\n return CombinationPrevention.NoFixtureCombo;\n case ComboPreventionType.NoEventSameLeagueCombo:\n return CombinationPrevention.OnlySameLeagueDifferentEvent;\n case ComboPreventionType.NoCompetitionCombo:\n return CombinationPrevention.NoCompetitionCombo;\n case ComboPreventionType.NoLeagueCombo:\n return CombinationPrevention.NoLeagueCombo;\n case ComboPreventionType.NoSportCombo:\n return CombinationPrevention.NoSportCombo;\n case ComboPreventionType.OtherLiveEvents:\n return CombinationPrevention.OnlyDifferentLiveEvents;\n case ComboPreventionType.NoGroupCombo:\n return CombinationPrevention.NoGroupCombo;\n }\n}\n","import { ComboPreventionType2 } from '@cds/betting-offer';\n\nexport enum MinimumCombination {\n /// \n /// No value was chosen for MinimumCombo.\n /// \n Default = 0,\n\n /// \n /// There must be at least one bet in the betslip (the current bet counts,\n /// so this effectively says that the current bet can be used on its own).\n /// \n Single = 1,\n\n /// \n /// There must be at least two bets in the betslip.\n /// \n Combo2 = 2,\n\n /// \n /// There must be at least three bets in the betslip.\n /// \n Combo3 = 3,\n\n /// \n /// There must be at least four bets in the betslip.\n /// \n Combo4 = 4,\n\n /// \n /// There must be at least five bets in the betslip.\n /// \n Combo5 = 5,\n\n /// \n /// There must be at least six bets in the betslip.\n /// \n Combo6 = 6,\n}\n\nexport function toMinimumCombo(status: ComboPreventionType2 | number): MinimumCombination {\n switch (status) {\n case ComboPreventionType2.Combo2:\n return MinimumCombination.Combo2;\n case ComboPreventionType2.Combo3:\n return MinimumCombination.Combo3;\n case ComboPreventionType2.Combo4:\n return MinimumCombination.Combo4;\n case ComboPreventionType2.Combo5:\n return MinimumCombination.Combo5;\n case ComboPreventionType2.Combo6:\n return MinimumCombination.Combo6;\n }\n\n return MinimumCombination.Single;\n}\n\nexport function toMinimumComboTV2(status?: number): MinimumCombination {\n if (!status) {\n return MinimumCombination.Single;\n }\n\n switch (status) {\n case 2:\n return MinimumCombination.Combo2;\n case 3:\n return MinimumCombination.Combo3;\n case 4:\n return MinimumCombination.Combo4;\n case 5:\n return MinimumCombination.Combo5;\n case 6:\n return MinimumCombination.Combo6;\n }\n\n return MinimumCombination.Single;\n}\n","import { FixtureViewType, Visibility } from '@cds/betting-offer';\nimport { MessageEnvelope, MessageType } from '@cds/push';\nimport { GameUpdateCommand } from '@cds/push/fixture-commands';\nimport { EventParticipant } from '@frontend/sports/betting-offer/feature/model';\nimport { SportConstant } from '@frontend/sports/common/core/data-access/constants';\nimport { GameDeleteMessageEnvelope, GameUpdateMessageEnvelope } from '@frontend/sports/content-distribution/feature';\nimport { OddsOperations } from '@frontend/sports/odds/feature';\n\nimport { BetslipPickChangeInfo, BetslipPickChangeType } from '../../modules/validation/picks/betslip-pick-change-info';\nimport { BetslipPick } from './betslip-pick';\nimport { toComboPrevention } from './combo-prevention';\nimport { toMinimumCombo } from './minimum-combination';\nimport { V1PickId } from './pick-id';\nimport {\n BetslipV1PickEvent,\n BetslipV1PickLeague,\n BetslipV1PickMarket,\n BetslipV1PickOption,\n BetslipV1PickRegion,\n BetslipV1PickSport,\n BetslipV1PickTeaserData,\n IBetslipV1PickDto,\n IBetslipV1PickStorage,\n IPrice,\n PickSubType,\n PriceType,\n} from './pick-models';\nimport { SignedName } from './signed-name.model';\n\nfunction isGameDeleteEnvelope(envelope: MessageEnvelope): envelope is GameDeleteMessageEnvelope {\n return envelope.messageType === MessageType.GameDelete;\n}\n\nfunction isGameUpdateEnvelope(envelope: MessageEnvelope): envelope is GameUpdateMessageEnvelope {\n return !!(envelope.messageType === MessageType.GameUpdate && (envelope.payload as GameUpdateCommand).game);\n}\n\nexport class BetslipV1Pick extends BetslipPick implements IBetslipV1PickDto {\n override get eventParticipants(): EventParticipant[] | undefined {\n return this.event.participants;\n }\n\n override get eventViewType(): FixtureViewType | undefined {\n return this.event.viewType;\n }\n\n override id: V1PickId;\n sport: BetslipV1PickSport;\n league: BetslipV1PickLeague;\n region: BetslipV1PickRegion;\n event: BetslipV1PickEvent;\n override market: BetslipV1PickMarket;\n option: BetslipV1PickOption;\n teaser: BetslipV1PickTeaserData;\n\n static fromJSON(value: IBetslipV1PickStorage): BetslipV1Pick {\n const pick = new BetslipV1Pick();\n pick.initPropertiesFromJSON(value);\n\n return pick;\n }\n\n static isPick(pick: BetslipPick): pick is BetslipV1Pick {\n return pick instanceof BetslipV1Pick;\n }\n\n constructor() {\n super();\n this.priceType = PriceType.Fixed;\n }\n\n protected override initPropertiesFromJSON(value: IBetslipV1PickStorage): void {\n super.initPropertiesFromJSON(value);\n this.event = {\n ...value.event,\n eventDate: new Date(value.event.eventDate),\n cutOffDate: new Date(value.event.cutOffDate),\n };\n this.league = value.league;\n this.region = value.region;\n this.sport = value.sport;\n this.market = {\n ...value.market,\n };\n this.option = {\n ...value.option,\n };\n this.teaser = {\n ...value.teaser,\n };\n }\n\n get eventName(): SignedName {\n return this.event.name;\n }\n\n get marketName(): SignedName {\n return this.market.name;\n }\n\n get optionName(): SignedName {\n return this.option.name;\n }\n\n get optionId(): number {\n return this.option.id;\n }\n\n get sportId(): SportConstant {\n return this.sport.id;\n }\n\n get partitionId(): number | undefined {\n return undefined;\n }\n\n get isVirtual(): boolean {\n return false;\n }\n\n get eventDate(): Date {\n return this.event.eventDate;\n }\n\n get regionName(): SignedName {\n return this.region.name;\n }\n\n get competitionName(): SignedName {\n return this.league.name;\n }\n\n get competitionId(): number {\n return this.league.id;\n }\n\n get sportName(): SignedName {\n return this.sport.name;\n }\n\n get leagueName(): SignedName {\n return this.league.name;\n }\n\n isPriceVisible(): boolean {\n return !!this.currentPrice && this.currentPrice.isVisible;\n }\n\n isMarketVisible(): boolean {\n return this.market.isVisible;\n }\n\n setMarketInvisible(): void {\n this.market.isVisible = false;\n }\n\n /**\n * Is pick open.\n * Pick is open when:\n * 1. Is result status is OPEN.\n * 2. Is cutoff date is in future.\n */\n override isOpen(): boolean {\n if (!super.isOpen() || this.market.isClosed) {\n return false;\n }\n\n return this.event.cutOffDate > new Date();\n }\n\n override toJSON(): IBetslipV1PickStorage {\n const base = super.toJSON();\n\n return {\n ...base,\n id: this.id.toString(),\n pickSubType: PickSubType.V1Pick,\n event: {\n id: this.event.id,\n name: { ...this.event.name },\n groupId: this.event.groupId,\n isLive: this.event.isLive,\n isPublished: this.event.isPublished,\n eventDate: this.event.eventDate.toISOString(),\n cutOffDate: this.event.cutOffDate.toISOString(),\n participants: this.event.participants,\n viewType: this.event.viewType,\n },\n league: {\n ...this.league,\n },\n region: {\n ...this.region,\n },\n sport: {\n ...this.sport,\n },\n market: {\n ...this.market,\n },\n option: {\n ...this.option,\n },\n teaser: {\n ...this.teaser,\n },\n };\n }\n\n copy(): BetslipV1Pick {\n const storage = this.toJSON();\n\n return BetslipV1Pick.fromJSON(storage);\n }\n\n updateFromMessage(m: MessageEnvelope): BetslipPickChangeInfo {\n const result: BetslipPickChangeInfo = {};\n\n if (isGameDeleteEnvelope(m) && m.payload.gameId === this.market.id) {\n this.market = { ...this.market, isVisible: false, isClosed: true };\n\n return result;\n }\n if (isGameUpdateEnvelope(m) && m.payload.game.id === this.market.id && m.payload.game.results.some((r) => r.id === this.option.id)) {\n const cdsGame = m.payload.game;\n this.market = {\n ...this.market,\n isVisible: cdsGame.visibility === Visibility.Visible,\n isClosed: cdsGame.visibility === Visibility.Hidden,\n comboPrevention: toComboPrevention(cdsGame.combo1),\n minimumCombo: toMinimumCombo(cdsGame.combo2),\n };\n\n const option = cdsGame.results.find((o) => o.id === this.option.id)!;\n const newPrice = {\n ...this.currentPrice!,\n nativeOdds: OddsOperations.createOdds(option),\n isVisible: option.visibility === Visibility.Visible && option.odds > 1,\n };\n if (!OddsOperations.equal(newPrice.nativeOdds, this.currentPrice!.nativeOdds) || newPrice.isVisible !== this.currentPrice!.isVisible) {\n this.priceHistory.push({ ...this.currentPrice! });\n if (!OddsOperations.equal(newPrice.nativeOdds, this.currentPrice!.nativeOdds)) {\n result.changeType = BetslipPickChangeType.OddsChange;\n } else if (newPrice.isVisible !== this.currentPrice!.isVisible) {\n result.changeType = BetslipPickChangeType.VisibilityChange;\n }\n }\n this.option.price = newPrice;\n }\n\n return result;\n }\n\n get isLive(): boolean {\n return this.event.isLive;\n }\n\n get prices(): IPrice[] {\n return [this.option.price];\n }\n}\n","import { BetslipV2OptionMarketPick } from './betslip-v2-option-market-pick';\nimport { IBetslipV2OptionMarketPickStorage, PickSubType } from './pick-models';\n\nexport class BetslipV2StandardPick extends BetslipV2OptionMarketPick {\n static fromJSON(value: IBetslipV2OptionMarketPickStorage): BetslipV2OptionMarketPick {\n const pick = new BetslipV2StandardPick();\n pick.initPropertiesFromJSON(value);\n\n return pick;\n }\n\n override toJSON(): IBetslipV2OptionMarketPickStorage {\n const base = super.toJSON();\n\n return {\n ...base,\n pickSubType: PickSubType.StandardV2Pick,\n };\n }\n\n copy(): BetslipV2OptionMarketPick {\n const storage = this.toJSON();\n\n return BetslipV2StandardPick.fromJSON(storage);\n }\n}\n","import { OptionMarket, ParticipantOption, ParticipantPriceStatus, ParticipantPriceType, Price } from '@cds/betting-offer';\nimport { OddsOperations, emptyOdds } from '@frontend/sports/odds/feature';\n\nimport { IPrice, PriceType } from './picks/pick-models';\n\nexport function participantOptionPriceToBetslipPrice(option: ParticipantOption): IPrice {\n return {\n id: option.price.id,\n isVisible:\n option.participantPriceStatus === ParticipantPriceStatus.Visible &&\n (option.priceType !== ParticipantPriceType.Fixed || OddsOperations.isOddsValid(OddsOperations.createOdds(option.price))),\n marketId: option.marketId,\n nativeOdds: option.priceType === ParticipantPriceType.Fixed ? OddsOperations.createOdds(option.price) : emptyOdds,\n type: PriceType[option.priceType],\n };\n}\n\nexport function optionPriceToBetslipPrice(market: OptionMarket, price: Price): IPrice {\n const option = market.options?.filter((o) => o.price.id === price.id)[0];\n\n return {\n id: price.id,\n isVisible: true,\n marketId: market.id,\n nativeOdds: OddsOperations.createOdds(price),\n type: option?.participantPriceType ? PriceType[option.participantPriceType] : PriceType.Fixed,\n };\n}\n","import { ParticipantMarketStatus, ParticipantOption, ParticipantPriceStatus, ParticipantPriceType, ParticipantStatus } from '@cds/betting-offer';\nimport { OddsOperations, emptyOdds } from '@frontend/sports/odds/feature';\n\nimport { participantOptionPriceToBetslipPrice } from '../price';\nimport { BetslipPick } from './betslip-pick';\nimport { BetslipV2Pick } from './betslip-v2-pick';\nimport { V2ParticipantPickId } from './pick-id';\nimport { IBetslipV2ParticipantPickStorage, IBetslipV2PickParticipant, IBetslipV2PickParticipantMarket, IPrice } from './pick-models';\nimport { SignedName } from './signed-name.model';\n\n/**\n * General Participant pick\n */\nexport abstract class BetslipV2ParticipantPick extends BetslipV2Pick {\n override id: V2ParticipantPickId;\n override market: IBetslipV2PickParticipantMarket;\n participants: IBetslipV2PickParticipant[];\n betType: string;\n betTypeAltTranslation: string;\n\n static override isPick(pick: BetslipPick): pick is BetslipV2ParticipantPick {\n return pick instanceof BetslipV2ParticipantPick;\n }\n\n protected constructor() {\n super();\n }\n\n protected override initPropertiesFromJSON(value: IBetslipV2ParticipantPickStorage): void {\n super.initPropertiesFromJSON(value);\n this.market = value.market;\n this.participants = value.participants.map((p) => ({\n ...p,\n prices: p.prices.map((pr) => ({ ...pr })),\n }));\n this.betType = value.betType;\n this.betTypeAltTranslation = value.betTypeAltTranslation;\n }\n\n protected updateFromPrice(thisPrice: IPrice, fixtureOption?: ParticipantOption): void {\n if (!fixtureOption) {\n this.priceHistory.push({ ...thisPrice });\n thisPrice.isVisible = false;\n } else {\n const newPrice = {\n isVisible:\n fixtureOption.marketStatus === ParticipantMarketStatus.Visible &&\n fixtureOption.participantPriceStatus === ParticipantPriceStatus.Visible,\n nativeOdds: emptyOdds,\n };\n if (fixtureOption.priceType === ParticipantPriceType.Fixed) {\n // Set odds only when priceType is fixed.\n newPrice.nativeOdds = OddsOperations.createOdds(fixtureOption.price);\n newPrice.isVisible = newPrice.isVisible && OddsOperations.isOddsValid(newPrice.nativeOdds);\n }\n if (!OddsOperations.equal(newPrice.nativeOdds, thisPrice.nativeOdds) || thisPrice.isVisible !== newPrice.isVisible) {\n this.priceHistory.push({ ...thisPrice });\n }\n thisPrice.isVisible = newPrice.isVisible;\n thisPrice.nativeOdds = newPrice.nativeOdds;\n }\n }\n\n protected addPriceFromOption(participant: IBetslipV2PickParticipant, option: ParticipantOption): void {\n const price = participantOptionPriceToBetslipPrice(option);\n participant.prices.push(price);\n }\n\n protected updateFromParticipantOptions(options: ParticipantOption[]): void {\n const currentMarketOptions = options.filter((o) => o.marketType === this.market.marketType);\n this.market.isVisible =\n !!currentMarketOptions.length && currentMarketOptions.every((o) => o.marketStatus === ParticipantMarketStatus.Visible);\n }\n\n override isPriceVisible(): boolean {\n return !this.participants.some((p) => p.status === ParticipantStatus.Hidden) && !!this.currentPrice && this.currentPrice.isVisible;\n }\n\n override isMarketVisible(): boolean {\n return this.market.isVisible;\n }\n\n /*\tupdateFromMessage(message: MessageEnvelope): void {\n\t\tif (\n\t\t\tisParticipantUpdateEnvelope(message) &&\n\t\t\tmessage.payload.fixtureId === this.fixture.fixtureId &&\n\t\t\tthis.participants.some(p => p.fixtureParticipantId === message.payload.participant.id)\n\t\t) {\n\t\t\tconst command = message.payload;\n\t\t\tconst pickParticipant = this.participants.find(p => p.fixtureParticipantId === message.payload.participant.id)!;\n\t\t\tthis.updateFromParticipant(pickParticipant, command.participant);\n\t\t}\n\t}*/\n\n get isLive(): boolean {\n return false;\n }\n\n override toJSON(): IBetslipV2ParticipantPickStorage {\n const base = super.toJSON();\n\n return {\n ...base,\n id: this.id.toString(),\n market: {\n id: this.market.id,\n isVisible: this.market.isVisible,\n marketType: this.market.marketType,\n isBetBuilderEnabled: false,\n },\n participants: this.participants.map((p) => ({\n ...p,\n })),\n betType: this.betType,\n betTypeAltTranslation: this.betTypeAltTranslation,\n };\n }\n\n get optionName(): SignedName {\n const optionsName = this.participants\n .sort((a, b) => a.position - b.position)\n .map((p) => p.name.name)\n .join(', ');\n\n return {\n name: optionsName,\n signature: '',\n };\n }\n\n get prices(): IPrice[] {\n return this.participants.flatMap((p) => p.prices.filter((pr) => pr.marketId === this.market.id));\n }\n\n abstract override copy(): BetslipV2ParticipantPick;\n}\n","import { ParticipantMarketStatus, ParticipantOption, ParticipantPriceType, ParticipantType } from '@cds/betting-offer';\n\nimport { BetslipPick } from './betslip-pick';\nimport { BetslipV2ParticipantPick } from './betslip-v2-participant-pick';\nimport { V2WinPickId } from './pick-id';\nimport { IBetslipV2PickParticipant, IBetslipV2PickWinParticipantMarket, IBetslipV2WinParticipantPickStorage, IPrice, PriceType } from './pick-models';\nimport { SignedName } from './signed-name.model';\n\n/**\n * General Win market pick\n */\nexport abstract class BetslipV2WinParticipantPick extends BetslipV2ParticipantPick {\n override id: V2WinPickId;\n override market: IBetslipV2PickWinParticipantMarket;\n\n static override isPick(pick: BetslipPick): pick is BetslipV2WinParticipantPick {\n return pick instanceof BetslipV2WinParticipantPick;\n }\n\n protected constructor() {\n super();\n }\n\n protected override initPropertiesFromJSON(value: IBetslipV2WinParticipantPickStorage): void {\n super.initPropertiesFromJSON(value);\n this.market = value.market;\n }\n\n protected override updateFromParticipantOptions(markets: ParticipantOption[]): void {\n super.updateFromParticipantOptions(markets);\n if (markets.length) {\n const fixedMarket = markets.find((m) => m.priceType === ParticipantPriceType.Fixed);\n const startingPriceMarket = markets.find((m) => m.priceType === ParticipantPriceType.StartingPrice);\n this.market.isStartingPriceAvailable = !!(startingPriceMarket && startingPriceMarket.marketStatus === ParticipantMarketStatus.Visible);\n this.market.isVisible = !!(fixedMarket && fixedMarket.marketStatus === ParticipantMarketStatus.Visible);\n this.market.isEachWay = fixedMarket?.properties.isEachWay || false;\n this.market.placeTerms = fixedMarket?.properties.placeTerms || null;\n }\n }\n\n override isMarketVisible(): boolean {\n if (!this.currentPrice) {\n return false;\n }\n switch (this.currentPrice.type) {\n case PriceType.Fixed:\n return this.market.isVisible;\n case PriceType.StartingPrice:\n return this.market.isStartingPriceAvailable;\n default:\n // only regular price ans starting price for win market\n return false;\n }\n }\n\n isStartingPriceAvailable(): boolean {\n return this.market.isStartingPriceAvailable && this.prices.some((pr) => pr.type === PriceType.StartingPrice && pr.isVisible);\n }\n\n isFixedPriceAvailable(): boolean {\n return this.market.isVisible && this.prices.some((pr) => pr.type === PriceType.Fixed && pr.isVisible);\n }\n\n override toJSON(): IBetslipV2WinParticipantPickStorage {\n const base = super.toJSON();\n\n return {\n ...base,\n market: {\n id: this.market.id,\n isVisible: this.market.isVisible,\n isStartingPriceAvailable: this.market.isStartingPriceAvailable,\n isEachWay: this.market.isEachWay,\n marketType: this.market.marketType,\n placeTerms: this.market.placeTerms && { ...this.market.placeTerms },\n isBetBuilderEnabled: false,\n },\n };\n }\n\n get participant(): IBetslipV2PickParticipant {\n return this.participants[0];\n }\n\n get isEachWay(): boolean {\n return this.market.isEachWay && this.participant.type !== ParticipantType.Favourite;\n }\n\n override get optionName(): SignedName {\n return this.participant.name;\n }\n\n override get prices(): IPrice[] {\n // Small optimization over participants pick, not to use flatArray. Here we know that there is only one participant\n return this.participant.prices.filter((pr) => pr.marketId === this.market.id);\n }\n\n override get currentPrice(): IPrice | undefined {\n return this.prices.find((p) => p.type === this.priceType);\n }\n\n abstract override copy(): BetslipV2WinParticipantPick;\n}\n","import { FixtureViewType } from '@cds/betting-offer';\nimport { EventParticipant } from '@frontend/sports/betting-offer/feature/model';\n\nimport { BetslipPick } from '../betslip-pick';\nimport { BetslipV2WinParticipantPick } from '../betslip-v2-win-participant-pick';\nimport { GolfPick, IBetslipV2GolfPickFixture, IBetslipV2GolfWinParticipantPickStorage, IBetslipV2PickFixture, PickSubType } from '../pick-models';\nimport { SignedName } from '../signed-name.model';\n\nexport class BetslipV2GolfWinParticipantPick extends BetslipV2WinParticipantPick implements GolfPick {\n override fixture: IBetslipV2GolfPickFixture;\n override get eventParticipants(): EventParticipant[] | undefined {\n return undefined;\n }\n\n override get eventViewType(): FixtureViewType | undefined {\n return undefined;\n }\n\n static override isPick(pick: BetslipPick): pick is BetslipV2GolfWinParticipantPick {\n return pick instanceof BetslipV2GolfWinParticipantPick;\n }\n\n constructor() {\n super();\n }\n\n static fromJSON(value: IBetslipV2GolfWinParticipantPickStorage): BetslipV2GolfWinParticipantPick {\n const pick = new BetslipV2GolfWinParticipantPick();\n pick.initPropertiesFromJSON(value);\n\n return pick;\n }\n\n protected override initPropertiesFromJSON(value: IBetslipV2GolfWinParticipantPickStorage): void {\n super.initPropertiesFromJSON(value);\n this.fixture = {\n ...(this.fixture as IBetslipV2PickFixture),\n fixtureGroup: value.fixture.fixtureGroup,\n };\n }\n\n override toJSON(): IBetslipV2GolfWinParticipantPickStorage {\n const base = super.toJSON();\n\n return {\n ...base,\n fixture: {\n ...base.fixture,\n fixtureGroup: {\n id: this.fixture.fixtureGroup.id,\n name: {\n name: this.fixture.fixtureGroup.name.name,\n signature: this.fixture.fixtureGroup.name.signature,\n },\n },\n },\n pickSubType: PickSubType.GolfWinPick,\n };\n }\n\n copy(): BetslipV2GolfWinParticipantPick {\n const storage = this.toJSON();\n\n return BetslipV2GolfWinParticipantPick.fromJSON(storage);\n }\n\n override get eventName(): SignedName {\n return this.fixture.fixtureGroup.name;\n }\n\n get marketName(): SignedName {\n return this.fixture.name;\n }\n}\n","import { FixtureStage, FixtureType, FixtureViewType } from '@cds/betting-offer';\nimport { EventParticipant } from '@frontend/sports/betting-offer/feature/model';\nimport { cloneDeep } from 'lodash-es';\n\nimport { BetslipPick } from '../betslip-pick';\nimport { BetslipV2OptionMarketPick } from '../betslip-v2-option-market-pick';\nimport { BetslipV2ParticipantPick } from '../betslip-v2-participant-pick';\nimport { BetslipV2Pick } from '../betslip-v2-pick';\nimport { BetslipV2WinParticipantPick } from '../betslip-v2-win-participant-pick';\nimport { V2OptionMarketXCastPickId } from '../pick-id';\nimport {\n HorseRaceParticipantPick,\n HorseRacePick,\n IBetslipV2HorseRaceOptionMarketPickStorage,\n IBetslipV2HorseRaceOptionMarketXCastPickStorage,\n IBetslipV2HorseRaceParticipantPickStorage,\n IBetslipV2HorseRacePickFixture,\n IBetslipV2HorseRacePickFixtureStorage,\n IBetslipV2HorseRacePickStorage,\n IBetslipV2HorseRaceWinParticipantPickStorage,\n IBetslipV2Option,\n IBetslipV2OptionMarket,\n IBetslipV2OptionMarketPickStorage,\n IBetslipV2ParticipantPickStorage,\n IBetslipV2PickFixture,\n IBetslipV2PickFixtureStorage,\n IBetslipV2PickParticipant,\n IPrice,\n ParticipantPickType,\n PickSubType,\n PriceType,\n} from '../pick-models';\nimport { SignedName } from '../signed-name.model';\n\nexport class RacePickProvider {\n static toJSON(pick: HorseRacePick, baseStorage: IBetslipV2PickFixtureStorage): { fixture: IBetslipV2HorseRacePickFixtureStorage } {\n return {\n fixture: {\n ...baseStorage,\n bestOddsGuarantee: pick.fixture.bestOddsGuarantee,\n isRaceOff: pick.fixture.isRaceOff,\n },\n };\n }\n\n static fromJSON(pick: HorseRacePick, fixture: IBetslipV2PickFixture, storage: { fixture: IBetslipV2HorseRacePickFixtureStorage }): void {\n pick.fixture = {\n ...fixture,\n bestOddsGuarantee: storage.fixture.bestOddsGuarantee,\n isRaceOff: storage.fixture.isRaceOff,\n };\n }\n\n static eventName(pick: HorseRacePick): SignedName {\n if (pick.fixture.fixtureType === FixtureType.DayOfRace) {\n return pick.leagueName;\n }\n\n return pick.fixture.name;\n }\n}\n\n/**\n * Horse racing Win participant pick\n */\nexport class BetslipV2HorseRaceWinParticipantPick extends BetslipV2WinParticipantPick implements HorseRaceParticipantPick {\n override get eventParticipants(): EventParticipant[] | undefined {\n return undefined;\n }\n\n override get eventViewType(): FixtureViewType | undefined {\n return undefined;\n }\n\n private _marketName: string;\n private _marketNameAltTranslation?: string;\n\n override fixture: IBetslipV2HorseRacePickFixture;\n override participants: IBetslipV2PickParticipant[];\n\n static fromJSON(value: IBetslipV2HorseRaceWinParticipantPickStorage): BetslipV2HorseRaceWinParticipantPick {\n const pick = new BetslipV2HorseRaceWinParticipantPick();\n pick.initPropertiesFromJSON(value);\n\n return pick;\n }\n\n static override isPick(pick: BetslipPick): pick is BetslipV2HorseRaceWinParticipantPick {\n return pick instanceof BetslipV2HorseRaceWinParticipantPick;\n }\n\n constructor() {\n super();\n }\n\n protected override initPropertiesFromJSON(value: IBetslipV2HorseRaceWinParticipantPickStorage): void {\n super.initPropertiesFromJSON(value);\n RacePickProvider.fromJSON(this, this.fixture, value);\n this.fromJSON(this, value);\n }\n\n setMarketName(value: string, altTranslation?: string): void {\n this._marketName = value;\n if (altTranslation) {\n this._marketNameAltTranslation = altTranslation;\n }\n }\n\n override toJSON(): IBetslipV2HorseRaceWinParticipantPickStorage {\n const base = super.toJSON();\n\n return {\n ...base,\n ...RacePickProvider.toJSON(this, base.fixture),\n ...this.toParticipantsJSON(this),\n pickSubType: PickSubType.HorseWinPick,\n };\n }\n\n private fromJSON(\n pick: HorseRaceParticipantPick,\n json: { participants: IBetslipV2PickParticipant[]; _marketName: string; _marketNameAltTranslation?: string },\n ): void {\n this._marketName = json._marketName;\n pick.participants = json.participants.map((p) => ({\n ...p,\n prices: p.prices,\n }));\n\n if (json._marketNameAltTranslation) {\n this._marketNameAltTranslation = json._marketNameAltTranslation;\n }\n }\n\n private toParticipantsJSON(pick: HorseRaceParticipantPick): {\n participants: IBetslipV2PickParticipant[];\n _marketName: string;\n _marketNameAltTranslation?: string;\n } {\n return {\n participants: pick.participants.map((p) => ({\n ...p,\n prices: p.prices,\n })),\n _marketName: this._marketName,\n ...(this._marketNameAltTranslation && { _marketNameAltTranslation: this._marketNameAltTranslation }),\n };\n }\n\n override setMarketInvisible(): void {\n super.setMarketInvisible();\n this.market.isStartingPriceAvailable = false;\n }\n\n copy(): BetslipV2HorseRaceWinParticipantPick {\n const storage = this.toJSON();\n\n return BetslipV2HorseRaceWinParticipantPick.fromJSON(storage);\n }\n\n override get participant(): IBetslipV2PickParticipant {\n return this.participants[0];\n }\n\n override get eventName(): SignedName {\n return RacePickProvider.eventName(this);\n }\n\n get marketName(): SignedName {\n return {\n name: this._marketName,\n signature: '',\n nameAlternate: this._marketNameAltTranslation,\n };\n }\n}\n\n/**\n * Horse racing XCast (Forecast, Tricast) pick\n */\nexport class BetslipV2HorseRaceXCastPick extends BetslipV2ParticipantPick implements HorseRaceParticipantPick {\n private _marketName: string;\n private _marketNameAltTranslation?: string;\n override fixture: IBetslipV2HorseRacePickFixture;\n override participants: IBetslipV2PickParticipant[];\n\n override get eventParticipants(): EventParticipant[] | undefined {\n return undefined;\n }\n\n override get eventViewType(): FixtureViewType | undefined {\n return undefined;\n }\n\n static override isPick(pick: BetslipPick): pick is BetslipV2HorseRaceXCastPick {\n return pick instanceof BetslipV2HorseRaceXCastPick;\n }\n\n static fromJSON(value: IBetslipV2HorseRaceParticipantPickStorage): BetslipV2HorseRaceXCastPick {\n const pick = new BetslipV2HorseRaceXCastPick();\n pick.initPropertiesFromJSON(value);\n\n return pick;\n }\n\n constructor() {\n super();\n this.priceType = PriceType.NoPrice;\n }\n\n protected override initPropertiesFromJSON(value: IBetslipV2HorseRaceParticipantPickStorage): void {\n super.initPropertiesFromJSON(value);\n RacePickProvider.fromJSON(this, this.fixture, value);\n this.fromJSON(this, value);\n }\n\n private getPermutations(pos: number): number {\n let res = 1;\n for (let i = this.participants.length; i > this.participants.length - pos; i--) {\n res *= i;\n }\n\n return res;\n }\n\n override betCount(): number {\n switch (this.id.pickType) {\n case ParticipantPickType.CombinationForecast:\n return this.getPermutations(2);\n case ParticipantPickType.CombinationTricast:\n return this.getPermutations(3);\n case ParticipantPickType.Forecast:\n case ParticipantPickType.Tricast:\n return 1;\n default:\n return 1;\n }\n }\n\n setMarketName(value: string, altTranslation?: string): void {\n this._marketName = value;\n if (altTranslation) {\n this._marketNameAltTranslation = altTranslation;\n }\n }\n\n override toJSON(): IBetslipV2HorseRaceParticipantPickStorage {\n const base = super.toJSON() as IBetslipV2ParticipantPickStorage & IBetslipV2HorseRacePickStorage;\n\n return {\n ...base,\n ...RacePickProvider.toJSON(this, base.fixture),\n ...this.toParticipantsJSON(this),\n pickSubType: PickSubType.HorseXCastPick,\n };\n }\n\n copy(): BetslipV2HorseRaceXCastPick {\n const storage = this.toJSON();\n\n return BetslipV2HorseRaceXCastPick.fromJSON(storage);\n }\n\n override get eventName(): SignedName {\n return RacePickProvider.eventName(this);\n }\n\n get marketName(): SignedName {\n return {\n name: this._marketName,\n signature: '',\n nameAlternate: this._marketNameAltTranslation,\n };\n }\n\n private fromJSON(\n pick: HorseRaceParticipantPick,\n json: { participants: IBetslipV2PickParticipant[]; _marketName: string; _marketNameAltTranslation?: string },\n ): void {\n this._marketName = json._marketName;\n pick.participants = json.participants.map((p) => ({\n ...p,\n prices: p.prices,\n }));\n\n if (json._marketNameAltTranslation) {\n this._marketNameAltTranslation = json._marketNameAltTranslation;\n }\n }\n\n private toParticipantsJSON(pick: HorseRaceParticipantPick): {\n participants: IBetslipV2PickParticipant[];\n _marketName: string;\n _marketNameAltTranslation?: string;\n } {\n return {\n participants: pick.participants.map((p) => ({\n ...p,\n prices: p.prices,\n })),\n _marketName: this._marketName,\n ...(this._marketNameAltTranslation && { _marketNameAltTranslation: this._marketNameAltTranslation }),\n };\n }\n}\n\n/**\n * Horse racing Option Market Pick\n */\nexport class BetslipV2HorseRaceOptionMarketPick extends BetslipV2OptionMarketPick implements HorseRacePick {\n override fixture: IBetslipV2HorseRacePickFixture;\n betType: string;\n betTypeAltTranslation: string;\n\n static fromJSON(value: IBetslipV2HorseRaceOptionMarketPickStorage): BetslipV2HorseRaceOptionMarketPick {\n const pick = new BetslipV2HorseRaceOptionMarketPick();\n pick.initPropertiesFromJSON(value);\n\n return pick;\n }\n\n constructor() {\n super();\n }\n\n protected override initPropertiesFromJSON(value: IBetslipV2HorseRaceOptionMarketPickStorage): void {\n super.initPropertiesFromJSON(value);\n RacePickProvider.fromJSON(this, this.fixture, value);\n this.betType = value.betType;\n this.betTypeAltTranslation = value.betTypeAltTranslation;\n }\n\n override toJSON(): IBetslipV2HorseRaceOptionMarketPickStorage {\n const base = super.toJSON() as IBetslipV2OptionMarketPickStorage & IBetslipV2HorseRacePickStorage;\n\n return {\n ...base,\n ...RacePickProvider.toJSON(this, base.fixture),\n pickSubType: PickSubType.HorseOptionMarketPick,\n betType: this.betType,\n betTypeAltTranslation: this.betTypeAltTranslation,\n };\n }\n\n copy(): BetslipV2HorseRaceOptionMarketPick {\n const storage = this.toJSON();\n\n return BetslipV2HorseRaceOptionMarketPick.fromJSON(storage);\n }\n\n override get eventName(): SignedName {\n return this.leagueName;\n }\n\n override get competitionName(): SignedName {\n return this.leagueName;\n }\n\n static override isPick(pick: BetslipPick): pick is BetslipV2HorseRaceOptionMarketPick {\n return pick instanceof BetslipV2HorseRaceOptionMarketPick;\n }\n\n isStartingPriceAvailable(): boolean {\n return this.market.isStartingPriceAvailable!;\n }\n\n isFixedPriceAvailable(): boolean {\n return this.market.isVisible && this.market.isFixedPriceAvailable!;\n }\n}\n\nexport class BetslipV2OptionMarketXCastRacePick extends BetslipV2Pick {\n options: IBetslipV2Option[];\n override market: IBetslipV2OptionMarket;\n override id: V2OptionMarketXCastPickId;\n override fixture: IBetslipV2HorseRacePickFixture;\n betType: string;\n betTypeAltTranslation: string;\n override get eventParticipants(): EventParticipant[] | undefined {\n return undefined;\n }\n\n override get eventViewType(): FixtureViewType | undefined {\n return undefined;\n }\n\n get marketName(): SignedName {\n return this.market.name;\n }\n\n get isLive(): boolean {\n return this.fixture.stage === FixtureStage.Live;\n }\n\n get optionName(): SignedName {\n const optionsName = this.options\n .sort((a, b) => a.position! - b.position!)\n .map((p) => p.name.name)\n .join(', ');\n\n return {\n name: optionsName,\n signature: '',\n };\n }\n\n private getPermutations(pos: number): number {\n let res = 1;\n for (let i = this.options.length; i > this.options.length - pos; i--) {\n res *= i;\n }\n\n return res;\n }\n\n override betCount(): number {\n switch (this.id.pickType) {\n case ParticipantPickType.CombinationForecast:\n return this.getPermutations(2);\n case ParticipantPickType.CombinationTricast:\n return this.getPermutations(3);\n case ParticipantPickType.Forecast:\n case ParticipantPickType.Tricast:\n return 1;\n default:\n return 1;\n }\n }\n\n get selectedOptions(): IBetslipV2Option[] {\n return this.options;\n }\n\n static override isPick(pick: BetslipPick): pick is BetslipV2OptionMarketXCastRacePick {\n return pick instanceof BetslipV2OptionMarketXCastRacePick;\n }\n\n static fromJSON(value: IBetslipV2HorseRaceOptionMarketXCastPickStorage): BetslipV2OptionMarketXCastRacePick {\n const pick = new BetslipV2OptionMarketXCastRacePick();\n pick.initPropertiesFromJSON(value);\n\n return pick;\n }\n\n constructor() {\n super();\n this.priceType = PriceType.NoPrice;\n }\n\n protected override initPropertiesFromJSON(value: IBetslipV2HorseRaceOptionMarketXCastPickStorage): void {\n super.initPropertiesFromJSON(value);\n RacePickProvider.fromJSON(this, this.fixture, value);\n this.market = value.market;\n this.betType = value.betType;\n this.betTypeAltTranslation = value.betTypeAltTranslation;\n\n this.options = value.options.map((o) => ({\n ...o,\n prices: cloneDeep(o.prices),\n }));\n }\n\n override toJSON(): IBetslipV2HorseRaceOptionMarketXCastPickStorage {\n const base = super.toJSON();\n\n return {\n ...base,\n ...RacePickProvider.toJSON(this, base.fixture),\n pickSubType: PickSubType.HorseOptionMarketXCastRacePick,\n options: this.options,\n market: this.market,\n betType: this.betType,\n betTypeAltTranslation: this.betTypeAltTranslation,\n };\n }\n\n copy(): BetslipV2OptionMarketXCastRacePick {\n const storage = this.toJSON();\n\n return BetslipV2OptionMarketXCastRacePick.fromJSON(storage);\n }\n\n override get eventName(): SignedName {\n return this.leagueName;\n }\n\n override get competitionName(): SignedName {\n return this.leagueName;\n }\n\n get prices(): IPrice[] {\n return this.options[0].prices.filter((pr) => pr.marketId === this.market.id);\n }\n}\n","import { Injectable } from '@angular/core';\n\nimport { BetslipBetBuilderPick } from '../../../core/picks/betslip-bet-builder-pick';\nimport { BetslipGroupPick } from '../../../core/picks/betslip-group-pick';\nimport { BetslipPick } from '../../../core/picks/betslip-pick';\nimport { BetslipV1Pick } from '../../../core/picks/betslip-v1-pick';\nimport { BetslipV2StandardPick } from '../../../core/picks/betslip-v2-standard-pick';\nimport {\n BetslipGroupPickStorage,\n IBetslipBetBuilderPickStorage,\n IBetslipPickStorage,\n IBetslipV1PickStorage,\n IBetslipV2GolfWinParticipantPickStorage,\n IBetslipV2HorseRaceOptionMarketPickStorage,\n IBetslipV2HorseRaceOptionMarketXCastPickStorage,\n IBetslipV2HorseRaceParticipantPickStorage,\n IBetslipV2HorseRaceWinParticipantPickStorage,\n IBetslipV2OptionMarketPickStorage,\n PickSubType,\n} from '../../../core/picks/pick-models';\nimport { BetslipV2GolfWinParticipantPick } from '../../../core/picks/sport-specific/betslip-v2-golf-picks';\nimport {\n BetslipV2HorseRaceOptionMarketPick,\n BetslipV2HorseRaceWinParticipantPick,\n BetslipV2HorseRaceXCastPick,\n BetslipV2OptionMarketXCastRacePick,\n} from '../../../core/picks/sport-specific/betslip-v2-horse-race-picks';\n\nexport function loadPickFromJSON(value: IBetslipPickStorage) {\n switch (value.pickSubType) {\n case PickSubType.V1Pick: {\n return BetslipV1Pick.fromJSON(value as IBetslipV1PickStorage);\n }\n case PickSubType.HorseWinPick: {\n return BetslipV2HorseRaceWinParticipantPick.fromJSON(value as IBetslipV2HorseRaceWinParticipantPickStorage);\n }\n case PickSubType.HorseXCastPick: {\n return BetslipV2HorseRaceXCastPick.fromJSON(value as IBetslipV2HorseRaceParticipantPickStorage);\n }\n case PickSubType.HorseOptionMarketPick: {\n return BetslipV2HorseRaceOptionMarketPick.fromJSON(value as IBetslipV2HorseRaceOptionMarketPickStorage);\n }\n case PickSubType.GolfWinPick: {\n return BetslipV2GolfWinParticipantPick.fromJSON(value as IBetslipV2GolfWinParticipantPickStorage);\n }\n case PickSubType.BetBuilderPick: {\n return BetslipBetBuilderPick.fromJSON(value as IBetslipBetBuilderPickStorage);\n }\n case PickSubType.StandardV2Pick: {\n return BetslipV2StandardPick.fromJSON(value as IBetslipV2OptionMarketPickStorage);\n }\n case PickSubType.GroupedPicks: {\n return BetslipGroupPick.fromJSON(value as BetslipGroupPickStorage);\n }\n case PickSubType.HorseOptionMarketXCastRacePick: {\n return BetslipV2OptionMarketXCastRacePick.fromJSON(value as IBetslipV2HorseRaceOptionMarketXCastPickStorage);\n }\n\n default: {\n throw new Error('Unknown pick sub type');\n }\n }\n}\n@Injectable({ providedIn: 'root' })\nexport class BetslipPickStorageLoader {\n loadPick(value: IBetslipPickStorage): BetslipPick {\n return loadPickFromJSON(value);\n }\n}\n","import { BasePrice } from '@cds/betting-offer';\nimport { LegInfo, ResultState, SuspensionState } from '@cds/betting-offer/domain-specific/bet-builder';\nimport { OddsOperations, emptyOdds } from '@frontend/sports/odds/feature';\n\nimport { BetslipPick } from './picks/betslip-pick';\nimport { BetslipV1Pick } from './picks/betslip-v1-pick';\nimport { BetslipV2OptionMarketPick } from './picks/betslip-v2-option-market-pick';\nimport { IPrice, PriceType } from './picks/pick-models';\n\nexport const mapLegInfoToClientLegInfo = (legInformation: LegInfo[]): { clientLegId: string; isVisible: boolean; isClosed: boolean }[] => {\n return legInformation?.map((legInfo) => mapLegVisibility(legInfo)) ?? [];\n};\n\nexport const mapLegVisibility = (legInformation: LegInfo): { clientLegId: string; isVisible: boolean; isClosed: boolean } => ({\n isVisible: !isSuspended(legInformation.suspensionState),\n isClosed: legInformation.resultState !== ResultState.Open,\n clientLegId: legInformation.clientLegId,\n});\n\nexport const isSuspended = (suspensionState?: SuspensionState): boolean => suspensionState !== SuspensionState.MarketUnsuspended;\n\nexport const buildIPriceFromBasePrice = (price: BasePrice): IPrice => {\n try {\n return {\n nativeOdds: OddsOperations.createOdds(price),\n type: PriceType.Fixed,\n id: 1,\n isVisible: true,\n marketId: 1,\n };\n } catch {\n return buildIPriceFromBasePrice(emptyPrice());\n }\n};\n\nexport const emptyPrice = (): BasePrice => {\n return {\n americanOdds: emptyOdds.moneyline,\n odds: emptyOdds.decimals,\n denominator: emptyOdds.fractional.denominator,\n numerator: emptyOdds.fractional.numerator,\n };\n};\n\nexport const emptyIPrice = buildIPriceFromBasePrice(emptyPrice());\n\nexport const resolveClientLegId = (leg: BetslipPick) => {\n if (BetslipV1Pick.isPick(leg)) {\n return `1:${leg.event.id}:${leg.market.id}:${leg.optionId}`;\n }\n if (BetslipV2OptionMarketPick.isPick(leg)) {\n return `${leg.fixture.fixtureId}:${leg.market.id}:${leg.optionId}`;\n }\n\n return '';\n};\n","export interface GroupLegState {\n clientLegId: string;\n isNoncombinable: boolean;\n isClosed: boolean;\n isVisible: boolean;\n}\n\nexport function defaultLegState(): GroupLegState {\n return {\n clientLegId: '',\n isNoncombinable: false,\n isClosed: false,\n isVisible: true,\n };\n}\n","import { FixtureViewType } from '@cds/betting-offer';\nimport { EventParticipant } from '@frontend/sports/betting-offer/feature/model';\nimport { SportConstant } from '@frontend/sports/common/core/data-access/constants';\n\nimport { loadPickFromJSON } from '../../modules/picks/services/betslip-pick-storage-loader';\nimport { emptyIPrice, resolveClientLegId } from '../group-pick-helpers';\nimport { BetslipGroupInformation } from '../groups/betslip-group-information';\nimport { BetslipPick } from './betslip-pick';\nimport { BetslipV1Pick } from './betslip-v1-pick';\nimport { BetslipV2Pick } from './betslip-v2-pick';\nimport { GroupLegState, defaultLegState } from './group-leg-state';\nimport { GroupPickId, PickId } from './pick-id';\nimport { BetslipGroupPickStorage, IPrice, PickOddsState, PickSubType, PriceType } from './pick-models';\nimport { SignedName } from './signed-name.model';\n\ninterface GroupPickInputs {\n sgpId: string;\n price: IPrice | null;\n legsInfo: { clientLegId: string; isVisible: boolean; isClosed: boolean }[];\n isSuspended: boolean;\n acceptedPrice?: IPrice | null;\n}\n\nexport abstract class BaseBetslipGroupPick extends BetslipPick {\n override id: GroupPickId;\n protected legs: BetslipPick[];\n //unique value provided for the group combination by the provider that can be used by clients for subscribing to price updates\n protected _sgpId: string | null;\n protected _legsState: Record = {};\n protected price: IPrice;\n isSuspended: boolean; // the whole combination can be suspended\n protected hasEmptyPrice: boolean;\n\n override get groupInfo(): BetslipGroupInformation {\n return this._groupInfo!;\n }\n\n static isPick(pick: BetslipPick): pick is BaseBetslipGroupPick {\n return pick instanceof BaseBetslipGroupPick;\n }\n\n get sgpId(): string | null {\n return this._sgpId;\n }\n\n getLegs(): BetslipPick[] {\n return this.legs;\n }\n\n get legsState(): Record {\n return this._legsState;\n }\n\n override get eventParticipants(): EventParticipant[] | undefined {\n return this.legs[0].eventParticipants;\n }\n\n override get eventViewType(): FixtureViewType | undefined {\n return this.legs[0].eventViewType;\n }\n\n hasPick(id: PickId): boolean {\n return this.legs.some((p) => p.id.toString() === id.toString());\n }\n\n legIsNoncombinable(pickId: PickId): boolean {\n const { isNoncombinable } = this._legsState[pickId.toString()];\n\n return isNoncombinable;\n }\n\n getOddsStateForLegId(legId: PickId): PickOddsState {\n const leg = this.legs.find((l) => l.id === legId);\n\n if (!leg) return PickOddsState.Closed;\n\n return this.getOddsStateForLeg(leg);\n }\n\n private getOddsStateForLeg(leg: BetslipPick): PickOddsState {\n const legOddsState = leg.oddsState;\n const { isClosed, isVisible } = this._legsState[leg.id.toString()];\n\n if (isClosed || legOddsState === PickOddsState.Closed) {\n return PickOddsState.Closed;\n }\n\n if (!isVisible || legOddsState === PickOddsState.Locked) {\n return PickOddsState.Locked;\n }\n\n return PickOddsState.Open;\n }\n\n protected hasNonOpenLegs(): boolean {\n return this.legs.some((leg) => this.getOddsStateForLeg(leg) !== PickOddsState.Open);\n }\n\n groupHasUncombinableLeg(): boolean {\n return Object.values(this._legsState).some((t) => t.isNoncombinable);\n }\n\n protected isPriceVisible(): boolean {\n return true;\n }\n\n isMarketVisible(): boolean {\n return true;\n }\n\n get isLive(): boolean {\n return this.legs.some((leg) => leg.isLive);\n }\n\n get prices(): IPrice[] {\n return [this.price];\n }\n\n //Overriding this because we want to show an invalid price in case there are any nonOpen legs in a group.\n //The price returned from the price call can still be a valid one.\n override get currentPrice(): IPrice | undefined {\n return this.hasEmptyPrice ? emptyIPrice : this.prices[0];\n }\n\n //We still want to run our price validators on the 'actual' price\n get actualGroupPrice(): IPrice | undefined {\n return this.prices[0];\n }\n\n get eventName(): SignedName {\n //all picks within the group belong to the same event, so it's a safe assumption\n return this.legs[0]!.eventName;\n }\n\n get marketName(): SignedName {\n //all picks within the group belong to the same event, so it's a safe assumption\n throw new Error(`Grouped picks market name getter shouldn't be invoked!`);\n }\n\n get optionName(): SignedName {\n throw new Error(`Grouped picks option name getter shouldn't be invoked!`);\n }\n\n get sportId(): SportConstant {\n return this.legs[0]!.sportId;\n }\n\n //Commented this particular code as AngstromSGP is not PROD-ready, It will be enabled,So GroupPick will not be initiated\n // get partitionId(): number | null {\n // //TO CHECK: whether all picks within the group belong to the same event, so it's a safe assumption\n // const fixtureId = this.tryGetFixtureId;\n // if (this.tryGetFixtureId === '') {\n // return null;\n // } else {\n // return getPartition(fixtureId);\n // }\n // }\n\n get partitionId(): number | undefined {\n throw new Error('Method not implemented.');\n }\n\n get isVirtual(): boolean {\n return this.legs.reduce((acc, curr) => acc && curr.isVirtual, true);\n }\n\n get eventDate(): Date {\n //all picks within the group belong to the same event, so it's a safe assumption\n return this.legs[0]!.eventDate;\n }\n\n get regionName(): SignedName {\n //all picks within the group belong to the same event, so it's a safe assumption\n return this.legs[0]!.regionName;\n }\n\n get competitionName(): SignedName {\n //all picks within the group belong to the same event, so it's a safe assumption\n return this.legs[0]!.competitionName;\n }\n\n get competitionId(): number {\n //all picks within the group belong to the same event, so it's a safe assumption\n return this.legs[0]!.competitionId;\n }\n\n get sportName(): SignedName {\n //all picks within the group belong to the same event, so it's a safe assumption\n return this.legs[0]!.sportName;\n }\n\n get leagueName(): SignedName {\n //all picks within the group belong to the same event, so it's a safe assumption\n return this.legs[0]!.leagueName;\n }\n\n get tryGetEventId(): string {\n const v1PickWithinTheGroup = this.legs.find((p) => p instanceof BetslipV1Pick);\n if (v1PickWithinTheGroup) {\n return (v1PickWithinTheGroup as BetslipV1Pick).event.id;\n }\n\n return '';\n }\n\n get tryGetFixtureId(): string {\n const v2PickWithinTheGroup = this.legs.find((p) => p instanceof BetslipV2Pick);\n\n if (v2PickWithinTheGroup) {\n return (v2PickWithinTheGroup as BetslipV2Pick).fixture.fixtureId;\n }\n\n return '';\n }\n\n get eventId(): string {\n return this.tryGetEventId || this.tryGetFixtureId;\n }\n\n protected override initPropertiesFromJSON(value: BetslipGroupPickStorage): void {\n super.initPropertiesFromJSON(value);\n this.price = value.price;\n this._sgpId = value.SGPId;\n this._legsState = value.legsState;\n }\n\n protected initLegState(legInfos: { clientLegId: string; isVisible: boolean; isClosed: boolean }[]) {\n this._legsState = this.legs.reduce>((acc, leg) => {\n const clientLegId = resolveClientLegId(leg);\n const legInfo = legInfos.find((li) => li.clientLegId === clientLegId) ?? defaultLegState();\n acc[leg.id.toString()] = {\n clientLegId, // in case the defaultLegState was used we don't have a clientLegId\n isVisible: legInfo.isVisible,\n isClosed: legInfo.isClosed,\n isNoncombinable: !leg.market.isBetBuilderEnabled,\n };\n\n return acc;\n }, {});\n }\n\n legExist(id: PickId): boolean {\n return !!this.legs.find((t) => t.id.toString() === id.toString());\n }\n\n abstract addPick(addPickInput: GroupPickInputs & { pick: BetslipPick }): BaseBetslipGroupPick;\n abstract addPicks(addPicksInput: GroupPickInputs & { picks: BetslipPick[] }): BaseBetslipGroupPick;\n abstract removePicks(removePickInput: GroupPickInputs & { ids: PickId[] }): BetslipPick;\n abstract updatePicks(picks: BetslipPick[]): BaseBetslipGroupPick;\n\n setPrice(price: IPrice): void {\n this.price = price;\n }\n\n override isOpen(): boolean {\n return super.isOpen();\n }\n}\n\nexport class BetslipGroupPick extends BaseBetslipGroupPick {\n constructor(input: GroupPickInputs & { groupInfo: BetslipGroupInformation; picks: BetslipPick[] }) {\n super();\n if (!input.groupInfo?.groupId) {\n throw new Error(`Can't init a group with empty group info!`);\n }\n this.priceType = PriceType.Fixed;\n this.id = new GroupPickId(input.groupInfo);\n this.setGroupInfo(input.groupInfo);\n this._sgpId = input.sgpId;\n this.legs = input.picks;\n if (input.price) {\n this.price = input.price;\n }\n this.acceptedPrice = { price: input.acceptedPrice ?? input.price, isManuallyAccepted: false };\n this.initLegState(input.legsInfo);\n this.subscriptionContext = input.sgpId ? [input.sgpId] : [];\n this.isSuspended = input.isSuspended;\n this.hasEmptyPrice = this.hasNonOpenLegs();\n }\n\n addPick(addPickInput: GroupPickInputs & { pick: BetslipPick }): BaseBetslipGroupPick {\n if (!!addPickInput.pick.groupInfo && addPickInput.pick.groupInfo.groupId !== this.groupInfo.groupId) {\n throw new Error(`Adding pick to a group failed, can't add a pick to a group with different groupId`);\n }\n\n if (this.legExist(addPickInput.pick.id)) {\n throw new Error(`Leg already exists in the group`);\n }\n const group = this.toJSON();\n\n const { sgpId, price, isSuspended, legsInfo } = addPickInput;\n\n return new BetslipGroupPick({\n groupInfo: this._groupInfo!,\n sgpId,\n price,\n isSuspended,\n legsInfo,\n picks: [...group.picks.map((t) => loadPickFromJSON(t)), addPickInput.pick],\n });\n }\n\n addPicks(addPicksInput: GroupPickInputs & { picks: BetslipPick[] }): BaseBetslipGroupPick {\n if (!!addPicksInput.picks[0].groupInfo && addPicksInput.picks[0].groupInfo.groupId !== this.groupInfo.groupId) {\n throw new Error(`Adding pick to a group failed, can't add a pick to a group with different group fixture`);\n }\n\n if (addPicksInput.picks.some((pick) => this.legExist(pick.id))) {\n throw new Error(`Leg already exists in the group`);\n }\n const group = this.toJSON();\n\n const { sgpId, price, isSuspended, legsInfo } = addPicksInput;\n\n return new BetslipGroupPick({\n groupInfo: this._groupInfo!,\n picks: [...group.picks.map((t) => loadPickFromJSON(t)), ...addPicksInput.picks],\n sgpId,\n price,\n legsInfo,\n isSuspended,\n });\n }\n\n removePicks(removePicksInput: GroupPickInputs & { ids: PickId[] }): BetslipPick {\n if (!removePicksInput.ids.every((inputPick) => this.legs.find((p) => p.id.toString() === inputPick.toString()))) {\n throw new Error(`Removing picks failed, at least one pick doesn't belong to the specified group`);\n }\n //if only 1 pick remains in the group, break the group down, and return the remaining pick as a single pick\n if (this.legs.length === removePicksInput.ids.length + 1) {\n const remainingLeg = this.legs.find((l) => !removePicksInput.ids.map((pid) => pid.toString()).includes(l.id.toString()));\n\n if (!remainingLeg) {\n throw new Error(`Removing picks failed, remaining leg was not found`);\n }\n\n return remainingLeg;\n }\n\n const group = this.toJSON();\n group.picks = group.picks.filter((p) => !removePicksInput.ids.some((inputPick) => p.id.toString() === inputPick.toString()));\n const { sgpId, price, isSuspended, legsInfo } = removePicksInput;\n\n return new BetslipGroupPick({\n groupInfo: group.groupInfo!,\n picks: group.picks.map((t) => loadPickFromJSON(t)),\n sgpId,\n price,\n legsInfo,\n isSuspended,\n });\n }\n\n updatePicks(picks: BetslipPick[]): BaseBetslipGroupPick {\n return new BetslipGroupPick({\n groupInfo: this._groupInfo!,\n sgpId: this._sgpId!,\n picks,\n price: this.price,\n legsInfo: Object.values(this._legsState),\n isSuspended: this.isSuspended,\n });\n }\n\n copy(): BetslipGroupPick {\n const copy = this.toJSON();\n\n return BetslipGroupPick.fromJSON(copy);\n }\n\n static fromJSON(value: BetslipGroupPickStorage): BetslipGroupPick {\n if (!value.groupInfo) {\n throw new Error(`Failed instantiating group from storage value, missing group info!`);\n }\n const pick = new BetslipGroupPick({\n groupInfo: value.groupInfo,\n sgpId: value.SGPId,\n picks: value.picks.map((t) => loadPickFromJSON(t)),\n price: value.price,\n legsInfo: Object.values(value.legsState).map((legState) => ({\n clientLegId: legState.clientLegId,\n isVisible: legState.isVisible,\n isClosed: legState.isClosed,\n })),\n isSuspended: value.isSuspended,\n });\n pick.initPropertiesFromJSON(value);\n\n return pick;\n }\n\n override toJSON(): BetslipGroupPickStorage {\n const base = super.toJSON();\n\n return {\n ...base,\n picks: this.legs.map((p) => p.toJSON()),\n price: this.price,\n SGPId: this._sgpId ?? '',\n legsState: this._legsState,\n pickSubType: PickSubType.GroupedPicks,\n isSuspended: this.isSuspended,\n };\n }\n}\n","import { FixtureViewType } from '@cds/betting-offer';\nimport { EventParticipant } from '@frontend/sports/betting-offer/feature/model';\n\nimport { BetslipPick } from './betslip-pick';\nimport { PickId } from './pick-id';\nimport { IPickTracking, IPrice, PriceType } from './pick-models';\nimport { SignedName } from './signed-name.model';\n\nexport class BetslipUnknownPick extends BetslipPick {\n readonly legs: BetslipPick[];\n static isPick(pick: BetslipPick): pick is BetslipUnknownPick {\n return pick instanceof BetslipUnknownPick;\n }\n\n constructor(pickId: PickId, tracking?: IPickTracking, legs?: BetslipPick[]) {\n super();\n this.id = pickId;\n this.market = {\n id: 0,\n isVisible: false,\n isBetBuilderEnabled: false,\n };\n this.priceType = PriceType.NoPrice;\n this.acceptedPrice = {\n price: null,\n isManuallyAccepted: false,\n };\n this.isAvailable = false;\n this.tracking = tracking || { source: '' };\n this.legs = legs ?? [];\n }\n\n // this should not be used, we have the 'legs' field. It exists only because the GroupPicks has a similar function,\n // and it's convenient to combine if statements. IMO GroupPicks should also ditch it and use a readonly field\n getLegs(): BetslipPick[] {\n return this.legs;\n }\n\n override copy(): BetslipUnknownPick {\n const base = super.toJSON();\n\n const pick = new BetslipUnknownPick(PickId.Empty(), void 0, this.legs);\n pick.initPropertiesFromJSON(base);\n\n return pick;\n }\n\n override get eventParticipants(): EventParticipant[] | undefined {\n return undefined;\n }\n\n override get eventViewType(): FixtureViewType | undefined {\n return undefined;\n }\n\n override get eventName(): SignedName {\n return {\n name: '',\n signature: '',\n };\n }\n\n override get isLive(): boolean {\n return false;\n }\n\n override isMarketVisible(): boolean {\n return false;\n }\n\n protected override isPriceVisible(): boolean {\n return false;\n }\n\n override get isVirtual(): boolean {\n return false;\n }\n\n override get marketName(): SignedName {\n return {\n name: '',\n signature: '',\n };\n }\n\n override get eventDate(): Date {\n return new Date();\n }\n\n override get regionName(): SignedName {\n return {\n name: '',\n signature: '',\n };\n }\n\n override get competitionName(): SignedName {\n return {\n name: '',\n signature: '',\n };\n }\n\n override get competitionId(): number {\n return 0;\n }\n\n override get leagueName(): SignedName {\n return {\n name: '',\n signature: '',\n };\n }\n\n override get optionName(): SignedName {\n return {\n name: '',\n signature: '',\n };\n }\n\n override get prices(): IPrice[] {\n return [];\n }\n\n override get sportName(): SignedName {\n return {\n name: '',\n signature: '',\n };\n }\n\n override get partitionId(): number | undefined {\n return undefined;\n }\n\n override get sportId(): number {\n return 0;\n }\n}\n","import { uniqBy } from 'lodash-es';\n\nimport { BetslipBetBuilderPick } from './picks/betslip-bet-builder-pick';\nimport { BaseBetslipGroupPick } from './picks/betslip-group-pick';\nimport { BetslipPick } from './picks/betslip-pick';\nimport { BetslipUnknownPick } from './picks/betslip-unknown-pick';\nimport { BetslipV2OptionMarketPick } from './picks/betslip-v2-option-market-pick';\nimport { BetBuilderPickId, GroupPickId, PickId } from './picks/pick-id';\n\nexport function getPicksCount(picks: BetslipPick[]): number {\n return picks.flatMap((pick) => (BaseBetslipGroupPick.isPick(pick) || isUnknownPickWithLegs(pick) ? pick.getLegs() : pick)).length;\n}\n\nexport function isBetBuilderPick(pick: BetslipPick): pick is BetslipBetBuilderPick | BaseBetslipGroupPick {\n return BetslipBetBuilderPick.isPick(pick) || BaseBetslipGroupPick.isPick(pick);\n}\n\nexport function isBetslipBetBuilderPick(pick: BetslipPick): pick is BetslipBetBuilderPick {\n return BetslipBetBuilderPick.isPick(pick);\n}\n\nexport function isBaseBetslipGroupPick(pick: BetslipPick): pick is BaseBetslipGroupPick {\n return BaseBetslipGroupPick.isPick(pick);\n}\n\nexport function isBetBuilderPickId(pickId: PickId): pickId is BetBuilderPickId | GroupPickId {\n return BetBuilderPickId.isId(pickId) || GroupPickId.isId(pickId);\n}\n\nexport function getOptionNameFromBetslipPick(pick: BetslipPick) {\n return pick.marketName.name + ' - ' + pick.optionName.name;\n}\n\nexport function getLegsCount(picks: BetslipPick[]): number {\n const flattenedPicks = uniqBy(flattenGroupPicksIfAny(picks), (pick) => pick.id.toString());\n\n return flattenedPicks.reduce((acc, curr) => {\n if (BetslipBetBuilderPick.isPick(curr)) {\n return acc + curr.getLegCount();\n }\n\n return acc + 1;\n }, 0);\n}\n\nexport function flattenGroupPicksIfAny(picks: BetslipPick[]): BetslipPick[] {\n return picks.flatMap((p) => (BaseBetslipGroupPick.isPick(p) || isUnknownPickWithLegs(p) ? p.getLegs() : p));\n}\n\n// Expands the legs for group picks. Keeps the groups in the list. Unique check added in case we're calling this method on a list multiple times\nexport function flattenPicks(picks: BetslipPick[]): BetslipPick[] {\n return uniqBy(\n picks.flatMap((p) => (BaseBetslipGroupPick.isPick(p) || isUnknownPickWithLegs(p) ? [p, ...p.getLegs()] : p)),\n (pick) => pick.id.toString(),\n );\n}\n\n// Checks if picks qualify as valid one pick combo. Currently, 2 cases are supported:\n// - Group picks(e.g. Angstrom bet builders)\n// - BetBuilder picks(eg. Sportcast) ONLY in case the feature is enabled\nexport const isValidOnePickCombo = (picks: BetslipPick[], isSportcastAsComboEnabled: boolean) =>\n picks.length === 1 && (BaseBetslipGroupPick.isPick(picks[0]) || (isSportcastAsComboEnabled && BetslipBetBuilderPick.isPick(picks[0])));\n\n// we need the { legs: { 0: BetslipPick } } restriction because if we have only the pick is BetslipUnknownPick\n// then TS will narrow the result type to BetslipUnknownPick, although we expect just a subclass of BetslipUnknownPick\nexport function isUnknownPickWithLegs(pick: BetslipPick): pick is BetslipUnknownPick & { legs: { 0: BetslipPick } } {\n return BetslipUnknownPick.isPick(pick) && pick.legs.length > 0;\n}\n\nexport function getOptionIsDraw(pick: BetslipPick): boolean | undefined {\n return pick instanceof BetslipV2OptionMarketPick ? (pick as BetslipV2OptionMarketPick).option.isDraw : undefined;\n}\n","export enum BetslipType {\n Single = 'SINGLE',\n Combo = 'COMBO',\n Teaser = 'TEASER',\n System = 'SYSTEM',\n EditBet = 'EDIT_MY_BET',\n BetBuilder = 'BET_BUILDER',\n}\n","import { EdsPromoToken, PromoToken } from '@bpos/common/bet-placement';\nimport { Decimal } from 'decimal.js';\n\nimport { BetslipPick } from '../../core/picks/betslip-pick';\nimport { IBasePrice, PickOddsState } from '../../core/picks/pick-models';\nimport { SlipId } from '../../modules/bet-generation/models';\nimport { SlipType } from '../../modules/bet-generation/slip-type';\n\nexport enum OverAskFlowState {\n None, // OverAsk is not active\n Pending, // User is waiting for trader response,\n Review, // User is reviewing trader offer.\n}\n\nexport enum OverAskRejectReason {\n CancelWaiting = 1,\n UserRejectOffer = 2,\n TraderRejectOffer = 3,\n AcceptOfferTimeout = 4,\n BettingOfferNotAvailable = 5,\n PlaceBetFailed = 6,\n TechnicalError = 7,\n ArcUserProfileRestriction = 8,\n}\n\nexport enum OverAskBetslipError {\n OfferExpired = 'ErrorOfferExpired',\n OfferRejected = 'ErrorOfferRejected',\n FlowFailed = 'ErrorFlowFailed',\n PlaceBetFailed = 'ErrorPlaceBetFailed',\n MaximumWinOfBetExceeded = 'ErrorMaximumWinOfBetExceeded',\n}\n\nexport enum OverAskMarketTypeChange {\n Win = 'WIN',\n EachWay = 'EW',\n}\n\nexport interface OverAskPickChanges {\n price?: IBasePrice; // Change in the price\n marketType?: OverAskMarketTypeChange; // Change in market type.\n}\n\nexport interface OverAskBetChange {\n stake?: Decimal; // Change in the bet stake\n}\n\nexport interface OverAskPick {\n index: number;\n pick: BetslipPick;\n isEachWay: boolean;\n isBanker: boolean;\n price: IBasePrice;\n oddsState: PickOddsState;\n changes: OverAskPickChanges;\n}\n\nexport interface OverAskBet {\n slipId: SlipId;\n index: number;\n stake: Decimal;\n type: SlipType;\n isRemoved: boolean;\n picks: OverAskPick[];\n changes: OverAskBetChange;\n promoTokens: PromoToken[];\n edsPromoTokens: EdsPromoToken[];\n betCount?: number;\n}\n\nexport interface IOverAskMessages {\n acceptChanges: string;\n acceptChangesAndPlaceBet: string;\n cancel: string;\n newOdds: string;\n stake: string;\n newStake: string;\n pendingRejectDialogTitle: string;\n pendingBetPlacementStatus: string;\n pendingCheckingYourBet: string;\n reviewInfoMessage: string;\n rejectDialogCancelButton: string;\n pendingRejectDialogContent: string;\n pendingRejectDialogDismissButton: string;\n pendingTitle: string;\n reviewBadgeTitle: string;\n reviewLimitWarning: string;\n reviewInPlayBadgeTitle: string;\n reviewInPlayInfoMessage: string;\n reviewOfferExpires: string;\n reviewRejectDialogTitle: string;\n reviewRejectDialogContent: string;\n reviewRejectDialogDismissButton: string;\n summaryOfferChanges: string;\n removed: string;\n undo: string;\n totalOdds: string;\n totalStake: string;\n possibleWinnings: string;\n possibleWinningsNet: string;\n tax: string;\n maxReturn: string;\n ErrorOfferRejected: string;\n ErrorOfferExpired: string;\n ErrorOfferRejectedTitle: string;\n ErrorOfferExpiredTitle: string;\n ErrorFlowFailed: string;\n ErrorFlowFailedTitle: string;\n ErrorMaximumWinOfBetExceeded: string;\n ErrorOfferZeroStakeHint: string;\n betslipLockedToastMessage: string;\n winMarketAbbr: string;\n eachWayAbbr: string;\n stakeChangeHeader: string;\n stakeChangeMsg: string;\n stakeChangeToggle: string;\n editStakeChangeDesktopUrl: string;\n editStakeChangeMobileUrl: string;\n}\n","/**\n * We should convert picks from instance objects to plain objects and use these helpers instead of methods or properties of the pick.\n * We need to do this, in order to keep plain objects in the betslip state and to apply easily updates and mutate the object as well serializing it.\n */\nimport { SportConstant } from '@frontend/sports/common/core/data-access/constants';\nimport { CalculatedOdds, Fraction, OddsConverter, OddsOperations, emptyCalculatedOdds } from '@frontend/sports/odds/feature';\nimport { Decimal } from 'decimal.js';\n\nimport { IBetslipState } from '../../../base/store/state';\nimport { BetslipType } from '../../../core/betslip-type';\nimport { BetslipBetBuilderPick } from '../../../core/picks/betslip-bet-builder-pick';\nimport { BetslipPick } from '../../../core/picks/betslip-pick';\nimport { BetslipV1Pick } from '../../../core/picks/betslip-v1-pick';\nimport { BetslipV2OptionMarketPick } from '../../../core/picks/betslip-v2-option-market-pick';\nimport { BetslipV2ParticipantPick } from '../../../core/picks/betslip-v2-participant-pick';\nimport { BetslipV2Pick } from '../../../core/picks/betslip-v2-pick';\nimport { BetslipV2StandardPick } from '../../../core/picks/betslip-v2-standard-pick';\nimport { BetslipV2WinParticipantPick } from '../../../core/picks/betslip-v2-win-participant-pick';\nimport { PickId } from '../../../core/picks/pick-id';\nimport {\n HorseRaceParticipantPickType,\n HorseRacePickType,\n IBasePrice,\n IPlaceTerms,\n ParticipantPickType,\n PickOddsState,\n PickType,\n PriceType,\n} from '../../../core/picks/pick-models';\nimport {\n BetslipV2HorseRaceOptionMarketPick,\n BetslipV2HorseRaceWinParticipantPick,\n BetslipV2HorseRaceXCastPick,\n BetslipV2OptionMarketXCastRacePick,\n} from '../../../core/picks/sport-specific/betslip-v2-horse-race-picks';\nimport { OverAskFlowState, OverAskMarketTypeChange } from '../../../model/over-ask/over-ask';\n\n/**\n * Check BetslipV2WinParticipantPick::getEachWayOdds\n *\n * @param price\n * @param placeTerms\n */\nexport function getEachWayOdds(price: IBasePrice, placeTerms: IPlaceTerms): CalculatedOdds {\n if (price.type === PriceType.Fixed && OddsOperations.isOddsValid(price.nativeOdds)) {\n const placeTermsFactor = new Fraction(placeTerms.numerator, placeTerms.denominator);\n\n const euOdds = OddsConverter.decimalToEachWay(new Decimal(price.nativeOdds.decimals), placeTermsFactor);\n const ukOdds = OddsConverter.fractionToEachWay(Fraction.fromJSON(price.nativeOdds.fractional), placeTermsFactor);\n const usOdds = OddsConverter.usToEachWay(new Decimal(price.nativeOdds.moneyline), placeTermsFactor);\n\n return {\n decimals: euOdds,\n fractional: ukOdds,\n moneyline: usOdds,\n };\n } else {\n return emptyCalculatedOdds;\n }\n}\n\nexport const isPickEachWayCapable = function (pick: BetslipPick): pick is BetslipV2WinParticipantPick | BetslipV2OptionMarketPick {\n return (BetslipV2WinParticipantPick.isPick(pick) || BetslipV2OptionMarketPick.isPick(pick)) && pick.isEachWay;\n};\n\nconst getPickEachWayTerms = function (pick: BetslipPick): IPlaceTerms | null {\n if (isPickEachWayCapable(pick)) {\n return pick.market.placeTerms;\n }\n\n return null;\n};\n\nexport function getPickEachWayInfo(pick: BetslipPick): { placeTerms: IPlaceTerms } | null {\n const placeTerms = getPickEachWayTerms(pick);\n\n return placeTerms ? { placeTerms } : null;\n}\n\nexport function getPickPrice(pick: BetslipPick): IBasePrice | null {\n if (pick.oddsState !== PickOddsState.Open) {\n return null;\n }\n const currentPrice = pick.currentPrice;\n if (currentPrice) {\n const { type, nativeOdds } = currentPrice;\n\n return { type, nativeOdds: { ...nativeOdds } };\n }\n\n return null;\n}\n\nexport function getPickPriceIds(pick: BetslipPick): number[] {\n if (BetslipV2ParticipantPick.isPick(pick) && pick.participants.length > 1) {\n return pick.prices.map((pr) => pr.id); // When we have pick with multiple participants return their price ids\n }\n const price = pick.currentPrice;\n if (price) {\n return [price.id];\n }\n\n return [];\n}\n\nexport function getSku(pick: BetslipPick): string {\n if (pick instanceof BetslipV2StandardPick) {\n const sportPick = pick;\n\n return [sportPick.sportId, sportPick.leagueId, sportPick.fixture.fixtureId, sportPick.option.id].join('-');\n }\n\n if (pick instanceof BetslipV1Pick) {\n const sportPick = pick;\n\n return [sportPick.sportId, sportPick.league.id, sportPick.event.id, sportPick.option.id].join('-');\n }\n\n if (pick instanceof BetslipBetBuilderPick) {\n const betBuilderPick = pick;\n\n return [betBuilderPick.sportId, betBuilderPick.leagueId, betBuilderPick.eventId, betBuilderPick.option.id].join('-');\n }\n\n if (pick instanceof BetslipV2ParticipantPick) {\n const racePick = pick;\n\n return [\n racePick.sportId,\n racePick.fixture.league ? racePick.leagueId : 0,\n racePick.fixture.fixtureId,\n (racePick.participants && racePick.participants.map((p) => p.fixtureParticipantId).join('_')) || racePick.id,\n ].join('-');\n }\n\n return '-';\n}\n\nexport function isEachWay(pickId: PickId, context: IBetslipState): boolean {\n if (context.overAskState.flowState !== OverAskFlowState.None) {\n const pick = context.overAskState.bets.flatMap((b) => b.picks).find((p) => p.pick.id.isEqual(pickId))!;\n\n if (pick.changes.marketType) {\n return pick.changes.marketType === OverAskMarketTypeChange.EachWay;\n }\n\n return pick.isEachWay;\n }\n\n switch (context.types.base.currentSelectedType) {\n case BetslipType.Single:\n return context.types.singleBet.picks[pickId.toString()].isEachWay;\n case BetslipType.Combo:\n return context.types.comboBet.isEachWay;\n case BetslipType.System:\n return context.types.systemBet.isEachWay;\n case BetslipType.EditBet:\n return false;\n default:\n throw new Error('No betslip type selected');\n }\n}\n\nexport function isSportPick(pick: BetslipPick): boolean {\n if (BetslipBetBuilderPick.isPick(pick)) {\n return false;\n }\n\n if (BetslipV2Pick.isPick(pick)) {\n return !isRacePick(pick);\n }\n\n return true;\n}\n\nexport function isRacePick(pick: BetslipV2Pick): pick is HorseRacePickType {\n return pick.fixture.sportId === SportConstant.Horses || pick.fixture.sportId === SportConstant.Greyhounds;\n}\n\nexport function isBOGRacePick(pick: BetslipV2Pick): pick is HorseRacePickType {\n return (\n (pick.fixture.sportId === SportConstant.Horses && pick instanceof BetslipV2ParticipantPick) ||\n (pick instanceof BetslipV2HorseRaceOptionMarketPick &&\n pick.pickType === ParticipantPickType.Win &&\n (pick.isStartingPriceAvailable() || pick.isFixedPriceAvailable())) ||\n pick.fixture.sportId === SportConstant.Greyhounds\n );\n}\n\nexport function isRaceParticipantPick(pick: BetslipV2Pick): pick is HorseRaceParticipantPickType {\n return pick.fixture.sportId === SportConstant.Horses && pick.id.getPickType() === PickType.V2ParticipantPick;\n}\n\nexport function isXCastPick(pick: BetslipPick) {\n return BetslipV2HorseRaceXCastPick.isPick(pick) || BetslipV2OptionMarketXCastRacePick.isPick(pick);\n}\n\nexport function isStartingPriceSelected(pick: BetslipPick): boolean {\n return (\n (pick instanceof BetslipV2HorseRaceWinParticipantPick || pick instanceof BetslipV2OptionMarketPick) &&\n pick.currentPrice?.type === PriceType.StartingPrice\n );\n}\n","import { createSelector } from '@ngrx/store';\n\nimport { betslipSelector } from '../../base/store/selectors';\nimport { BetBuilderPickId } from '../../core/picks/pick-id';\nimport { flattenPicks, getLegsCount, getPicksCount, isBetBuilderPick } from '../../core/utils';\nimport { isPickEachWayCapable } from './services/betslip-pick-helpers';\n\nexport const betslipPickStateSelector = createSelector(betslipSelector, (state) => state.picks);\nexport const betslipPicksListSelector = createSelector(betslipPickStateSelector, (state) => state.pickList);\nexport const selectBetslipFlattenedPicksList = createSelector(betslipPicksListSelector, (pickList) => flattenPicks(pickList));\nexport const betslipPicksListCountSelector = createSelector(betslipPicksListSelector, (pickList) => getPicksCount(pickList));\nexport const betslipPicksListCountSelectorIncludeBetbuilderLegs = createSelector(betslipPicksListSelector, (pickList) => getLegsCount(pickList));\nexport const betslipPickIdsSelector = createSelector(betslipPickStateSelector, (state) => state.pickList.flatMap((p) => p.id));\nexport const betslipPicksUiSelector = createSelector(betslipPickStateSelector, (state) => state.ui);\n\nexport const pickWithErrorSelector = createSelector(betslipPickStateSelector, (state) => state.lastPickError);\n\nexport const emptyBetslipPicksListSelector = createSelector(betslipPicksListSelector, (state) => !state.length);\nexport const selectHasEachWayPick = createSelector(betslipPicksListSelector, (picks) => picks.some(isPickEachWayCapable));\n\nexport const selectBetBuilderPicksInfo = createSelector(betslipPicksListSelector, betslipPicksListCountSelector, (picks, betslipPicksCount) => {\n const betBuilderPicks = picks.filter((pick) => BetBuilderPickId.isId(pick.id));\n\n return {\n hasOnlyBetBuilderPicks: betslipPicksCount === betBuilderPicks.length,\n betBuilderPicksCount: betBuilderPicks.length,\n nonBetBuilderPicksCount: betslipPicksCount - betBuilderPicks.length,\n picks: betBuilderPicks,\n };\n});\nexport const selectPicksCountInfo = createSelector(betslipPicksListSelector, betslipPicksListCountSelector, (picks, betslipPicksCount) => {\n const betBuilderPicks = picks.filter((pick) => BetBuilderPickId.isId(pick.id));\n\n return {\n hasOnlyBetBuilderPicks: betslipPicksCount === betBuilderPicks.length,\n betBuilderPicksCount: betBuilderPicks.length,\n nonBetBuilderPicksCount: betslipPicksCount - betBuilderPicks.length,\n };\n});\n\nexport const selectNonBetBuilderPicks = createSelector(selectBetslipFlattenedPicksList, (picks) => picks.filter((pick) => !isBetBuilderPick(pick)));\n\nexport const selectBetslipNonBetBuilderPicksCount = createSelector(selectNonBetBuilderPicks, (pickList) => pickList.length);\n","import { createSelector } from '@ngrx/store';\n\nimport { betslipSelector, selectIsLinearBetslip } from '../../base/store/selectors';\nimport { BetslipType } from '../../core/betslip-type';\nimport { PickId } from '../../core/picks/pick-id';\nimport { selectBetBuilderPicksInfo, selectBetslipFlattenedPicksList, selectNonBetBuilderPicks } from '../picks/selectors';\n\nexport const betslipTypeStateSelector = createSelector(betslipSelector, (b) => b.types);\n\nexport const betslipTabsStateSelector = createSelector(\n betslipTypeStateSelector,\n selectBetBuilderPicksInfo,\n (betslipTypeState, betBuilderPicksState) => ({ betslipTypeState, betBuilderPicksState }),\n);\nexport const editBetStateSelector = createSelector(betslipTypeStateSelector, (state) => state.editBet);\nexport const editBetPicksListSelector = createSelector(betslipSelector, (state) => state.types.editBet.picks.pickList);\nexport const editBetPickStateSelector = createSelector(betslipTypeStateSelector, (state) => state.editBet.picks.pickState);\n\nexport const editBetAddedPicksListSelector = createSelector(betslipSelector, (state) => state.types.editBet.addPicksState.pickList);\n\nexport const editBetPickSelectorFactory = (pickId: PickId) =>\n createSelector(editBetPicksListSelector, editBetAddedPicksListSelector, (pickList, pickAddedList) =>\n [...pickList, ...pickAddedList].find((p) => p.id['id'].toString() === pickId.toString()),\n );\n\nexport const betslipSelectedTypeStateSelector = createSelector(betslipTypeStateSelector, (b) => b.base.currentSelectedType);\n\nexport const selectIsSingleBetSelected = createSelector(betslipSelectedTypeStateSelector, (selected) => selected === BetslipType.Single);\nexport const selectSinglePicksState = createSelector(betslipTypeStateSelector, (b) => b.singleBet);\nexport const selectComboPicksState = createSelector(betslipTypeStateSelector, (b) => b.comboBet);\nexport const selectSystemPicksState = createSelector(betslipTypeStateSelector, (b) => b.systemBet);\nexport const selectBetBuilderPicksState = createSelector(betslipTypeStateSelector, (b) => b.betBuilder);\nexport const selectTeaserPicksState = createSelector(betslipTypeStateSelector, (b) => b.teaserBet);\n\nexport const selectSingleBetPicks = createSelector(\n selectSinglePicksState,\n selectBetslipFlattenedPicksList,\n selectNonBetBuilderPicks,\n selectIsLinearBetslip,\n (singleBet, picksList, nonBetBuilderPickList, isLinear) => {\n const picks = isLinear ? nonBetBuilderPickList : picksList;\n\n return picks.filter((pick) => singleBet.picks[pick.id.toString()]);\n },\n);\n\nexport const selectSystemBetPicks = createSelector(\n selectSystemPicksState,\n selectBetslipFlattenedPicksList,\n selectNonBetBuilderPicks,\n selectIsLinearBetslip,\n (systemBet, picksList, nonBetBuilderPickList, isLinear) => {\n const picks = isLinear ? nonBetBuilderPickList : picksList;\n\n return picks.filter((pick) => systemBet.picks[pick.id.toString()]);\n },\n);\n\nexport const selectSinglePicksCount = createSelector(selectSingleBetPicks, (picks) => picks.length);\nexport const selectComboPicksCount = createSelector(selectComboPicksState, (b) => Object.keys(b.picks).length);\nexport const selectSystemPicksCount = createSelector(selectSystemBetPicks, (picks) => picks.length);\nexport const selectBetBuilderPicksCount = createSelector(selectBetBuilderPicksState, (b) => Object.keys(b.picks).length);\nexport const selectTeaserPicksCount = createSelector(selectTeaserPicksState, (b) => Object.keys(b.picks).length);\nexport const selectTypesPickCount = createSelector(\n selectSinglePicksCount,\n selectComboPicksCount,\n selectSystemPicksCount,\n selectBetBuilderPicksCount,\n selectTeaserPicksCount,\n\n (singlePicksCount, comboPicksCount, systemPicksCount, betBuilderPicksCount, teaserPicksCount) => ({\n singlePicksCount,\n comboPicksCount,\n systemPicksCount,\n betBuilderPicksCount,\n teaserPicksCount,\n }),\n);\n\nexport const selectHasBetBuilderStatePicks = createSelector(selectBetBuilderPicksCount, (count) => count > 0);\n","import { BetslipType } from '../../core/betslip-type';\nimport { SlipId } from '../bet-generation/models';\nimport { BetslipError } from './errors/betslip-error';\nimport { ResultError } from './errors/result/result-error';\n\nexport interface IBetslipPickErrors {\n [pickId: string]: ResultError[];\n}\n\nexport type BetslipTypePickErrors = Record;\nexport type BetslipTypeErrors = Record;\nexport type SlipErrors = Record;\n\n// New error state necessary to support linear betslip\n// Try to avoid using the state directly and instead use one of the many selectors or create a new selector\nexport interface IBetslipErrorsState {\n pickErrors: BetslipTypePickErrors;\n slipErrors: SlipErrors;\n betslipTypeErrors: BetslipTypeErrors;\n betslipErrors: BetslipError[];\n}\n\nexport interface IBetslipLegacyErrorsState {\n pickErrors: IBetslipPickErrors;\n betslipErrors: BetslipError[];\n}\n\nexport const defaultErrorState: IBetslipErrorsState = {\n pickErrors: {\n SINGLE: {},\n COMBO: {},\n SYSTEM: {},\n TEASER: {},\n EDIT_MY_BET: {},\n BET_BUILDER: {},\n },\n slipErrors: {},\n betslipTypeErrors: {\n SINGLE: [],\n COMBO: [],\n SYSTEM: [],\n TEASER: [],\n EDIT_MY_BET: [],\n BET_BUILDER: [],\n },\n betslipErrors: [],\n};\n\nexport const emptyPickErrors = (): BetslipTypePickErrors => ({\n SINGLE: {},\n COMBO: {},\n SYSTEM: {},\n TEASER: {},\n EDIT_MY_BET: {},\n BET_BUILDER: {},\n});\n\nexport const emptyBetslipTypeErrors = (): BetslipTypeErrors => ({\n SINGLE: [],\n COMBO: [],\n SYSTEM: [],\n TEASER: [],\n EDIT_MY_BET: [],\n BET_BUILDER: [],\n});\n","import { Injectable } from '@angular/core';\n\nimport { LocalStoreService, NativeAppService, SessionStoreService } from '@frontend/vanilla/core';\n\nimport { ISavedState } from './state-storage.model';\n\n@Injectable({ providedIn: 'root' })\nexport default class StoragePersister {\n private readonly STORE_KEY = 'redux-store-v3';\n\n constructor(\n private session: SessionStoreService,\n private local: LocalStoreService,\n private nativeAppService: NativeAppService,\n ) {}\n\n /**\n * Apply the new saved state over the current saved and save it.\n * This will allow features to save state individually.\n *\n * @param state update on state\n */\n save(state: Partial): void {\n if (!this.nativeAppService.isTerminal) {\n this.local.set(this.STORE_KEY, state);\n\n return;\n }\n this.session.set(this.STORE_KEY, state);\n }\n\n load(): ISavedState | undefined {\n const savedState = !this.nativeAppService.isTerminal\n ? this.local.get(this.STORE_KEY)\n : this.session.get(this.STORE_KEY);\n\n return savedState ? savedState : undefined;\n }\n}\n","import { TemplateRef } from '@angular/core';\n\nimport { OddsAcceptanceMode } from '@bpos';\nimport { OfferSource } from '@cds';\nimport { Fixture } from '@cds/betting-offer';\nimport { PicksView } from '@cds/betting-offer/domain-specific';\nimport { EventModel } from '@frontend/sports/betting-offer/feature/model';\nimport { pprops } from '@frontend/sports/common/core/utils/redux';\nimport { createAction, props } from '@ngrx/store';\nimport { BetBuilderMarket } from 'packages/sports/web/app/src/betbuilder/model/bet-builder.model';\nimport { OptionRequest } from 'packages/sports/web/app/src/navigation-core/pick-uri.model';\n\nimport { EditMyBetPicksPayload } from '../model/edit-mybet/edit-bet-init';\nimport { BetStationFreeBetToken } from '../modules/reward-tokens/reward-tokens.model';\nimport { BetslipType } from './betslip-type';\nimport { BetslipGroupInformation } from './groups/betslip-group-information';\nimport { BetBuilderPickId, PickId, V1PickId, V2OptionMarketPickId, V2OptionMarketXCastPickId, V2ParticipantPickId } from './picks/pick-id';\nimport { IPickTracking } from './picks/pick-models';\n\nexport interface BetBuilderV1EventData {\n event: {\n id: string;\n name: string;\n date: Date;\n isLive?: boolean;\n groupId?: number;\n };\n league: {\n id: number;\n name: string;\n parentLeagueId?: number;\n realCompetitionId?: number;\n };\n region: {\n id: number;\n name: string;\n };\n offerSource: OfferSource;\n betBuilderTradingV2FixtureId?: string;\n}\n\nexport interface PickAddPayload {\n pickId: V1PickId | V2OptionMarketPickId | V2ParticipantPickId | V2OptionMarketXCastPickId;\n tracking: IPickTracking; // Pick source tracking information\n priceId?: number; // In case when pick can have multiple prices the call can choose which one to be selected ( optional )\n isPriceBoosted?: boolean;\n groupInfo?: BetslipGroupInformation;\n parentLinkedEventId?: string;\n isBetBuilder?: boolean;\n}\nexport interface PickRemovePayload {\n pickId: V1PickId | V2OptionMarketPickId | V2ParticipantPickId;\n tracking: IPickTracking; // Pick source tracking information\n priceId?: number; // In case when pick can have multiple prices the call can choose which one to be selected ( optional )\n isPriceBoosted?: boolean;\n}\n\nexport interface BABAdditionalInfo {\n sportcastId?: number;\n betgeniusId?: number;\n useV2Key?: boolean;\n longIds?: string[];\n}\n\nexport enum BetslipHost {\n Digital = 'Digital',\n BetStation = 'BetStation',\n}\n\nexport class ExternalBetslipActions {\n static readonly ACTION_SCHEMA = `PICKS`;\n\n private static readonly ADD_PICK = `${ExternalBetslipActions.ACTION_SCHEMA}/ADD_PICK`;\n private static readonly ADD_GROUP_PICK = `${ExternalBetslipActions.ACTION_SCHEMA}/ADD_GROUP_PICK`;\n private static readonly ADD_MULTIPLE_PICKS = `${ExternalBetslipActions.ACTION_SCHEMA}/ADD_MULTIPLE_PICKS`;\n private static readonly ADD_BET_BUILDER_PICK = `${ExternalBetslipActions.ACTION_SCHEMA}/ADD_BET_BUILDER_PICK`;\n private static readonly ECHO_ADD_BET_BUILDER_PICK = `${ExternalBetslipActions.ACTION_SCHEMA}/ADDED_BET_BUILDER_PICK`;\n private static readonly ECHO_ADD_PICK = `${ExternalBetslipActions.ACTION_SCHEMA}/ADDED_PICK`;\n private static readonly ADD_ENTAIN_UI_BET_BUILDER_PICK = `${ExternalBetslipActions.ACTION_SCHEMA}/ADD_ENTAIN_UI_BET_BUILDER_PICK`;\n private static readonly REMOVE_ENTAIN_UI_BET_BUILDER_PICK = `${ExternalBetslipActions.ACTION_SCHEMA}/REMOVE_ENTAIN_UI_BET_BUILDER_PICK`;\n private static readonly CHECK_PRECREATED_BAB_CONVERSION = `${ExternalBetslipActions.ACTION_SCHEMA}/CHECK_PRECREATED_BAB_CONVERSION`;\n\n private static readonly REMOVE_PICK = `${ExternalBetslipActions.ACTION_SCHEMA}/REMOVE_PICK`;\n private static readonly ECHO_REMOVE_PICK = `${ExternalBetslipActions.ACTION_SCHEMA}/REMOVED_PICK`;\n private static readonly REMOVE_MULTIPLE_PICKS = `${ExternalBetslipActions.ACTION_SCHEMA}/REMOVE_MULTIPLE_PICKS`;\n private static readonly ECHO_REMOVE_MULTIPLE_PICKS = `${ExternalBetslipActions.ACTION_SCHEMA}/REMOVED_MULTIPLE_PICKS`;\n private static readonly REMOVE_PICKS_FROM_GROUP = `${ExternalBetslipActions.ACTION_SCHEMA}/REMOVE_PICKS_FROM_GROUP`;\n\n private static readonly REQUEST_BETSLIP_TYPE = `${ExternalBetslipActions.ACTION_SCHEMA}/REQUEST_BETSLIP_TYPE`;\n\n private static readonly REQUEST_ADD_AFFILIATE_PICKS = `${ExternalBetslipActions.ACTION_SCHEMA}/REQUEST_ADD_AFFILIATE_PICKS`;\n private static readonly SHARE_AFFILIATE_PICKS = `${ExternalBetslipActions.ACTION_SCHEMA}/SHARE_AFFILIATE_PICKS`;\n private static readonly START_EDIT_MY_BET = `${ExternalBetslipActions.ACTION_SCHEMA}/START_EDIT_MY_BET`;\n\n private static readonly REQUEST_NOTIFICATION_SETTING_UPDATE = `${ExternalBetslipActions.ACTION_SCHEMA}/REQUEST_NOTIFICATION_SETTING_UPDATE`;\n\n private static readonly ADD_WEB_APP_PICK = `${ExternalBetslipActions.ACTION_SCHEMA}/ADD_WEB_APP_PICK`;\n private static readonly REMOVE_WEB_APP_PICK = `${ExternalBetslipActions.ACTION_SCHEMA}/REMOVE_WEB_APP_PICK`;\n\n private static readonly SET_REWARD_TOKEN = `${ExternalBetslipActions.ACTION_SCHEMA}/SET_REWARD_TOKEN`;\n private static readonly TOGGLE_WIN_PICK = `${ExternalBetslipActions.ACTION_SCHEMA}/TOGGLE_WIN_PICK`;\n\n /**\n * Action called with request to add v1 or option market pick to betslip,\n * Optionally set stake value when first pick to override the default stake.\n */\n static addPick = createAction(ExternalBetslipActions.ADD_PICK, props());\n\n /**\n * Action called with request to add several v1 or option market picks in a SGP group to betslip\n */\n static addGroupPick = createAction(\n ExternalBetslipActions.ADD_GROUP_PICK,\n props<{ picks: PickAddPayload[]; headerTemplate?: TemplateRef }>(),\n );\n\n /**\n * Action called with request to add v1 or option market pick to betslip\n */\n\n static addMultiplePicks = createAction(ExternalBetslipActions.ADD_MULTIPLE_PICKS, props<{ picks: PickAddPayload[]; stake?: number }>());\n /**\n * Action called with request to add bet builder pick to betslip, when v1\n */\n static addBetBuilderPick = createAction(\n ExternalBetslipActions.ADD_BET_BUILDER_PICK,\n props<{ pickId: BetBuilderPickId; tracking: IPickTracking; eventData: BetBuilderV1EventData | null; additionalInfo?: BABAdditionalInfo }>(),\n );\n\n /**\n * Action called with request to add entain ui bet builder pick to betslip\n */\n static addEntainUiBetBuilderPick = createAction(\n ExternalBetslipActions.ADD_ENTAIN_UI_BET_BUILDER_PICK,\n props<{\n pickId: BetBuilderPickId;\n tracking: IPickTracking;\n eventModel: EventModel;\n betBuilderOptionMarket: BetBuilderMarket;\n eventData: BetBuilderV1EventData;\n longId: string;\n }>(),\n );\n\n /**\n * Action called with request to remove entain ui bet builder pick to betslip\n */\n static removeEntainUiBetBuilderPick = createAction(\n ExternalBetslipActions.REMOVE_ENTAIN_UI_BET_BUILDER_PICK,\n props<{\n pickId: BetBuilderPickId;\n longId: string;\n eventId: string;\n }>(),\n );\n\n /**\n * Action called with request to check if there is already a customized BAB for the event on the betslip\n */\n static checkPrecreatedBABConversion = createAction(\n ExternalBetslipActions.CHECK_PRECREATED_BAB_CONVERSION,\n props<{\n pickId: BetBuilderPickId;\n eventModel: EventModel;\n market: BetBuilderMarket;\n eventData: BetBuilderV1EventData;\n longId: string[];\n headerTemplate: TemplateRef;\n isBetslip?: boolean;\n }>(),\n );\n\n /**\n * Action called when pick is added to betslip\n */\n static echoAddPick = createAction(ExternalBetslipActions.ECHO_ADD_PICK, props());\n\n /**\n * Action called with request to add bet builder pick to betslip on betstation cross screen\n */\n static echoAddBetBuilderPick = createAction(\n ExternalBetslipActions.ECHO_ADD_BET_BUILDER_PICK,\n props<{ pickId: BetBuilderPickId; tracking: IPickTracking; eventData: BetBuilderV1EventData | null; additionalInfo?: BABAdditionalInfo }>(),\n );\n\n /**\n * Action called with request to remove pick from betslip\n */\n static removePick = createAction(ExternalBetslipActions.REMOVE_PICK, props<{ pickId: PickId }>());\n\n /**\n * Action called when pick is removed from betslip\n */\n static echoRemovePick = createAction(ExternalBetslipActions.ECHO_REMOVE_PICK, props<{ pickId: PickId }>());\n\n /**\n * Remove picks from the slip\n */\n static removeMultiplePicks = createAction(ExternalBetslipActions.REMOVE_MULTIPLE_PICKS, props<{ pickIds: PickId[] }>());\n\n /**\n * Action called when pick is removed from betslip\n */\n static echoRemoveMultiplePicks = createAction(ExternalBetslipActions.ECHO_REMOVE_MULTIPLE_PICKS, props<{ pickIds: PickId[] }>());\n\n /**\n * Remove picks from group in the slip\n */\n static removePicksFromGroup = createAction(ExternalBetslipActions.REMOVE_PICKS_FROM_GROUP, props<{ pickIds: PickId[] }>());\n\n /**\n * Action called in order betslip to change its type\n */\n static requestBetslipType = createAction(ExternalBetslipActions.REQUEST_BETSLIP_TYPE, props<{ betslipType: BetslipType }>());\n\n /**\n * Action called when affiliate deep link is found\n */\n static requestAddAffiliatePicks = createAction(\n ExternalBetslipActions.REQUEST_ADD_AFFILIATE_PICKS,\n props<{ request: OptionRequest; picksView: PicksView }>(),\n );\n\n /**\n * Action called to share bet (in course of affiliate deep link)\n */\n static shareBet = createAction(ExternalBetslipActions.SHARE_AFFILIATE_PICKS);\n\n /**\n * Action called when bet editing is started\n */\n static startEditMyBet = createAction(ExternalBetslipActions.START_EDIT_MY_BET, props<{ payload: EditMyBetPicksPayload }>());\n\n /**\n * Action called to update (all - e.g. email or app) notification settings\n */\n static requestNotificationSettingUpdate = createAction(\n ExternalBetslipActions.REQUEST_NOTIFICATION_SETTING_UPDATE,\n props<{ oddsAcceptance?: OddsAcceptanceMode; emailNotify?: boolean; appNotify?: boolean }>(),\n );\n\n /**\n * Action called to add a pick via web bridge service (e.g. used for AB testing)\n */\n static addWebAppPick = createAction(\n ExternalBetslipActions.ADD_WEB_APP_PICK,\n props<{\n fixture: Fixture;\n option: number;\n }>(),\n );\n /**\n \n * Action called to remove a pick via web bridge service (e.g. used for AB testing)\n \n */\n\n static removeWebAppPick = createAction(\n ExternalBetslipActions.REMOVE_WEB_APP_PICK,\n\n props<{\n fixture: Fixture;\n option: number;\n }>(),\n );\n\n /**\n * Action called to set a reward token (e.g. via free-bet.service)\n */\n static setBetstationFreeBetToken = createAction(ExternalBetslipActions.SET_REWARD_TOKEN, pprops());\n\n /**\n * Action called when Toggle Win pick is called\n */\n static toggleWinPick = createAction(\n ExternalBetslipActions.TOGGLE_WIN_PICK,\n props<{ pickId: V1PickId | V2OptionMarketPickId | V2ParticipantPickId }>(),\n );\n}\n","import { createSelector } from '@ngrx/store';\n\nimport { selectIsLinearBetslip } from '../../../base/store/selectors';\nimport { BetslipType } from '../../../core/betslip-type';\nimport { betslipTypeStateSelector } from '../selectors';\n\nexport const betslipTypesBaseSelector = createSelector(betslipTypeStateSelector, (b) => b.base);\nexport const betslipCurrentTypeSelector = createSelector(betslipTypesBaseSelector, (b) => b.currentSelectedType);\nexport const betslipActiveTypesSelector = createSelector(betslipTypesBaseSelector, (b) => b.activeTypes);\nexport const selectLinearTypes = createSelector(betslipTypesBaseSelector, (b) => b.linearTypes);\n\nexport const selectIsLinearSingleBetBuilder = createSelector(\n selectIsLinearBetslip,\n selectLinearTypes,\n (isLinear, linearTypes) => isLinear && !linearTypes[BetslipType.Combo] && linearTypes[BetslipType.BetBuilder],\n);\n","export enum BetPlacementErrorIcon {\n Warning,\n Error,\n}\n","import { CashoutErrorType } from '@bpos/v1/cashout';\nimport { PlacementErrorType } from '@frontend/sports/types/betslip';\n\nimport { BetslipType } from '../../../core/betslip-type';\nimport { BetPlacementErrorIcon } from './bet-placement-error-icon';\nimport { ClientErrorType } from './client-error-type';\n\nexport enum ErrorOrigin {\n // Error comes from the server\n BetPlacement = 'BetPlacement',\n // Error is created on the client.\n BetSlip = 'BetSlip',\n}\n\nexport enum CleanStrategy {\n // Is the error is cleaned on every action.\n Always = 'Always',\n // Is the error cleaned on manual action ( Errors from BPS, Some Warnings. )\n Manually = 'Manually',\n}\n\nexport interface BetslipErrorRestrictions {\n betslipType: BetslipType;\n slipTypeKey?: string;\n}\n\nexport interface BetslipErrorSaved {\n // Error type string name of the error.\n type: PlacementErrorType | CashoutErrorType | ClientErrorType;\n // Error icon to display.\n icon: BetPlacementErrorIcon;\n // Priority of the error. Lower is bigger. We display only one error, so errors with lower priority will be displayed first.\n priority: number;\n // The source of the error.\n origin: ErrorOrigin;\n // The strategy to remove the error.\n cleanStrategy: CleanStrategy;\n // Does the error have client validation ?\n hasClientValidation: boolean;\n // Should we display a message to the user for this error.\n isHidden: boolean;\n // The error code of the error.\n code: string;\n // Is this error restricted to a betslip type or slip type?\n restrictions?: BetslipErrorRestrictions;\n}\n\nexport class BetslipError implements BetslipErrorSaved {\n // Error type string name of the error.\n type: PlacementErrorType | CashoutErrorType | ClientErrorType;\n // Error icon to display.\n icon: BetPlacementErrorIcon = BetPlacementErrorIcon.Error;\n // Priority of the error. Lower is bigger. We display only one error, so errors with lower priority will be displayed first.\n priority = 0;\n // The source of the error.\n origin: ErrorOrigin = ErrorOrigin.BetSlip;\n // The strategy to remove the error.\n cleanStrategy: CleanStrategy = CleanStrategy.Always;\n // Does the error have client validation ?\n hasClientValidation = false;\n // Should we display a message to the user for this error.\n isHidden = false;\n // The error code of the error.\n code: string;\n\n slipId: string;\n\n sportId: number | null;\n\n /**\n * Is one error equals to another.\n *\n * @param error\n */\n equals(error: BetslipError): boolean {\n // Return false always.\n return false;\n }\n\n copy(): this {\n return Object.assign(Object.create(Object.getPrototypeOf(this)), this);\n }\n}\n","import { PlacementErrorType } from '@frontend/sports/types/betslip';\n\nimport { PosError } from '../../../base/utils/pos-errors';\nimport { BetslipError, BetslipErrorSaved } from './betslip-error';\n\nexport interface BetPlacementErrorSaved extends BetslipErrorSaved {\n type: PlacementErrorType;\n errorMessage: string;\n newStakeHint?: number;\n}\n\nexport class BetPlacementError extends BetslipError implements BetPlacementErrorSaved {\n constructor() {\n super();\n }\n\n override type: PlacementErrorType;\n errorMessage: string;\n platformError: PosError;\n newStakeHint?: number;\n detailedErrorCode?: string;\n}\n","import { PlacementErrorType } from '@frontend/sports/types/betslip';\n\nimport { BetPlacementError } from '../bet-placement-error';\n\nexport class UserError extends BetPlacementError {\n constructor() {\n super();\n this.type = PlacementErrorType.UserError;\n }\n}\n","import { UserError } from './user/user-error';\n\nexport class StakeError extends UserError {\n constructor() {\n super();\n }\n}\n","import { PlacementErrorType } from '@frontend/sports/types/betslip';\n\nimport { BetPlacementErrorIcon } from '../bet-placement-error-icon';\nimport { StakeError } from '../stake-error';\n\nexport class UnderMinimumStake extends StakeError {\n constructor() {\n super();\n this.icon = BetPlacementErrorIcon.Warning;\n this.hasClientValidation = true;\n this.type = PlacementErrorType.StakeBelowMinimumLimit;\n }\n}\n","import { BetslipError, CleanStrategy, ErrorOrigin } from '../betslip-error';\n\nexport interface PreCheckErrorMessages {\n AllowedStakes: string;\n ComboPrevention: string;\n ComboPreventionSameEvent: string;\n ForecastSystemBet: string;\n FreeBetAdded: string;\n minComboBetsCount: string;\n MinimumCombo: string;\n MinStakePerBet: string;\n MinStakeTotal: string;\n MinStakeValue: string;\n minSystemBetsCount: string;\n BetbuilderSystemNotAllowed: string;\n MultipleSinglesNotAllowed: string;\n NotEnoughMoney: string;\n NotMultiple: string;\n PickAdded: string;\n PickAlreadyAdded: string;\n PickNotFound: string;\n PickRemoved: string;\n PicksMaximum: string;\n LessStakeAfterTax: string;\n SelectExactRaceSystemBets: string;\n SelectMaxComboBets: string;\n SelectMaxRaceComboBets: string;\n SelectMaxSystemBets: string;\n SelectMinComboBets: string;\n SelectMinComboBetsLinear: string;\n SelectMinSingleBets: string;\n SelectMinSystemBets: string;\n SelectMaxSystemBetsBanker: string;\n SelectMinSystemBetsBanker: string;\n SelectMaxTeaserBets: string;\n MinTeaserBetsCount: string;\n SelectMinTeaserBets: string;\n}\n\n// We initially used the message key directly matching an error to a specific message type, this means we couldn't have specific messages for linear\n// Solution:\n// - New abstraction here (ErrorDetailSpecifier)\n// - Resolve the ErrorDetailSpecifier to a Message Key in a later step\nexport enum ErrorDetailsSpecifier {\n MinComboBetsCount,\n MinSystemBetsCount,\n MinTeaserBetsCount,\n PicksMaximum,\n SelectMaxComboBets,\n SelectMaxRaceComboBets,\n SelectMaxSystemBets,\n SelectMinComboBets,\n SelectMinSingleBets,\n SelectMinSystemBets,\n SelectMaxSystemBetsBanker,\n SelectMinSystemBetsBanker,\n SelectMinTeaserBets,\n SelectMaxTeaserBets,\n ForecastSystemBet,\n}\n\nexport interface IPickPreCheckError {\n pickId: string;\n}\n\ninterface TClassType {\n new (...args: any[]): T;\n prototype: T;\n}\n\nexport function PreCheckErrorMixin = TClassType>(\n // eslint-disable-next-line @typescript-eslint/naming-convention, no-underscore-dangle, id-blacklist, id-match\n BaseErrorType: TBaseClass,\n): TBaseClass {\n return class extends BaseErrorType {\n constructor(...args: any[]) {\n super(...args);\n this.origin = ErrorOrigin.BetSlip;\n this.cleanStrategy = CleanStrategy.Always;\n this.hasClientValidation = true;\n }\n };\n}\n","import { Decimal } from 'decimal.js';\n\nimport { BetslipType } from '../../../../core/betslip-type';\nimport { UnderMinimumStake } from '../general/under-minimum-stake';\nimport { PreCheckErrorMixin } from './pre-check-error';\nimport { ITypeRestrictedPreCheckError } from './type-restricted-pre-check-error.interface';\n\nexport class UnderMinimumStakePreCheckError extends PreCheckErrorMixin(UnderMinimumStake) {\n constructor(\n readonly minimumStake: Decimal,\n readonly betStake: Decimal,\n readonly numberOfBetsInBet: number,\n readonly pickId: string,\n teaserId: number,\n ) {\n super();\n this.sportId = typeof teaserId === 'undefined' ? null : teaserId;\n }\n\n getMinimumStake = () => this.minimumStake;\n}\n\nexport class BetslipTypeUnderMinimumStakePreCheckError extends UnderMinimumStakePreCheckError implements ITypeRestrictedPreCheckError {\n constructor(\n minimumStake: Decimal,\n betStake: Decimal,\n numberOfBetsInBet: number,\n pickId: string,\n teaserId: number,\n private errorBetslipType: BetslipType,\n private errorSlipTypeKey?: string,\n ) {\n super(minimumStake, betStake, numberOfBetsInBet, pickId, teaserId);\n }\n\n get betslipType(): BetslipType {\n return this.errorBetslipType;\n }\n\n get slipTypeKey(): string | undefined {\n return this.errorSlipTypeKey;\n }\n}\n","import { BetslipType } from '../../core/betslip-type';\nimport { SystemSlipType } from '../bet-generation/slip-type';\nimport { ISingleBetPickState, ISingleBetState } from '../single-bet/state';\nimport { ISystemBetState } from '../system-bet/state';\nimport { IBetslipTypeState } from '../types/state';\nimport { BetslipError } from '../validation/errors/betslip-error';\nimport { UnderMinimumStakePreCheckError } from '../validation/errors/pre-check/under-minimum-stake-pre-check-error';\n\nexport function GetSingleBetStake(singleBet: ISingleBetState, pickId: string): string | null {\n return singleBet.picks[pickId]?.actualStake;\n}\n\nexport function GetSummaryStake(types: IBetslipTypeState): string | null {\n if (types.base.currentSelectedType === BetslipType.Combo) {\n return types.comboBet?.actualStake;\n } else if (types.base.currentSelectedType === BetslipType.System) {\n return types.systemBet?.actualStake;\n } else if (types.base.currentSelectedType === BetslipType.Teaser) {\n return types.teaserBet.actualStake;\n }\n\n return null;\n}\n\nexport function GetStakeForSystemType(systemBetState: ISystemBetState, type: SystemSlipType): string | null {\n const typeStakes = systemBetState.linearTypeStakes ?? {};\n\n return typeStakes[type.key].actualStake ?? null;\n}\n\nconst MAX_POSSIBLE_DIGIT = '9';\n\nconst canPotentialyTypeDigitsToReachMinStake = (stake: string, minimumStake: string) => {\n if (stake.indexOf('.') < 0 && minimumStake.indexOf('.') >= 0) {\n return true;\n }\n let highestPotentialStake = stake;\n const minStakeDecimalPlaces = minimumStake.split('.')[1]?.length || 0;\n const stakeDecimalPlaces = stake.split('.')[1]?.length || 0;\n for (let i = 0; i < minStakeDecimalPlaces - stakeDecimalPlaces; i++) {\n highestPotentialStake += MAX_POSSIBLE_DIGIT;\n }\n if (highestPotentialStake.endsWith('.')) {\n highestPotentialStake = highestPotentialStake.substring(0, highestPotentialStake.length - 1);\n }\n\n return Number(highestPotentialStake) >= Number(minimumStake);\n};\n\nexport const HideMinError = (minimumStake: number, isNumpadOpen: boolean, totalStake: string | null) => {\n if (!totalStake) {\n return true;\n }\n\n const actualStake = Number(totalStake);\n if (isNumpadOpen && actualStake < minimumStake) {\n return canPotentialyTypeDigitsToReachMinStake(totalStake, Number(minimumStake).toString());\n }\n\n return actualStake >= minimumStake;\n};\n\nexport function isSameStakeAppliedToAllSingles(picks: Record): boolean {\n const stakes = Object.values(picks)\n .filter((pick) => pick.isSelected)\n .map((pick) => pick.stake);\n\n return new Set(stakes).size === 1;\n}\n\nexport function validateStake(stakeError: BetslipError | undefined, actualStake: string | null, isNumpadOpen: boolean): BetslipError | null {\n if (!stakeError) {\n return null;\n }\n\n if (!stakeError.hasClientValidation) {\n return null;\n }\n\n if (stakeError instanceof UnderMinimumStakePreCheckError) {\n const shouldHide = HideMinError(stakeError.minimumStake.toNumber(), isNumpadOpen, actualStake);\n if (shouldHide) {\n return null;\n }\n }\n\n return stakeError;\n}\n\nexport function parseStakeFieldStake(stakeString: string | null | undefined): number | null {\n const numberStake = stakeString ? +stakeString : null;\n\n if (numberStake === 0) {\n // 0 stake should count as no stake being entered\n return null;\n }\n\n return numberStake;\n}\n","import { createSelector } from '@ngrx/store';\nimport { pickBy } from 'lodash-es';\n\nimport { PickId } from '../../core/picks/pick-id';\nimport { IStake } from '../stake/models';\nimport { isSameStakeAppliedToAllSingles } from '../stake/utils';\nimport { betslipTypeStateSelector, selectIsSingleBetSelected } from '../types/selectors';\nimport { ISingleBetPickState } from './state';\n\nexport const singleBetStateSelector = createSelector(betslipTypeStateSelector, (typeState) => typeState.singleBet);\nexport const singleBetPicksSelector = createSelector(singleBetStateSelector, (s) => s.picks);\n\nexport const isMultiSingleBetSelector = createSelector(\n singleBetPicksSelector,\n (singleBetState) => Object.values(singleBetState).filter((pick) => pick.isSelected).length > 1,\n);\n\nexport const singleBetPickSelectorFactory = (pickId: PickId) =>\n createSelector(singleBetPicksSelector, (picks): ISingleBetPickState | undefined => {\n return picks[pickId.toString()];\n });\n\nexport const selectSingleBetPickStakeFactory = (pickId: PickId) =>\n createSelector(singleBetPickSelectorFactory(pickId), (pick): IStake | null => {\n return pick ? { actualStake: pick.actualStake, stake: pick.stake } : null;\n });\n\nexport const singleBetSelectedPicksSelector = createSelector(singleBetPicksSelector, (picks) => pickBy(picks, (pick) => pick.isSelected));\n\nexport const singleBetPicksGeneralStakeSelector = createSelector(singleBetPicksSelector, (picks) => {\n const singleStates = Object.values(picks);\n\n if (singleStates.length < 1) {\n return null;\n }\n\n const firstState = singleStates[0];\n\n const stake = firstState.stake;\n\n if (singleStates.every((singleState) => singleState.stake === stake)) {\n return { stake, actualStake: firstState.actualStake };\n }\n\n return { stake: null, actualStake: null };\n});\n\nexport const selectSingleBetAllSameStake = createSelector(selectIsSingleBetSelected, singleBetPicksSelector, (isSingleBet, picks) => {\n return isSingleBet && isSameStakeAppliedToAllSingles(picks);\n});\n\nexport const selectSingleBetSinglePickState = createSelector(singleBetSelectedPicksSelector, (selectedPicks) => {\n const states = Object.values(selectedPicks);\n\n return states.length === 1 ? states[0] : null;\n});\n\nexport const selectSingleBetSinglePickId = createSelector(singleBetSelectedPicksSelector, (selectedPicks) => {\n const states = Object.keys(selectedPicks);\n\n return states.length === 1 ? states[0] : null;\n});\n","/// \n/// System bet type, \n/// \nexport enum SystemType {\n // Singles = N combinations\n SystemSingle = 0,\n // Combo = 1 combination\n SystemCombo = 1,\n\n /// \n /// 3 combo bets 2/3\n /// \n System2Of3 = 2, // Make it equal to 2 so to unify it with BetSlipType\n\n /// \n /// 3 combo bets 2/3 + 1 combo bet 3/3 + 3 single bets\n /// \n SystemPatent,\n\n /// \n /// 3 combo bets 2/3 + 1 combo bet 3/3\n /// \n SystemTrixie,\n\n /// \n /// 6 combo bets 2/4\n /// \n System2Of4,\n\n /// \n /// 4 combo bets 3/4\n /// \n System3Of4,\n\n /// \n /// 6 combo bets 2/4 + 4 combo bets 3/4 + 1 combo bet 4/4\n /// \n SystemYankee,\n\n /// \n /// 6 combo bets 2/4 + 4 combo bets 3/4 + 1 combo bet 4/4 + 4 single bets\n /// \n SystemLucky15,\n\n /// \n /// 10 combo bets 2/5\n /// \n System2Of5,\n\n /// \n /// 10 combo bets 3/5\n /// \n System3Of5,\n\n /// \n /// 5 combo bets 4/5\n /// \n System4Of5,\n\n /// \n /// 10 combo bets 2/5 + 10 combo bets 3/5 + 5 combo bets 4/5 + 1 combo bet 5/5\n /// \n SystemCanadian,\n\n /// \n /// 10 combo bets 2/5 + 10 combo bets 3/5 + 5 combo bets 4/5 + 1 combo bet 5/5 + 5 single bets\n /// \n SystemLucky31,\n\n /// \n /// 15 combo bets 2/6\n /// \n System2Of6,\n\n /// \n /// 20 combo bets 3/6\n /// \n System3Of6,\n\n /// \n /// 15 combo bets 4/6\n /// \n System4Of6,\n\n /// \n /// 6 combo bets 5/6\n /// \n System5Of6,\n\n /// \n /// 15 combo bets 2/6 + 20 combo bets 3/6 + 15 combo bets 4/6 + 6 combo bets 5/6 + 1 combo bet 6/6\n /// \n SystemHeinz,\n\n /// \n /// 15 combo bets 2/6 + 20 combo bets 3/6 + 15 combo bets 4/6 + 6 combo bets 5/6 + 1 combo bet 6/6 + 6 single bets\n /// \n SystemLucky63,\n\n /// \n /// 21 combo bets 2/7\n /// \n System2Of7,\n\n /// \n /// 35 combo bets 3/7\n /// \n System3Of7,\n\n /// \n /// 35 combo bets 4/7\n /// \n System4Of7,\n\n /// \n /// 21 combo bets 5/7\n /// \n System5Of7,\n\n /// \n /// 7 combo bets 6/7\n /// \n System6Of7,\n\n /// \n /// 21 combo bets 2/7 + 35 combo bets 3/7 + 35 combo bets 4/7 + 21 combo bets 5/7 + 7 combo bets 6/7 + 1 combo bet 7/7\n /// \n SystemSuperHeinz,\n\n /// \n /// 28 combo bets 2/8\n /// \n System2Of8,\n\n /// \n /// 56 combo bets 3/8\n /// \n System3Of8,\n\n /// \n /// 70 combo bets 4/8\n /// \n System4Of8,\n\n /// \n /// 56 combo bets 5/8\n /// \n System5Of8,\n\n /// \n /// 28 combo bets 6/8\n /// \n System6Of8,\n\n /// \n /// 8 combo bets 7/8\n /// \n System7Of8,\n\n /// \n /// 28 combo bets 2/8 + 56 combo bets 3/8 + 70 combo bets 4/8 + 56 combo bets 5/8 + 28 combo bets 6/8 + 8 combo bets 7/8 + 1 combo bet 8/8\n /// \n SystemGoliath,\n\n /// \n /// Banker bets + single bets.\n /// \n SystemBankerSingles,\n /// \n /// Multi single bets, used in betstation to place multiple bets as atomic transaction.\n /// \n SystemSingles,\n}\n\nexport enum SlipTypeKeys {\n Single = 'single',\n Combo = 'combo',\n BetBuilder = 'betbuilder',\n}\n","import { SystemSlipType } from '../bet-generation/slip-type';\n\nexport enum SystemKey {\n Patent = 'patent',\n Lucky15 = 'lucky15',\n Lucky31 = 'lucky31',\n Lucky63 = 'lucky63',\n Trixie = 'trixie',\n Yankee = 'yankee',\n Canadian = 'canadian',\n Heinz = 'heinz',\n SuperHeinz = 'superheinz',\n Goliath = 'goliath',\n // eslint-disable-next-line @typescript-eslint/naming-convention\n MOfN = 'mofn',\n BankerSingles = 'systembankersingles',\n}\n\nexport enum SystemModesLinearMOfN {\n System2of3 = 'system2of3',\n System2of4 = 'system2of4',\n System2of5 = 'system2of5',\n System2of6 = 'system2of6',\n System2of7 = 'system2of7',\n System2of8 = 'system2of8',\n System3of4 = 'system3of4',\n System3of5 = 'system3of5',\n System3of6 = 'system3of6',\n System3of7 = 'system3of7',\n System3of8 = 'system3of8',\n System4of5 = 'system4of5',\n System4of6 = 'system4of6',\n System4of7 = 'system4of7',\n System4of8 = 'system4of8',\n System5of6 = 'system5of6',\n System5of7 = 'system5of7',\n System5of8 = 'system5of8',\n System6of7 = 'system6of7',\n System6of8 = 'system6of8',\n System7of8 = 'system7of8',\n}\n\nexport interface SystemModes {\n canadian: string;\n goliath: string;\n heinz: string;\n lucky15: string;\n lucky31: string;\n lucky63: string;\n patent: string;\n superheinz: string;\n system2of3: string;\n system2of4: string;\n system2of5: string;\n system2of6: string;\n system2of7: string;\n system2of8: string;\n system3of4: string;\n system3of5: string;\n system3of6: string;\n system3of7: string;\n system3of8: string;\n system4of5: string;\n system4of6: string;\n system4of7: string;\n system4of8: string;\n system5of6: string;\n system5of7: string;\n system5of8: string;\n system6of7: string;\n system6of8: string;\n system7of8: string;\n trixie: string;\n yankee: string;\n}\n\nexport interface ISlipInfo {\n slipType: SystemSlipType;\n betCount: number;\n}\n\nexport enum LinearSystemBetTracking {\n SystemBetPositionEvent = 'Sytem bet expand/collapse',\n HidePickListEventDetails = 'Collapse system bet',\n ViewPickListEventDetails = 'Expand system bet',\n SystemBetPosition = 'system bet',\n}\n\nexport interface ISystemSlipTypeInfo {\n slipType: SystemSlipType;\n betCount: number;\n typeName: string;\n}\n\nexport const LinearSystemBetPositionEvent = 'Linear System Bet Info Popup';\nexport const LinearSystemBetEventDetailsOpened = 'System Bet Info Icon Open';\nexport const LinearSystemBetEventDetailsClosed = 'System Bet Info Icon Closed';\nexport const LinearSystemBetActionEvent = 'Click';\n","import { SystemKey } from '../system-bet/models';\nimport { SlipTypeKeys } from './betslip-system-type';\n\nexport abstract class SlipType {\n private _key: string;\n private _name: string;\n private _basis: number[];\n\n constructor(key: string, name: string, basis: number[]) {\n this._key = key;\n this._name = name;\n this._basis = basis;\n }\n\n get key(): string {\n return this._key;\n }\n\n get name(): string {\n return this._name;\n }\n\n get basis(): number[] {\n return this._basis;\n }\n\n equals(object: SlipType | null | undefined): boolean {\n if (!object) {\n return false;\n }\n\n return this.key === object.key && SlipType.isBasisEqual(this.basis, object.basis);\n }\n\n toJSON(): { key: string; name: string; basis: number[] } {\n return {\n key: this.key,\n name: this.name,\n basis: this.basis,\n };\n }\n\n private static isBasisEqual(thisBasis: number[], otherBasis: number[]): boolean {\n if (thisBasis.length !== otherBasis.length) {\n return false;\n }\n for (let i = 0; i < thisBasis.length; i++) {\n if (thisBasis[i] !== otherBasis[i]) {\n return false;\n }\n }\n\n return true;\n }\n}\n\nexport class SingleSlipType extends SlipType {\n constructor() {\n super(SlipTypeKeys.Single, '', [1]);\n }\n}\n\nexport class ComboSlipType extends SlipType {\n constructor() {\n super(SlipTypeKeys.Combo, '', [-1]);\n }\n}\n\nexport class SystemSlipType extends SlipType {\n constructor(key: string, name: string, basis: number[]) {\n super(key, name, basis);\n }\n}\n\nexport class SystemBankerSinglesSlipType extends SystemSlipType {\n constructor() {\n super(SystemKey.BankerSingles, '', [1]);\n }\n}\n","import { BetslipType } from '../../../core/betslip-type';\nimport { BetslipPick } from '../../../core/picks/betslip-pick';\nimport { PickId } from '../../../core/picks/pick-id';\nimport { SlipTypeKeys } from '../betslip-system-type';\nimport { ISlip, SlipId } from '../models';\nimport { ComboSlipType, SingleSlipType, SystemSlipType } from '../slip-type';\n\nconst SYSTEM_SLIP_ID_PREFIX = 'system__';\nconst BET_BUILDER_SLIP_ID_PREFIX = `${SlipTypeKeys.BetBuilder}__`;\nconst SINGLE_SLIP_ID_PREFIX = `${SlipTypeKeys.Single}__`;\n\nexport function getSlipBetslipType(slip: ISlip): BetslipType | undefined {\n const type = slip.type;\n\n if (type instanceof SingleSlipType) {\n return BetslipType.Single;\n }\n\n if (type instanceof ComboSlipType) {\n return BetslipType.Combo;\n }\n\n if (type instanceof SystemSlipType) {\n return BetslipType.System;\n }\n\n return undefined;\n}\n\nexport function getEditBetSlipId(): string {\n return 'editbet';\n}\n\nexport function getComboSlipId(): string {\n return SlipTypeKeys.Combo;\n}\n\nexport function getSystemSlipId(slipType: SystemSlipType): string {\n return getSystemSlipIdFromString(slipType.key);\n}\n\nexport function getSystemSlipIdFromString(slipKey: string): string {\n return `${SYSTEM_SLIP_ID_PREFIX}${slipKey}`;\n}\n\nexport function isSystemSlipId(slipId: SlipId): boolean {\n return slipId.startsWith(SYSTEM_SLIP_ID_PREFIX);\n}\n\nexport function getSystemTypeKeyFromSlipId(slipId: SlipId): string | undefined {\n if (!isSystemSlipId(slipId)) {\n return undefined;\n }\n\n return slipId.substring(SYSTEM_SLIP_ID_PREFIX.length);\n}\n\nexport function isComboSlipId(slipId: SlipId): boolean {\n return slipId === getComboSlipId();\n}\n\nexport function getSingleSlipId(pickId: PickId): string {\n return pickId ? getSingleSlipIdFromString(pickId.toString()) : '';\n}\n\nexport function getSingleSlipIdFromString(pickId: string): string {\n return `${SINGLE_SLIP_ID_PREFIX}${pickId}`;\n}\n\nexport function getBetBuilderSlipId(pickId: PickId): string {\n return getBetBuilderSlipIdFromString(pickId.toString());\n}\n\nexport function getBetBuilderSlipIdFromString(pickId: string): string {\n return `${BET_BUILDER_SLIP_ID_PREFIX}${pickId}`;\n}\n\nexport function isSingleSlipId(slipId: SlipId): boolean {\n return slipId.startsWith(SINGLE_SLIP_ID_PREFIX);\n}\n\nexport function getTeaserSlipId(): string {\n return 'teaser';\n}\n\nexport function isTeaserSlipId(slipId: SlipId): boolean {\n return slipId === getTeaserSlipId();\n}\n\nexport function isBetBuilderSlipId(slipId: SlipId): boolean {\n return slipId.startsWith(BET_BUILDER_SLIP_ID_PREFIX);\n}\n\nexport function getBetslipTypeFromId(slipId: SlipId): BetslipType | null {\n if (isSingleSlipId(slipId)) {\n return BetslipType.Single;\n }\n\n if (isComboSlipId(slipId)) {\n return BetslipType.Combo;\n }\n\n if (isTeaserSlipId(slipId)) {\n return BetslipType.Teaser;\n }\n\n if (isBetBuilderSlipId(slipId)) {\n return BetslipType.BetBuilder;\n }\n\n if (isSystemSlipId(slipId)) {\n return BetslipType.System;\n }\n\n return null;\n}\n\n/**\n * Returns a unique list of picks accross multiple slips\n */\nexport function getUniqueSlipPicks(slips: ISlip[]): BetslipPick[] {\n const allPicks = slips.flatMap((slip) => Object.values(slip.slipPicks));\n\n return allPicks.reduce(\n (acc, curr, i) => {\n if (acc.some((pick) => pick.id === curr.pick.id)) {\n return acc;\n }\n\n return acc.concat([curr.pick]);\n },\n [],\n );\n}\n","import { createSelector } from '@ngrx/store';\n\nimport { getLegsCount } from '../../core/utils';\nimport { betslipPicksListSelector } from '../picks/selectors';\nimport { betslipTypeStateSelector } from '../types/selectors';\nimport { BetBuilderPick, LinearBetBuilderPickId } from './models';\n\nexport const selectBetBuilderTypeState = createSelector(betslipTypeStateSelector, (betslipTypeState) => betslipTypeState.betBuilder);\n\nexport const selectBetBuilderTypeStatePicks = createSelector(selectBetBuilderTypeState, (betBuilderTypeState) => betBuilderTypeState.picks);\n\nexport const selectBetBuilderPicks = createSelector(\n selectBetBuilderTypeStatePicks,\n betslipPicksListSelector,\n (betBuilderPicks, pickList) => pickList.filter((pick) => !!betBuilderPicks[pick.id.toString()]) as BetBuilderPick[],\n);\n\nexport const selectBetBuilderPickStakeFactory = (pickId: LinearBetBuilderPickId) =>\n createSelector(selectBetBuilderTypeStatePicks, (picks) => {\n const pick = picks[pickId.toString()];\n\n if (!pick) {\n return {\n actualStake: null,\n stake: null,\n };\n }\n\n return {\n actualStake: pick.actualStake,\n stake: pick.stake,\n };\n });\n\nexport const selectBetBuilderPickLegsFactory = (pickId: LinearBetBuilderPickId) =>\n createSelector(betslipPicksListSelector, (pickList) => {\n const pick = pickList.find((p) => p.id.isEqual(pickId));\n\n return pick ? getLegsCount([pick]) : 0;\n });\n","import { createSelector } from '@ngrx/store';\n\nimport { BetslipType } from '../../core/betslip-type';\nimport { SlipId } from '../bet-generation/models';\nimport { getBetBuilderSlipIdFromString, getSingleSlipIdFromString, getSystemSlipIdFromString } from '../bet-generation/services/slip.utils';\nimport { betslipTypeStateSelector } from '../types/selectors';\nimport { IBetslipTypeState } from '../types/state';\n\nexport const selectLinearStakeForSlip = (slipId: SlipId, betslipType: BetslipType) =>\n createSelector(betslipTypeStateSelector, (types: IBetslipTypeState): string | null => {\n switch (betslipType) {\n case BetslipType.Combo:\n return types.comboBet.actualStake;\n\n case BetslipType.Teaser:\n return types.teaserBet.actualStake;\n\n case BetslipType.Single: {\n const pickEntry = Object.entries(types.singleBet.picks).find(([pickId]) => getSingleSlipIdFromString(pickId) === slipId);\n\n if (!pickEntry) {\n return null;\n }\n\n const [, pick] = pickEntry;\n\n return pick.actualStake ?? null;\n }\n\n case BetslipType.System: {\n const type = Object.keys(types.systemBet.linearTypeStakes).find((k) => getSystemSlipIdFromString(k) === slipId);\n\n if (!type) {\n return null;\n }\n\n const typeStake = types.systemBet.linearTypeStakes[type];\n\n return typeStake?.actualStake ?? null;\n }\n\n case BetslipType.EditBet: {\n return types.editBet.addedStake.toString();\n }\n\n case BetslipType.BetBuilder: {\n const pickEntry = Object.entries(types.betBuilder.picks).find(([pickId]) => getBetBuilderSlipIdFromString(pickId) === slipId);\n\n if (!pickEntry) {\n return null;\n }\n\n const [, pick] = pickEntry;\n\n return pick.actualStake ?? null;\n }\n }\n });\n","import { createSelector } from '@ngrx/store';\n\nimport { PickId } from '../../core/picks/pick-id';\nimport { PickOddsState } from '../../core/picks/pick-models';\nimport { betslipTypeStateSelector, selectSystemBetPicks } from '../types/selectors';\nimport { ISystemBetPickState, ISystemBetTypeStake } from './state';\n\nexport const systemBetStateSelector = createSelector(betslipTypeStateSelector, (state) => state.systemBet);\nexport const systemBetPicksSelector = createSelector(systemBetStateSelector, (s) => s.picks);\nexport const systemBetSystemInfoSelector = createSelector(systemBetStateSelector, (s) => s.systemInfo);\nexport const systemBetBankerEnabledSelector = createSelector(systemBetStateSelector, (s) => s.isBankerActive);\nexport const systemBetBankerSelector = createSelector(systemBetStateSelector, (s) => ({\n isBankerActive: s.isBankerActive,\n hasBanker: Object.values(s.picks).some((p) => p.isBanker),\n}));\nexport const systemBetBankerCount = createSelector(systemBetStateSelector, (s) =>\n s.isBankerActive ? Object.values(s.picks).filter((p) => p.isSelected && !p.isLocked && p.isBanker).length : 0,\n);\n\nexport const systemBetBarStateSelector = createSelector(systemBetStateSelector, (systemBetState) => {\n return { isBankerActive: systemBetState.isBankerActive, selectedSlipType: systemBetState.systemInfo?.key };\n});\n\nexport const systemBetPickSelectorFactory = (pickId: PickId) =>\n createSelector(systemBetPicksSelector, (picks) => {\n return picks[pickId.toString()];\n });\n\nexport const systemBetTypeStakeSelectorFactory = (typeKey: string) =>\n createSelector(systemBetStateSelector, (state): ISystemBetTypeStake | null => {\n const typeStakes = state.linearTypeStakes ?? {};\n\n return typeStakes[typeKey] ?? null;\n });\n\nexport const systemBetLinearSlipTypeStakesSelector = createSelector(systemBetStateSelector, (s) => {\n return Object.values(s.linearTypeStakes).map((typeStake) => typeStake);\n});\n\nexport const systemBetPickOddStateSelector = createSelector(systemBetStateSelector, selectSystemBetPicks, (systemBetState, pickList) => {\n const picks: { [pickId: string]: ISystemBetPickState & { isOpen: boolean } } = {};\n\n pickList.forEach((pick) => {\n picks[pick.id.toString()] = {\n ...systemBetState.picks[pick.id.toString()],\n isOpen: pick.oddsState === PickOddsState.Open,\n };\n });\n\n return picks;\n});\n\nexport const systemBetStateSelectorPickOddStateSelector = createSelector(\n systemBetStateSelector,\n systemBetPickOddStateSelector,\n (systemBetState, picks) => {\n return { ...systemBetState, picks };\n },\n);\n\nexport const selectIsSystemBetExpanded = createSelector(systemBetStateSelector, (state) => state.isLinearExpanded);\n","import { BetslipError } from '../betslip-error';\n\nexport class MinSelectionsBetbuilderError extends BetslipError {\n constructor() {\n super();\n this.hasClientValidation = true;\n }\n}\n","import { BetPlacementError, BetPlacementErrorSaved } from '../bet-placement-error';\n\nexport interface ResultErrorSaved extends BetPlacementErrorSaved {\n pickId: string;\n}\n\nexport class ResultError extends BetPlacementError implements ResultErrorSaved {\n constructor(readonly pickId: string) {\n super();\n }\n}\n","import { PlacementErrorType } from '@frontend/sports/types/betslip';\n\nimport { BetslipType } from '../../../../core/betslip-type';\nimport { BetslipPick } from '../../../../core/picks/betslip-pick';\nimport {\n getBetBuilderSlipIdFromString,\n getComboSlipId,\n getEditBetSlipId,\n getSingleSlipId,\n getSingleSlipIdFromString,\n getSystemSlipId,\n getTeaserSlipId,\n} from '../../../bet-generation/services/slip.utils';\nimport { IBetslipTypeState } from '../../../types/state';\nimport { BetslipError, ErrorOrigin } from '../../errors/betslip-error';\nimport { ResultError } from '../../errors/result/result-error';\nimport {\n BetslipTypeErrors,\n BetslipTypePickErrors,\n IBetslipErrorsState,\n IBetslipPickErrors,\n SlipErrors,\n emptyBetslipTypeErrors,\n emptyPickErrors,\n} from '../../state';\n\nexport function updateKeyBasedErrorCollection(\n currentCollection: Record,\n newCollection: Record,\n defaultCollection: Record,\n oldKeys: TKey[],\n newKeys: TKey[],\n): { errors: Record; hasChanged: boolean } {\n let hasChanged = false;\n\n const result = { ...defaultCollection };\n const clientTypeErrors = { ...defaultCollection };\n\n // Iterate through all keys (all slip types) for current errors\n for (const key of oldKeys) {\n const currentErrors = currentCollection[key];\n\n const clientResult = [];\n const serverResult = [];\n\n // Split into client and non-client errors\n for (const error of currentErrors) {\n if (error.hasClientValidation) {\n clientResult.push(error);\n\n hasChanged = hasChanged || !newKeys.includes(key);\n continue;\n }\n\n serverResult.push(error);\n }\n\n if (clientResult.length) {\n // Prepare client errors for comparison\n clientTypeErrors[key] = clientResult;\n }\n\n if (serverResult.length) {\n // Keep all non-client errors\n result[key] = serverResult;\n }\n }\n\n // Iterate through all keys (all slip types) for new errors\n for (const key of newKeys) {\n const clientErrors = clientTypeErrors[key];\n const newErrors = newCollection[key];\n\n // Make sure we keep result errors from before\n const existingResultErrors = result[key];\n\n if (!newErrors.length) {\n result[key] = existingResultErrors ? existingResultErrors : [];\n hasChanged = hasChanged || !oldKeys.includes(key) || !!clientErrors?.length;\n\n continue;\n }\n\n // Check if we have current errors for this key\n if (!clientErrors || !clientErrors.length) {\n // If we don't have current errors, just take the new ones\n result[key] = existingResultErrors ? [...existingResultErrors, ...newErrors] : newErrors;\n hasChanged = true;\n\n continue;\n }\n\n // If we have current errors for this slip, update the errors\n const resultErrors = errorCollectionUpdater(clientErrors, newErrors);\n\n result[key] = existingResultErrors ? [...existingResultErrors, ...resultErrors] : resultErrors;\n\n // If the errors have changed before or the result errors do not equal the current erros, set hasChanged to true\n hasChanged = hasChanged || resultErrors !== clientErrors;\n }\n\n return {\n errors: result,\n hasChanged,\n };\n}\n\n/**\n * A betslip errors reducer helper. Add/Replace/Deletes errors from current errors for which we have a validator\n *\n * @param currentErrors\n * @param newErrors - It is mandatory all new errors to be with hasClientValidation === true.\n */\nexport const errorCollectionUpdater = (currentErrors: TError[], newErrors: TError[]): TError[] => {\n // Get all errors that are not validated on client.\n const result: TError[] = [];\n const clientErrors: TError[] = [];\n for (const error of currentErrors) {\n if (error.hasClientValidation) {\n clientErrors.push(error);\n } else {\n result.push(error);\n }\n }\n\n let hasNewError = false;\n\n // Go foreach of new errors.\n for (const newError of newErrors) {\n // Try to check if already this error exists.\n const sameError = clientErrors.find((be) => be.equals(newError));\n\n // If error is already present do not update it.\n if (sameError) {\n // Take the same error just change the origin.\n const updatedError = sameError.copy();\n updatedError.origin = ErrorOrigin.BetSlip;\n result.push(updatedError);\n } else {\n hasNewError = true;\n result.push(newError);\n }\n }\n if (result.length === currentErrors.length && !hasNewError) {\n return currentErrors;\n }\n\n return result;\n};\n\nexport function extractPickErrors(\n pickErrors: BetslipTypePickErrors,\n betslipTypeErrors: BetslipTypeErrors,\n): [BetslipTypePickErrors, BetslipTypeErrors] {\n const extractedPickErrors: BetslipTypePickErrors = {\n SINGLE: { ...pickErrors[BetslipType.Single] },\n COMBO: { ...pickErrors[BetslipType.Combo] },\n SYSTEM: { ...pickErrors[BetslipType.System] },\n TEASER: { ...pickErrors[BetslipType.Teaser] },\n EDIT_MY_BET: { ...pickErrors[BetslipType.EditBet] },\n BET_BUILDER: { ...pickErrors[BetslipType.BetBuilder] },\n };\n\n const filteredTypeErrors: BetslipTypeErrors = emptyBetslipTypeErrors();\n\n for (const type of [BetslipType.Single, BetslipType.Combo, BetslipType.System, BetslipType.Teaser, BetslipType.EditBet, BetslipType.BetBuilder]) {\n const typeErrors = betslipTypeErrors[type];\n const typePickErrors = extractedPickErrors[type];\n\n for (const error of typeErrors) {\n if (error instanceof ResultError && !!error.pickId) {\n typePickErrors[error.pickId] = typePickErrors[error.pickId] || [];\n\n typePickErrors[error.pickId].push(error);\n continue;\n }\n\n filteredTypeErrors[type].push(error);\n }\n }\n\n return [extractedPickErrors, filteredTypeErrors];\n}\n\nexport const getTypedPickErrors = function (pickErrors: BetslipTypePickErrors): [BetslipType, IBetslipPickErrors][] {\n return Object.entries(pickErrors).map(([key, errors]) => [key as BetslipType, errors]);\n};\n\nconst getTypedBetslipErrors = function (slipErrors: BetslipTypeErrors): [BetslipType, BetslipError[]][] {\n return Object.entries(slipErrors).map(([key, errors]) => [key as BetslipType, errors]);\n};\n\nexport const filterErrorState = (\n state: IBetslipErrorsState,\n betslipErrorsPredicate: (error: BetslipError) => boolean,\n betslipTypeErrorsPredicate: (error: BetslipError) => boolean,\n slipErrorsPredicate: (error: BetslipError) => boolean,\n pickErrorsPredicate: (error: ResultError) => boolean,\n): IBetslipErrorsState => {\n const betslipErrors = state.betslipErrors.filter(betslipErrorsPredicate);\n\n const pickErrors: BetslipTypePickErrors = emptyPickErrors();\n const betslipTypeErrors: BetslipTypeErrors = emptyBetslipTypeErrors();\n const slipErrors: Record = {};\n\n for (const [type, errors] of getTypedPickErrors(state.pickErrors)) {\n const pickIds = Object.keys(errors);\n\n for (const pickId of pickIds) {\n pickErrors[type][pickId] = errors[pickId].filter(pickErrorsPredicate);\n }\n }\n\n for (const [type, errors] of getTypedBetslipErrors(state.betslipTypeErrors)) {\n betslipTypeErrors[type] = errors.filter(betslipTypeErrorsPredicate);\n }\n\n for (const [key, errors] of Object.entries(state.slipErrors)) {\n slipErrors[key] = errors.filter(slipErrorsPredicate);\n }\n\n return {\n pickErrors,\n betslipTypeErrors,\n betslipErrors,\n slipErrors,\n };\n};\n\n// Caution: This modifies the original errors which is fine for reducers since they handle immutability.\n// This should be used with caution elsewhere.\nexport const modifyErrorState = (\n state: IBetslipErrorsState,\n betslipErrorsMap: (error: BetslipError) => [BetslipError, boolean],\n betslipTypeErrorsMap: (error: BetslipError) => [BetslipError, boolean],\n slipErrorsMap: (error: BetslipError) => [BetslipError, boolean],\n pickErrorsMap: (error: ResultError) => [ResultError, boolean],\n): IBetslipErrorsState & { isModified: boolean } => {\n const betslipErrorsMapping = state.betslipErrors.map(betslipErrorsMap);\n\n let isModified = betslipErrorsMapping.some(([_, modified]) => modified);\n\n const betslipErrors = betslipErrorsMapping.map(([e]) => e);\n\n const pickErrors: BetslipTypePickErrors = emptyPickErrors();\n const betslipTypeErrors: BetslipTypeErrors = emptyBetslipTypeErrors();\n const slipErrors: Record = {};\n\n for (const [type, errors] of getTypedPickErrors(state.pickErrors)) {\n const pickIds = Object.keys(errors);\n\n for (const pickId of pickIds) {\n const mappedErrors = errors[pickId].map(pickErrorsMap);\n\n isModified = isModified || mappedErrors.some(([_, modified]) => modified);\n\n pickErrors[type][pickId] = mappedErrors.map(([e]) => e);\n }\n }\n\n for (const [type, errors] of getTypedBetslipErrors(state.betslipTypeErrors)) {\n const mappedErrors = errors.map(betslipTypeErrorsMap);\n\n isModified = isModified || mappedErrors.some(([_, modified]) => modified);\n\n betslipTypeErrors[type] = mappedErrors.map(([e]) => e);\n }\n\n for (const [key, errors] of Object.entries(state.slipErrors)) {\n const mappedErrors = errors.map(slipErrorsMap);\n\n isModified = isModified || mappedErrors.some(([_, modified]) => modified);\n\n slipErrors[key] = mappedErrors.map(([e]) => e);\n }\n\n return {\n pickErrors,\n betslipTypeErrors,\n betslipErrors,\n slipErrors,\n isModified,\n };\n};\n\nexport function getSlipErrorsForType(type: BetslipType, slipErrors: SlipErrors, typesState: IBetslipTypeState, isLinear: boolean): BetslipError[] {\n switch (type) {\n case BetslipType.Combo:\n return slipErrors[getComboSlipId()] ?? [];\n\n case BetslipType.Teaser:\n return slipErrors[getTeaserSlipId()] ?? [];\n\n case BetslipType.System:\n return isLinear\n ? Object.values(typesState.systemBet.linearTypeStakes).flatMap((systemType) => slipErrors[getSystemSlipId(systemType.slipType)] ?? [])\n : typesState.systemBet.systemInfo\n ? (slipErrors[getSystemSlipId(typesState.systemBet.systemInfo)] ?? [])\n : [];\n\n case BetslipType.Single:\n return Object.keys(typesState.singleBet.picks).flatMap((pick) => slipErrors[getSingleSlipIdFromString(pick)] ?? []);\n\n case BetslipType.EditBet:\n return slipErrors[getEditBetSlipId()] ?? [];\n\n case BetslipType.BetBuilder:\n return Object.keys(typesState.betBuilder.picks).flatMap((pick) => slipErrors[getBetBuilderSlipIdFromString(pick)] ?? []);\n }\n}\n\nexport function checkSlipErrorsForSinglePicks(slipErrors: SlipErrors, picks: BetslipPick[], predicate: (error: BetslipError) => boolean): boolean {\n return picks.some((pick) => slipErrors[getSingleSlipId(pick.id)]?.some(predicate));\n}\n\nexport function hasOddsChangedError(pickErrors: BetslipTypePickErrors): boolean {\n return Object.values(pickErrors).some((p) =>\n Object.values(p).some((errors) => errors.some((error) => error.type === PlacementErrorType.OddsChanged)),\n );\n}\n\nexport const getAllPickErrorTypes = function (typePickErrors: BetslipTypePickErrors): Set {\n const types = Object.values(typePickErrors).flatMap((typeErrors) =>\n Object.values(typeErrors).flatMap((errors) => errors.map((error) => error.type)),\n );\n\n return new Set(types);\n};\n\nexport const hasAnyErrors = function (state: IBetslipErrorsState): boolean {\n return (\n state.betslipErrors.length > 0 ||\n Object.values(state.slipErrors).some((s) => s.length > 0) ||\n Object.values(state.betslipTypeErrors).some((s) => s.length > 0) ||\n Object.values(state.pickErrors).some((p) => Object.values(p).some((e) => e.length > 0))\n );\n};\n\nexport const getErrorsAcrossTypesForPicks = function (errors: BetslipTypePickErrors): IBetslipPickErrors {\n return Object.values(errors).reduce((a, c) => {\n const pickErrors = Object.entries(c);\n\n for (const [key, value] of pickErrors) {\n a[key] = a[key] ? [...a[key], ...value] : [...value];\n }\n\n return a;\n }, {} as IBetslipPickErrors);\n};\n","import { BetSlipType, OddsBoostFormula } from '@bpos';\nimport { ReplaceableParameter } from '@bpos/v1/sports-promo/campaigns';\nimport { RewardTargetType } from '@bpos/v1/sports-promo/tokens';\nimport { NumberDictionary, StringDictionary } from '@frontend/sports/common/core/utils/extended-types';\nimport { Odds } from '@frontend/sports/odds/feature';\nimport {\n BetStatus,\n BuildABet,\n PayoutType,\n RewardTokenType,\n RewardTriggerType,\n TokenFilter,\n} from 'packages/sports/web/app/src/tokens-base/token-base.models';\n\nimport { BetslipType } from '../../core/betslip-type';\nimport { PickId } from '../../core/picks/pick-id';\nimport { IRewardTokenEligibility } from './state';\n\nexport const ACCA_BOOST_ID = 'AccaBoost';\n\nexport interface IRewardToken {\n id: string;\n startDate: Date;\n expiryDate: Date;\n cmsItemId: string;\n userTokenId: string;\n minimumLegs?: number;\n wageringMultiplier?: number;\n payoutType: PayoutType;\n tokenType: string;\n filter: TokenFilter;\n rewardTokenType: RewardTokenType;\n payoutExpiry?: number;\n minOdds?: number;\n customTitle?: string;\n maximumStake?: number;\n selectionLevelMinimumOdds?: TokenOddsData;\n selectionLevelMaximumOdds?: TokenOddsData;\n replaceableParameters?: StringDictionary;\n minimumOdds?: TokenOddsData;\n maximumOdds?: TokenOddsData;\n customDescription?: string;\n isBetBuilder?: boolean;\n isSingleAndComboSelected?: boolean;\n url?: string;\n customMarketTypes?: string;\n rewardTargetType?: RewardTargetType;\n isIncrementalPayout?: boolean;\n incrementalBetAmount?: number;\n incrementalPayoutNoOfTimes?: number;\n isVipExclusive?: boolean;\n}\n\nexport interface OddsBoostToken extends IRewardToken {\n boostFactor: number;\n maxWinningsBoost?: number;\n minimumStake?: number;\n oddsBoostFormula: OddsBoostFormula;\n}\n\nexport interface RiskFreeToken extends IRewardToken {\n maximumPayout: number;\n minimumStake?: number;\n riskFreePercentage: number;\n}\n\nexport interface FreeBetToken extends IRewardToken {\n amount: number;\n}\n\nexport interface BetAndGetToken extends IRewardToken {\n percentagePayout: number | undefined;\n payoutValue: number;\n rewardTriggerType: RewardTriggerType;\n maximumPayout?: number;\n minimumPayout?: number;\n betStatus: BetStatus;\n minimumStake: number;\n subType: string;\n isEarlyPayoutEnabled?: boolean;\n buildABet?: BuildABet;\n}\n\nexport interface TokenOddsData {\n european: number;\n britishNumerator: number;\n britishDenominator: number;\n american: number;\n}\n\nexport interface BetStationFreeBetToken extends IRewardToken {\n amount: number;\n currency: string;\n marketTemplateIds: Array;\n marketParameters: Array;\n}\n\nexport interface AccaBoostToken extends IRewardToken {\n boostLadder: AccaBoostLadderItem[];\n maxReward: number;\n priority: number;\n filter: AccaBoostFilter;\n countryCodes: string[];\n}\nexport interface EdsToken extends IRewardToken {\n priority?: number;\n replaceableParameters: Record;\n filter: EdsFilter;\n}\n\nexport interface AccaBoostLadderItem {\n numberOfLegs: number;\n ratio: number;\n}\nexport interface EdsFilter extends TokenFilter {\n marketIds: Array;\n minSelections?: number;\n maxSelections?: number;\n minStake?: number;\n maxStake?: number;\n currency?: string;\n overallMinOdds?: Odds;\n overallMaxOdds?: Odds;\n selectionMinOdds?: Odds;\n selectionMaxOdds?: Odds;\n}\n\nexport interface AccaBoostFilter extends TokenFilter {\n sportsConfigs: Record;\n minOddsPerLeg: AccaBoostTokenMinOdds;\n}\n\nexport interface AccaBoostSportConfig {\n gridGroups: StringDictionary;\n competitions: StringDictionary;\n}\n\nexport interface AccaBoostTokenMinOdds {\n european: number;\n britishNumerator: number;\n britishDenominator: number;\n american: number;\n}\n\nexport interface BestOddsGuaranteedToken extends IRewardToken {\n filter: BestOddsGuaranteedFilter;\n}\n\nexport interface BestOddsGuaranteedFilter extends TokenFilter {\n slipTypes: BetSlipType[];\n sportsFilter: NumberDictionary;\n}\n\nexport interface BestOddsGuaranteedSportsFilter {\n regions: number[];\n competitions: number[];\n meetings: number[];\n}\n\nexport enum AvailableTokensType {\n FreeBet = 'FreeBet',\n RiskFreeBet = 'RiskFreeBet',\n OddsBoost = 'OddsBoost',\n BetAndGet = 'BetAndGet',\n AccaBoost = 'AccaBoost',\n MultipleRewards = 'MultipleRewards',\n}\n\nexport enum CriteriaType {\n Sports = 'Sports',\n Competitions = 'Competitions',\n Meetings = 'Meetings',\n Fixtures = 'Fixtures',\n GameType = 'GameType',\n OptionId = 'OptionId',\n TemplateId = 'TemplateId',\n MarketParameter = 'MarketParameter',\n MarketId = 'MarketId',\n Forbidden = 'Forbidden',\n EachWaySelected = 'EachWaySelected',\n StartDate = 'StartDate',\n ExpiryDate = 'ExpiryDate',\n ParticipantBetSelected = 'ParticipantBetSelected',\n BetBuilderBetSelected = 'BetBuilderBetSelected',\n MinOdds = 'MinOdds',\n MaxOdds = 'MaxOdds',\n SelectionMaxOdds = 'SelectionMaxOdds',\n Market = 'Market',\n // eslint-disable-next-line @typescript-eslint/no-shadow\n SlipType = 'SlipType',\n MinimumLegs = 'MinimumLegs',\n MaximumStake = 'MaximumStake',\n MinimumStake = 'MinimumStake',\n DayOfWeek = 'DayOfWeek',\n TimeOfDay = 'TimeOfDay',\n BestOddsFlag = 'BestOddsFlag',\n MultiSingleBet = 'MultiSingleBet',\n SelectionOdds = 'SelectionOdds',\n TotalOdds = 'TotalOdds',\n SelectionsCount = 'SelectionsCount',\n Stake = 'Stake',\n Currency = 'Currency',\n BetBuilder = 'BetBuilder',\n SystemBet = 'SystemBet',\n TeaserReward = 'TeaserReward',\n EachWay = 'EachWay',\n ComboPrevention = 'ComboPrevention',\n}\n\nexport interface IOnboardingViewData {\n title: string;\n content: string;\n icon: string | undefined;\n isNew: boolean;\n}\n\nexport interface IRewardTokenSelection {\n tokenId: string;\n pickId?: PickId;\n betslipType?: BetslipType;\n trackingSource?: { [key: string]: string };\n}\n\nexport interface IRewardTokenDeSelection {\n rewardTokenType?: RewardTokenType;\n}\n\nexport interface IRewardTokenInfo {\n token: IRewardToken;\n selected: boolean;\n}\n\nexport interface MarketParameter {\n marketType: string;\n period: string;\n happening: string;\n}\n\nexport enum RewardBannerTrackingConstants {\n Load = 'load',\n Expand = 'expand',\n Collapse = 'collapse',\n Click = 'click',\n BetRewardsBanner = 'bet rewards banner',\n RewardsBanner = 'rewards banner',\n RewardDetails = 'reward details',\n PromoHub = 'promo hub',\n}\n\nexport interface BetAndGetEdsToken {\n payoutType: PayoutType;\n triggerType: RewardTriggerType;\n payoutDetails: PayoutDetails;\n maximumPayout?: number;\n rewardTargetType?: RewardTargetType;\n}\n\nexport interface PayoutDetails extends IncrementalPayoutDetails {\n value: number;\n percentage: number | undefined;\n maxValue?: number;\n}\n\nexport interface VIPExclusiveDetails {\n badge: string;\n badgeSize: string;\n badgeTitle: string;\n}\n\nexport interface PromoTokenPlaceBetSignPostingInfo extends IncrementalPayoutDetails {\n rewardTargetType?: RewardTargetType;\n}\n\nexport interface IncrementalPayoutDetails {\n noOfSlabs?: number;\n slabAmount?: number;\n}\n\n// TODO - nx library split - During consolidation of the tokens models we should check if we can take these values directly from the bpos contract\n// TODO: @Andriy explore ways of having this from bpos-v4 typings\nexport enum RewardTokenAdditionalInfoKeys {\n BoostPercentage = 'boostPercentage',\n MaxTokenStake = 'maxTokenStake',\n MaxWinningsBoost = 'maxWinningsBoost',\n UserExpectedWinningsBoost = 'userExpectedWinningsBoost',\n RiskFreeMaxCompensation = 'riskFreeMaxCompensation',\n RiskFreePercentage = 'riskFreePercentage',\n AccaBoostRatio = 'accaBoostRatio',\n AccaBoostFormulaType = 'formulaType',\n EdsCampaignPromotionType = 'edsCampaignPromotionType',\n BetIndex = 'betIndex',\n IsDigitalV2 = 'isDigitalV2',\n OddsBoostFormulaType = 'oddsBoostFormula',\n PlaceBetSignPostingData = 'placeBetSignPostingData',\n PlaceBetSignPostingVersion = 'placeBetSignPostingVersion',\n}\n\nexport interface EdsTokenWithEligiblePicks {\n token: EdsToken;\n eligiblePicksIds: string[];\n}\n\nexport interface BogTokenWithEligibility {\n token: BestOddsGuaranteedToken;\n eligibility: IRewardTokenEligibility | undefined;\n}\n\nexport interface RewardTokenContext {\n betslipType: BetslipType | undefined;\n pickId?: PickId;\n}\n\nexport interface RewardTokenSelectionContext {\n betslipType: BetslipType | undefined;\n pickId?: string;\n}\n\nexport interface SelectedRewardTokenState {\n tokenId: string;\n token: IRewardToken | null;\n tokenEligibility: IRewardTokenEligibility | null;\n}\n\nexport interface AcquisitionContext {\n acquisitionNotificationShown: boolean;\n autoApplyNotificationShown: boolean;\n AddWelcomeOfferNotificationShown: boolean;\n deselectedByUser: boolean;\n rewardAutoApplied: boolean;\n}\n\nexport enum AcquisitionContextType {\n BannerTooltip = 'BannerTooltip',\n AutoApplyTooltip = 'AutoApplyTooltip',\n AutoApply = 'AutoApply',\n DeselectedByUser = 'DeselectedByUser',\n AddWelcomeOffer = 'AddWelcomeOffer',\n}\n","import { EdsPromoTokenType } from '@bpos';\nimport { EdsPromotionToken } from '@bpos/v1/my-bets';\nimport { RewardTargetType } from '@bpos/v1/sports-promo/tokens';\nimport { FixturePricingState } from '@cds/betting-offer/add-ons';\nimport { Nullable, hasValue, isDefined } from '@frontend/sports/common/core/utils/extended-types';\nimport { CalculatedOdds, Odds, OddsOperations, emptyCalculatedOdds, emptyOdds } from '@frontend/sports/odds/feature';\nimport { Decimal } from 'decimal.js';\nimport { orderBy } from 'lodash-es';\nimport { BestOddsFixtureLikeInput } from 'packages/sports/web/app/src/best-odds-guarantee/models/models';\nimport { BuildABet, IRewardTokenData, PayoutType, RewardTokenType } from 'packages/sports/web/app/src/tokens-base/token-base.models';\n\nimport { BetslipType } from '../../../core/betslip-type';\nimport { BetslipPick } from '../../../core/picks/betslip-pick';\nimport { BetBuilderPickId, GroupPickId } from '../../../core/picks/pick-id';\nimport { PickOddsState, PriceType } from '../../../core/picks/pick-models';\nimport {\n BetslipV2HorseRaceOptionMarketPick,\n BetslipV2HorseRaceWinParticipantPick,\n BetslipV2HorseRaceXCastPick,\n} from '../../../core/picks/sport-specific/betslip-v2-horse-race-picks';\nimport { filterPicksForType } from '../../picks/services/linear-betslip-pick.utils';\nimport { ISlipSummary } from '../../summary/models';\nimport { IBetslipTypeState } from '../../types/state';\nimport { isErrorComboPrevention } from '../../validation/services/utils/betslip-errors-utils';\nimport { BetslipTypePickErrors } from '../../validation/state';\nimport {\n AccaBoostLadderItem,\n AccaBoostToken,\n AvailableTokensType,\n BetAndGetToken,\n CriteriaType,\n FreeBetToken,\n IRewardToken,\n OddsBoostToken,\n RewardTokenContext,\n RiskFreeToken,\n TokenOddsData,\n} from '../reward-tokens.model';\nimport { IRewardTokenEligibility, IRewardTokenEligibilityState, TokensRecord } from '../state';\n\nexport function isFreebetToken(token?: IRewardToken | IRewardTokenData | null): token is FreeBetToken {\n return !!token && token.rewardTokenType === RewardTokenType.FreeBet;\n}\n\nexport function isOddsBoostToken(token: IRewardToken | IRewardTokenData | null): token is OddsBoostToken {\n return !!token && token.rewardTokenType === RewardTokenType.OddsBoost;\n}\n\nexport function isRiskFreeToken(token: IRewardToken | IRewardTokenData | null): token is RiskFreeToken {\n return !!token && token.rewardTokenType === RewardTokenType.RiskFreeBet;\n}\n\nexport function isBetAndGetToken(token: IRewardToken | IRewardTokenData | null): token is BetAndGetToken {\n return !!token && token.rewardTokenType === RewardTokenType.BetAndGet;\n}\n\nexport function isAccaBoostToken(token?: Nullable): token is AccaBoostToken {\n return !!token && token.rewardTokenType === RewardTokenType.AccaBoost;\n}\n\nexport function getAcquisitionRewardToken(tokens: IRewardToken[]): IRewardToken | undefined {\n return tokens.find((token) => isAcquisitionRewardToken(token));\n}\n\nconst isAcquisitionRewardToken = (token: IRewardToken): boolean => token.rewardTargetType === RewardTargetType.WelcomeOffer;\n\nexport function getPayoutExpiryInDays(token: IRewardToken): string {\n if (!token.payoutExpiry) {\n return '1';\n }\n\n const expiry = token.payoutExpiry / 24;\n\n return (expiry <= 1 ? 1 : Math.round(expiry)).toString();\n}\n\nexport function getTokensType(tokens: IRewardToken[]): AvailableTokensType {\n if (tokens.every((token) => isFreebetToken(token))) {\n return AvailableTokensType.FreeBet;\n }\n if (tokens.every((token) => isOddsBoostToken(token))) {\n return AvailableTokensType.OddsBoost;\n }\n if (tokens.every((token) => isRiskFreeToken(token))) {\n return AvailableTokensType.RiskFreeBet;\n }\n if (tokens.every((token) => isAccaBoostToken(token))) {\n return AvailableTokensType.AccaBoost;\n }\n\n if (tokens.every((token) => isBetAndGetToken(token))) {\n return AvailableTokensType.BetAndGet;\n }\n\n return AvailableTokensType.MultipleRewards;\n}\n\nexport function getNonAccaTokens(tokens: IRewardToken[]): IRewardToken[] {\n return tokens.filter((token) => !isAccaBoostToken(token));\n}\n\nexport function getSortedNonAccaTokensWithExpiryDate(tokens: IRewardToken[], isVIPBadgeEnabled: boolean): IRewardToken[] {\n return orderBy(getNonAccaTokens(tokens), [\n (token) => token.rewardTargetType !== RewardTargetType.WelcomeOffer,\n (token) => (isVIPBadgeEnabled ? !token.isVipExclusive && token.expiryDate : false),\n (token) => token.expiryDate,\n ]);\n}\n\nexport function getSortedNonAccaTokens(tokens: IRewardToken[]): IRewardToken[] {\n return orderBy(getNonAccaTokens(tokens), (token) => isAcquisitionRewardToken(token), 'desc');\n}\n\nexport function findAccaBoostLadderItem(token: AccaBoostToken, count: number): AccaBoostLadderItem {\n return token.boostLadder.reduce((previous, current) => {\n return current.numberOfLegs <= count ? current : previous;\n });\n}\n\nexport function getAccaBoostRatio(token: AccaBoostToken, count: number): Decimal {\n return new Decimal(findAccaBoostLadderItem(token, count).ratio);\n}\n\nexport function findMinimumLegsAccaTokens(eligibilityState: IRewardTokenEligibilityState, tokens: TokensRecord): AccaBoostToken[] {\n const accaTokens = Object.values(tokens).filter(isAccaBoostToken);\n\n return accaTokens.sort(byAccaPriority).filter((token) => {\n const eligibility = eligibilityState.comboBet.find((item) => item.tokenId === token.userTokenId);\n\n return !!eligibility && eligibility.invalidHardCriteria === CriteriaType.MinimumLegs;\n });\n}\n\nexport function findMinimumOddsAccaTokens(eligibilityState: IRewardTokenEligibilityState, tokens: TokensRecord): AccaBoostToken | undefined {\n const accaTokens = Object.values(tokens).filter(isAccaBoostToken);\n\n return accaTokens.sort(byAccaPriority).find((token) => {\n const eligibility = eligibilityState.comboBet.find((item) => item.tokenId === token.userTokenId);\n\n return !!eligibility && eligibility.invalidHardCriteria === CriteriaType.MinOdds;\n });\n}\n\nexport const byAccaPriority = (first: AccaBoostToken, second: AccaBoostToken): number => first.priority - second.priority;\n\nexport function isValidAccaBoostCombo(\n typePickErrors: BetslipTypePickErrors,\n pickList: BetslipPick[],\n types: IBetslipTypeState,\n isLinear: boolean,\n): boolean {\n const type = types.base.currentSelectedType;\n\n const isValidTab = (type === BetslipType.Combo && !types.comboBet.isEachWay) || type === BetslipType.Single;\n\n if (!type || !isValidTab) {\n return false;\n }\n\n const selectedPicks = filterPicksForType(types, type, pickList, {\n isLocked: isLinear ? false : undefined,\n isSelected: true,\n });\n\n if (selectedPicks.length < 2) {\n return false;\n }\n\n const allValidPicks = selectedPicks.every(({ oddsState }) => oddsState === PickOddsState.Open);\n\n if (!allValidPicks) {\n return false;\n }\n\n const pickErrors = typePickErrors[type];\n\n return !Object.values(pickErrors)\n .flatMap((errors) => errors)\n .some((e) => isErrorComboPrevention(e));\n}\n\nexport function calcTotalOdds(picks: BetslipPick[], roundPriceValues: boolean = false): CalculatedOdds {\n const totalOdds = picks.reduce(\n (odds: CalculatedOdds, pick: BetslipPick) => {\n if (\n (OddsOperations.isOddsEmpty(odds),\n !pick.currentPrice || pick.priceType !== PriceType.Fixed || OddsOperations.isOddsEmpty(pick.currentPrice.nativeOdds))\n ) {\n odds = emptyCalculatedOdds;\n } else {\n odds = OddsOperations.multiply([odds, OddsOperations.toCalculatedOdds(pick.currentPrice!.nativeOdds)], roundPriceValues);\n }\n\n return odds;\n },\n OddsOperations.fromDecimalValue(new Decimal(1)),\n );\n\n return totalOdds;\n}\n\nexport function calcTotalOddsForOddsBoost(picks: BetslipPick[], roundPriceValues: boolean = false): CalculatedOdds {\n const oddsArray = picks.filter((p) => p.currentPrice?.nativeOdds).map((p) => OddsOperations.toCalculatedOdds(p.currentPrice!.nativeOdds));\n\n return OddsOperations.multiply(oddsArray, roundPriceValues);\n}\n\nexport function calculateWinningsBoost(slipSummary: ISlipSummary): Decimal {\n const boostedWinnings = slipSummary.rewardTokenModifications?.oddsBoost?.boostedWinnings;\n const possibleWinnings = slipSummary.possibleWinnings;\n\n return boostedWinnings && possibleWinnings ? boostedWinnings.minus(possibleWinnings) : new Decimal(0);\n}\n\nexport function convertToBestOddsFixtureLikeInput(\n pick: BetslipV2HorseRaceOptionMarketPick | BetslipV2HorseRaceWinParticipantPick | BetslipV2HorseRaceXCastPick,\n): BestOddsFixtureLikeInput {\n const horseFixture = pick.fixture;\n const fixture: BestOddsFixtureLikeInput = {\n startDate: pick.fixture.eventDate.toISOString(),\n fixtureType: pick.fixture.fixtureType,\n sport: { id: horseFixture.sportId },\n meeting: hasValue(horseFixture.league) ? { id: horseFixture.league.id } : undefined,\n region: hasValue(horseFixture.region) ? { id: horseFixture.region.id } : undefined,\n competition: hasValue(horseFixture.league) ? { id: horseFixture.league.id } : undefined,\n addons: {\n pricingState: FixturePricingState.None,\n isRaceOff: horseFixture.isRaceOff,\n bestOddsGuarantee:\n horseFixture.bestOddsGuarantee && pick.currentPrice?.type !== PriceType.StartingPrice && !BetslipV2HorseRaceXCastPick.isPick(pick),\n },\n };\n\n return fixture;\n}\n\nexport function getAllEligibleTokens(eligibilityState: IRewardTokenEligibilityState, tokens: TokensRecord): IRewardToken[] {\n const ids: string[] = [];\n\n Object.values(eligibilityState.singleBet)\n .flatMap((e) => e)\n .forEach((eligibility) => {\n if (!eligibility.isEligible || ids.includes(eligibility.tokenId)) {\n return;\n }\n\n ids.push(eligibility.tokenId);\n });\n\n Object.values(eligibilityState.betBuilder)\n .flatMap((e) => e)\n .forEach((eligibility) => {\n if (!eligibility.isEligible || ids.includes(eligibility.tokenId)) {\n return;\n }\n\n ids.push(eligibility.tokenId);\n });\n\n eligibilityState.comboBet.forEach((eligibility) => {\n if (!eligibility.isEligible || ids.includes(eligibility.tokenId)) {\n return;\n }\n\n ids.push(eligibility.tokenId);\n });\n\n eligibilityState.teaserBet.forEach((eligibility) => {\n if (!eligibility.isEligible || ids.includes(eligibility.tokenId)) {\n return;\n }\n\n ids.push(eligibility.tokenId);\n });\n\n return ids.map((id) => tokens[id]).filter(isDefined);\n}\n\nexport function isBetBuilderExclusiveToken(token: IRewardToken | BetAndGetToken) {\n return (\n (token.isBetBuilder && !token.filter.betSlipType && !token.isSingleAndComboSelected) ||\n ('buildABet' in token && token.buildABet === BuildABet.Yes)\n );\n}\nexport function isBetBuilderToken(token: IRewardToken | BetAndGetToken) {\n return token.isBetBuilder || ('buildABet' in token && token?.buildABet !== BuildABet.No);\n}\n\n// This is only used for quick-bet-builder-drawer, therefore we do not need\nexport function getTokenContextForBetBuilderPickId(\n pickId: BetBuilderPickId | GroupPickId,\n isSportcastAsComboEnabled: boolean,\n isLinear: boolean,\n): RewardTokenContext {\n if (isLinear) {\n return {\n betslipType: BetslipType.BetBuilder,\n pickId,\n };\n }\n\n const isComboBetContext = GroupPickId.isId(pickId) || isSportcastAsComboEnabled;\n\n return {\n betslipType: isComboBetContext ? BetslipType.Combo : BetslipType.Single,\n pickId: isComboBetContext ? undefined : pickId,\n };\n}\n\nexport function mapOdds(oddsPerLeg: TokenOddsData | undefined): Odds {\n if (!oddsPerLeg) {\n return emptyOdds;\n }\n\n const { european, britishNumerator, britishDenominator, american } = oddsPerLeg;\n\n return OddsOperations.createOdds({\n odds: european,\n numerator: britishNumerator,\n denominator: britishDenominator,\n americanOdds: american,\n });\n}\n\nexport function parseMarketType(token: IRewardToken): string[] {\n const marketTypes = token.customMarketTypes\n ? (token.customMarketTypes.split(',') ?? [])\n : token.filter.marketParameters.map((mt) => mt.marketType);\n\n return marketTypes;\n}\n\nexport function isIncrementalPayoutEligible(token: IRewardToken, stake?: number): boolean {\n return (\n token.payoutType === PayoutType.FreeBet &&\n !!token.isIncrementalPayout &&\n !!token.incrementalBetAmount &&\n !!token.incrementalPayoutNoOfTimes &&\n !!stake &&\n stake >= token.incrementalBetAmount &&\n token.incrementalPayoutNoOfTimes > 1\n );\n}\n\nexport function isEdsPromoTokenBetAndGet(edsPromoTokens: EdsPromotionToken[] | undefined): boolean {\n return edsPromoTokens?.length === 1 && edsPromoTokens[0].tokenType === EdsPromoTokenType.BetAndGet;\n}\n\nexport function isAcquisitionEligible(token: IRewardTokenEligibility, acquisitionUserTokenId: string): boolean {\n return token.isEligible && !!Object.values(token.softCriteriasValidity).every(Boolean) && acquisitionUserTokenId === token.tokenId;\n}\n","export const SYSTEMS = [\n { id: 'systembankersingles', picks: -1, bets: -1, group: 1, basis: [1] },\n { id: 'system2of3', picks: 3, bets: 3, group: 2, basis: [2] },\n { id: 'patent', picks: 3, bets: 7, group: 9, basis: [1, 2, 3] },\n { id: 'trixie', picks: 3, bets: 4, group: 8, basis: [2, 3] },\n { id: 'system3of4', picks: 4, bets: 4, group: 3, basis: [3] },\n { id: 'system2of4', picks: 4, bets: 6, group: 2, basis: [2] },\n { id: 'lucky15', picks: 4, bets: 15, group: 9, basis: [1, 2, 3, 4] },\n { id: 'yankee', picks: 4, bets: 11, group: 8, basis: [2, 3, 4] },\n { id: 'system4of5', picks: 5, bets: 5, group: 4, basis: [4] },\n { id: 'system3of5', picks: 5, bets: 10, group: 3, basis: [3] },\n { id: 'system2of5', picks: 5, bets: 10, group: 2, basis: [2] },\n { id: 'lucky31', picks: 5, bets: 31, group: 9, basis: [1, 2, 3, 4, 5] },\n { id: 'canadian', picks: 5, bets: 26, group: 8, basis: [2, 3, 4, 5] },\n { id: 'system5of6', picks: 6, bets: 6, group: 5, basis: [5] },\n { id: 'system4of6', picks: 6, bets: 15, group: 4, basis: [4] },\n { id: 'system3of6', picks: 6, bets: 20, group: 3, basis: [3] },\n { id: 'system2of6', picks: 6, bets: 15, group: 2, basis: [2] },\n { id: 'lucky63', picks: 6, bets: 63, group: 9, basis: [1, 2, 3, 4, 5, 6] },\n { id: 'heinz', picks: 6, bets: 57, group: 8, basis: [2, 3, 4, 5, 6] },\n { id: 'system6of7', picks: 7, bets: 7, group: 6, basis: [6] },\n { id: 'system5of7', picks: 7, bets: 21, group: 5, basis: [5] },\n { id: 'system4of7', picks: 7, bets: 35, group: 4, basis: [4] },\n { id: 'system3of7', picks: 7, bets: 35, group: 3, basis: [3] },\n { id: 'system2of7', picks: 7, bets: 21, group: 2, basis: [2] },\n { id: 'superheinz', picks: 7, bets: 120, group: 8, basis: [2, 3, 4, 5, 6, 7] },\n { id: 'system7of8', picks: 8, bets: 8, group: 7, basis: [7] },\n { id: 'system6of8', picks: 8, bets: 28, group: 6, basis: [6] },\n { id: 'system5of8', picks: 8, bets: 56, group: 5, basis: [5] },\n { id: 'system4of8', picks: 8, bets: 70, group: 4, basis: [4] },\n { id: 'system3of8', picks: 8, bets: 56, group: 3, basis: [3] },\n { id: 'system2of8', picks: 8, bets: 28, group: 2, basis: [2] },\n { id: 'goliath', picks: 8, bets: 247, group: 8, basis: [2, 3, 4, 5, 6, 7, 8] },\n];\n","import { BetslipType } from '../../../../core/betslip-type';\nimport { IBetBuilderBetState } from '../../../bet-builder/models';\nimport { IComboBetState } from '../../../combo-bet/state';\nimport { isFreebetToken } from '../../../reward-tokens/services/reward-tokens.utils';\nimport { IRewardTokensState } from '../../../reward-tokens/state';\nimport { ISingleBetState } from '../../../single-bet/state';\nimport { SystemKey } from '../../../system-bet/models';\nimport { SYSTEMS } from '../../../system-bet/services/system-bet.constants';\nimport { ISystemBetState } from '../../../system-bet/state';\nimport { ITeaserBetState } from '../../../teaser-bet/state';\nimport { IBetslipTypeState } from '../../../types/state';\n\nexport interface TotalStake {\n stake: number | null;\n freeBetAmount: number | null;\n}\n\nfunction getStakeWithEachWay({ isEachWay, stake }: { isEachWay: boolean; stake: number | null }): number | null {\n if (!stake) {\n return null;\n }\n\n return isEachWay ? stake * 2 : stake;\n}\n\nexport function isFreeBetStake(stakes: TotalStake[]): boolean {\n const valueStakes = stakes.filter((stake) => !!stake.stake || !!stake.freeBetAmount);\n\n return valueStakes.length === 1 && !!valueStakes[0]?.freeBetAmount;\n}\n\nexport function getTotalStakesForCurrentType(typeState: IBetslipTypeState, tokenState: IRewardTokensState): TotalStake[] {\n const { currentSelectedType } = typeState.base;\n switch (currentSelectedType) {\n case BetslipType.Single:\n return getSingleBetStakes(typeState.singleBet, tokenState);\n case BetslipType.Combo:\n return getComboStake(typeState.comboBet, tokenState);\n case BetslipType.System:\n return getSystemStake(typeState.systemBet);\n default:\n return [];\n }\n}\n\nexport function getSingleBetStakes(singleState: ISingleBetState, tokensState: IRewardTokensState, filterLockedPicks: boolean = false): TotalStake[] {\n return Object.values(singleState.picks)\n .filter((p) => p.isSelected && (!p.isLocked || !filterLockedPicks))\n .map((p) => ({\n stake: getStakeWithEachWay(p),\n freeBetAmount: getFreeBetAmount(p.rewardTokenId, tokensState),\n }));\n}\n\nexport function getComboStake(comboState: IComboBetState, tokensState: IRewardTokensState): TotalStake[] {\n return [\n {\n stake: getStakeWithEachWay(comboState),\n freeBetAmount: getFreeBetAmount(comboState.rewardTokenId, tokensState),\n },\n ];\n}\n\nexport function getTeaserStake(teaserState: ITeaserBetState, tokensState: IRewardTokensState): TotalStake[] {\n return [\n {\n stake: teaserState.stake,\n freeBetAmount: getFreeBetAmount(teaserState.rewardTokenId, tokensState),\n },\n ];\n}\n\nexport function getBetBuilderStakes(betBuilderState: IBetBuilderBetState, tokensState: IRewardTokensState): TotalStake[] {\n return Object.values(betBuilderState.picks)\n .filter((p) => p.isSelected)\n .map((p) => ({\n stake: p.stake,\n freeBetAmount: getFreeBetAmount(p.rewardTokenId, tokensState),\n }));\n}\n\nexport function getFreeBetAmount(rewardTokenId: string | null, tokenState: IRewardTokensState) {\n const token = rewardTokenId ? tokenState.tokens[rewardTokenId] : null;\n const freeBetAmount = isFreebetToken(token) ? token.amount : null;\n\n return freeBetAmount;\n}\n\n/**\n * Sums multiple total stakes into a single total stake.\n * If both freebet and regular stake are set for a total stake, only the freebet value will be summed up.\n * This is done to ensure that adding freeBetAmount and stake leads to the correct total stake value that the bets would be placed with.\n * @param stakes The stakes to sum up.\n * @returns The total stake that the current bets will be placed with.\n */\nexport function combineTotalStakes(stakes: TotalStake[]): TotalStake {\n return stakes.reduce(\n (acc, curr) => {\n const freeBetAmount = curr.freeBetAmount === null ? acc.freeBetAmount : (acc.freeBetAmount ?? 0) + curr.freeBetAmount;\n const stake = curr.stake === null || curr.freeBetAmount ? acc.stake : (acc.stake ?? 0) + curr.stake;\n\n return {\n freeBetAmount,\n stake,\n };\n },\n {\n freeBetAmount: null,\n stake: null,\n },\n );\n}\n\nfunction getSystemStake(systemState: ISystemBetState): TotalStake[] {\n const typeKey = systemState.systemInfo?.key;\n\n if (!typeKey) {\n return [];\n }\n\n const stake = systemState.stake;\n\n const betCount =\n typeKey === SystemKey.BankerSingles\n ? Object.values(systemState.picks).filter((p) => p.isSelected && !p.isBanker).length\n : (SYSTEMS.find((s) => s.id === typeKey.toLowerCase())?.bets ?? null);\n\n return [\n {\n stake: betCount && stake ? stake * betCount : null,\n freeBetAmount: null,\n },\n ];\n}\n","import { BetslipType } from '../../../../core/betslip-type';\nimport { IBetBuilderBetState, IBetBuilderPickState } from '../../../bet-builder/models';\nimport { IComboBetState } from '../../../combo-bet/state';\nimport { isFreebetToken } from '../../../reward-tokens/services/reward-tokens.utils';\nimport { IRewardTokensState, TokensRecord } from '../../../reward-tokens/state';\nimport { ISingleBetPickState, ISingleBetState } from '../../../single-bet/state';\nimport { ISystemBetState } from '../../../system-bet/state';\nimport { ITeaserBetState } from '../../../teaser-bet/state';\nimport { IBetslipTypeState } from '../../../types/state';\nimport { TotalStake, getBetBuilderStakes, getComboStake, getSingleBetStakes, getTeaserStake } from './stake-utils';\n\nexport function getStakedTypes(typeState: IBetslipTypeState, tokens: TokensRecord): BetslipType[] {\n const linearTypes = typeState.base.linearTypes;\n\n const types: BetslipType[] = [];\n\n if (linearTypes[BetslipType.Single] && hasSingleBetStake(typeState.singleBet, tokens)) {\n types.push(BetslipType.Single);\n }\n\n if (linearTypes[BetslipType.BetBuilder] && hasBetBuilderBetStake(typeState.betBuilder, tokens)) {\n types.push(BetslipType.BetBuilder);\n }\n\n if (linearTypes[BetslipType.Combo] && hasComboBetStake(typeState.comboBet, tokens)) {\n types.push(BetslipType.Combo);\n }\n\n if (linearTypes[BetslipType.System] && hasSystemBetStake(typeState.systemBet)) {\n types.push(BetslipType.System);\n }\n\n if (linearTypes[BetslipType.Teaser] && hasTeaserBetStake(typeState.teaserBet, tokens)) {\n types.push(BetslipType.Teaser);\n }\n\n return types;\n}\n\nexport function hasSingleBetStake(singleState: ISingleBetState, tokens: TokensRecord): boolean {\n return Object.values(singleState.picks).some((a) => hasSingleBetPickStake(a, tokens));\n}\n\nexport function hasSingleBetPickStake(singleBetPickState: ISingleBetPickState, tokens: TokensRecord): boolean {\n return singleBetPickState.stake !== null || hasFreeBetStake(singleBetPickState.rewardTokenId, tokens);\n}\n\nexport function hasComboBetStake(comboState: IComboBetState, tokens: TokensRecord): boolean {\n return comboState.stake !== null || hasFreeBetStake(comboState.rewardTokenId, tokens);\n}\n\nexport function hasBetBuilderBetStake(betBuilderState: IBetBuilderBetState, tokens: TokensRecord): boolean {\n return Object.values(betBuilderState.picks).some((a) => hasBetBuilderBetPickStake(a, tokens));\n}\n\nexport function hasBetBuilderBetPickStake(betBuilderBetPickState: IBetBuilderPickState, tokens: TokensRecord): boolean {\n return betBuilderBetPickState.stake !== null || hasFreeBetStake(betBuilderBetPickState.rewardTokenId, tokens);\n}\n\nexport function hasTeaserBetStake(teaserState: ITeaserBetState, tokens: TokensRecord): boolean {\n return teaserState.stake != null || hasFreeBetStake(teaserState.rewardTokenId, tokens);\n}\n\nfunction hasFreeBetStake(tokenId: string | null, tokens: TokensRecord): boolean {\n return !!tokenId && isFreebetToken(tokens[tokenId]);\n}\n\nexport function hasSystemBetStake(systemState: ISystemBetState): boolean {\n return Object.values(systemState.linearTypeStakes).some((stake) => stake.stake !== null);\n}\n\nexport function getTotalStakesForAllTypes(typeState: IBetslipTypeState, tokensState: IRewardTokensState): TotalStake[] {\n const linearTypes = typeState.base.linearTypes;\n\n const singleBetStakes = linearTypes[BetslipType.Single] ? getSingleBetStakes(typeState.singleBet, tokensState, true) : [];\n const comboBetStakes = linearTypes[BetslipType.Combo] ? getComboStake(typeState.comboBet, tokensState) : [];\n const systemBetStakes = linearTypes[BetslipType.System] ? getSystemStakesLinear(typeState.systemBet) : [];\n const teaserBetStakes = linearTypes[BetslipType.Teaser] ? getTeaserStake(typeState.teaserBet, tokensState) : [];\n const betBuilderStakes = linearTypes[BetslipType.BetBuilder] ? getBetBuilderStakes(typeState.betBuilder, tokensState) : [];\n\n return [...singleBetStakes, ...comboBetStakes, ...systemBetStakes, ...teaserBetStakes, ...betBuilderStakes];\n}\n\nfunction getSystemStakesLinear(systemState: ISystemBetState) {\n return Object.values(systemState.linearTypeStakes).map((type) => {\n const stake = type.stake && type.stake * type.betCount;\n\n return {\n stake: type.isEachWay && stake ? stake * 2 : stake,\n freeBetAmount: null,\n };\n });\n}\n","import { isDefined } from '@frontend/sports/common/core/utils/extended-types';\nimport { isNil } from 'lodash-es';\n\nimport { BetslipType } from '../../../core/betslip-type';\nimport { BetslipPick } from '../../../core/picks/betslip-pick';\nimport { BetslipUnknownPick } from '../../../core/picks/betslip-unknown-pick';\nimport { PickId } from '../../../core/picks/pick-id';\nimport { PickOddsState } from '../../../core/picks/pick-models';\nimport { flattenPicks } from '../../../core/utils';\nimport {\n hasBetBuilderBetPickStake,\n hasComboBetStake,\n hasSingleBetPickStake,\n hasSystemBetStake,\n hasTeaserBetStake,\n} from '../../betplacement/services/stake/linear-stake-utils';\nimport { TokensRecord } from '../../reward-tokens/state';\nimport { IBetslipTypeState } from '../../types/state';\n\ninterface IPickTypeState {\n isLocked: boolean;\n isSelected: boolean;\n}\n\nexport function getSelectedPlaceablePicks(types: IBetslipTypeState, picksList: BetslipPick[], tokens: TokensRecord): BetslipPick[] {\n let picks: BetslipPick[] = [];\n\n const singleState = types.singleBet;\n const comboState = types.comboBet;\n const systemState = types.systemBet;\n const teaserState = types.teaserBet;\n const betBuilderState = types.betBuilder;\n\n const singlePicks = flattenPicks(picksList).filter((p) => {\n const typePick = singleState.picks[p.id.toString()];\n\n return typePick && hasSingleBetPickStake(typePick, tokens) && typePick.isSelected;\n });\n\n picks = [...singlePicks];\n\n const betBuilderPicks = picksList.filter((p) => {\n const typePick = betBuilderState.picks[p.id.toString()];\n\n return typePick && hasBetBuilderBetPickStake(typePick, tokens) && typePick.isSelected;\n });\n\n picks = [...picks, ...betBuilderPicks];\n\n if (hasComboBetStake(types.comboBet, tokens)) {\n const multiPicks = picksList.filter((p) => {\n const typePick = comboState.picks[p.id.toString()];\n\n return typePick.isSelected && picks.every((s) => !s.id.isEqual(p.id));\n });\n\n picks = [...picks, ...multiPicks];\n }\n\n if (hasSystemBetStake(types.systemBet)) {\n const systemPicks = flattenPicks(picksList).filter((p) => {\n const typePick = systemState.picks[p.id.toString()];\n\n return typePick && typePick.isSelected && picks.every((s) => !s.id.isEqual(p.id));\n });\n\n picks = [...picks, ...systemPicks];\n }\n\n if (hasTeaserBetStake(types.teaserBet, tokens)) {\n const teaserPicks = flattenPicks(picksList).filter((p) => {\n const typePick = teaserState.picks[p.id.toString()];\n\n return typePick?.isSelected && picks.every((s) => !s.id.isEqual(p.id));\n });\n\n picks = [...picks, ...teaserPicks];\n }\n\n return flattenPicks(picks);\n}\n\nexport function getSelectedLockedPicksForAllTypes(types: IBetslipTypeState, picksList: BetslipPick[], isLocked: boolean): BetslipPick[] {\n const flattenedPicksMap = flattenPicks(picksList).reduce<{ [pickId: string]: BetslipPick }>((acc, curr) => {\n acc[curr.id.toString()] = curr;\n\n return acc;\n }, {});\n\n const lockedPicks: { [pickId: string]: BetslipPick } = {};\n const updateLockedPicksForTypeState = (typeState: { [pickId: string]: IPickTypeState }): void => {\n Object.entries(typeState)\n .filter(([, typePick]) => typePick.isSelected && typePick.isLocked === isLocked)\n .forEach(([pickId]) => {\n lockedPicks[pickId] = flattenedPicksMap[pickId];\n });\n };\n\n updateLockedPicksForTypeState(types.singleBet.picks);\n updateLockedPicksForTypeState(types.comboBet.picks);\n updateLockedPicksForTypeState(types.systemBet.picks);\n updateLockedPicksForTypeState(types.betBuilder.picks);\n updateLockedPicksForTypeState(types.teaserBet.picks);\n\n return Object.values(lockedPicks).filter(isDefined);\n}\n\nexport function hasSelectedLockedPick(types: IBetslipTypeState, picksList: BetslipPick[], isLocked: boolean): boolean {\n return picksList.some((pick) => {\n const single = types.singleBet.picks[pick.id.toString()];\n const combo = types.comboBet.picks[pick.id.toString()];\n const system = types.systemBet.picks[pick.id.toString()];\n const teaser = types.teaserBet.picks[pick.id.toString()];\n\n if (!single || !combo || !system) {\n return false;\n }\n\n return (\n (single.isSelected && single.isLocked === isLocked) ||\n (combo.isSelected && combo.isLocked === isLocked) ||\n (system.isSelected && system.isLocked === isLocked) ||\n (teaser?.isSelected && teaser?.isLocked === isLocked)\n );\n });\n}\n\nexport function hasUnacceptedPick(types: IBetslipTypeState, picksList: BetslipPick[]): boolean {\n return picksList.some((pick) => {\n if (BetslipUnknownPick.isPick(pick)) {\n return false;\n }\n\n const single = types.singleBet.picks[pick.id.toString()];\n const combo = types.comboBet.picks[pick.id.toString()];\n const system = types.systemBet.picks[pick.id.toString()];\n\n if (!single || !combo || !system) {\n return false;\n }\n\n switch (pick.oddsState) {\n case PickOddsState.Closed:\n return true;\n\n case PickOddsState.Open:\n return single.isLocked || combo.isLocked || system.isLocked;\n\n case PickOddsState.Locked:\n return !single.isLocked || !combo.isLocked || !system.isLocked;\n }\n });\n}\n\nexport function isPickSelectedLocked(pickId: string, types: IBetslipTypeState, picksList: BetslipPick[], isLocked: boolean): boolean {\n const pick = picksList.find((p) => p.id.toString() === pickId);\n\n if (!pick) {\n return false;\n }\n\n const single = types.singleBet.picks[pickId];\n const combo = types.comboBet.picks[pickId];\n const system = types.systemBet.picks[pickId];\n const teaser = types.teaserBet.picks[pickId];\n\n return (\n (single.isSelected && single.isLocked === isLocked) ||\n (combo.isSelected && combo.isLocked === isLocked) ||\n (system.isSelected && system.isLocked === isLocked) ||\n (teaser?.isSelected && teaser?.isLocked === isLocked)\n );\n}\n\nexport function filterPicksForType(\n types: IBetslipTypeState,\n betslipType: BetslipType,\n picksList: BetslipPick[],\n filter: { isLocked?: boolean; isSelected?: boolean },\n): BetslipPick[] {\n let picks: { [id: string]: IPickTypeState } = {};\n switch (betslipType) {\n case BetslipType.Combo:\n picks = types.comboBet.picks;\n break;\n case BetslipType.System:\n picks = types.systemBet.picks;\n break;\n case BetslipType.Teaser:\n picks = types.teaserBet.picks;\n break;\n case BetslipType.BetBuilder:\n picks = types.betBuilder.picks;\n break;\n default:\n picks = types.singleBet.picks;\n break;\n }\n\n return filterTypePicks(picks, picksList, filter);\n}\n\nexport function filterTypePicks(\n typePicks: { [id: string]: IPickTypeState },\n picksList: BetslipPick[],\n filter: { isLocked?: boolean; isSelected?: boolean },\n): BetslipPick[] {\n return picksList.filter((pick) => {\n const pickState = typePicks[pick.id.toString()];\n if (!pickState) {\n return false;\n }\n\n const lockedFilterMatched = isNil(filter.isLocked) ? true : pickState.isLocked === filter.isLocked;\n const selectedFilterMatched = isNil(filter.isSelected) ? true : pickState.isSelected === filter.isSelected;\n\n return lockedFilterMatched && selectedFilterMatched;\n });\n}\n\nexport function isPickLocked(pickId: PickId, types: IBetslipTypeState): boolean {\n return (\n types.singleBet.picks[pickId.toString()]?.isLocked ||\n types.comboBet.picks[pickId.toString()]?.isLocked ||\n types.systemBet.picks[pickId.toString()]?.isLocked ||\n types.teaserBet.picks[pickId.toString()]?.isLocked\n );\n}\n","export enum ClientErrorType {\n NotLoggedInError = 'NotLoggedInError',\n UndoCashOutFailedError = 'UndoCashOutFailedError',\n RewardTokensMaxStakeError = 'RewardTokensMaxStakeError',\n RewardTokensMinLegsError = 'RewardTokensMinLegsError',\n RewardTokensTradingEntityMismatchError = 'RewardTokensTradingEntityMismatchError',\n RewardTokensForbiddenError = 'RewardTokensForbiddenError',\n RewardTokensGameTypeError = 'RewardTokensGameTypeError',\n RewardTokensSlipTypeError = 'RewardTokensSlipTypeError',\n RewardTokensMinStakeError = 'RewardTokensMinStakeError',\n RewardTokensTotalOddsError = 'RewardTokensTotalOddsError',\n RewardTokensSelectionLevelOddsError = 'RewardTokensSelectionLevelOddsError',\n RewardTokensBetBuilderError = 'RewardTokensBetBuilderError',\n BetBuilderMaxSelectionsPerPickError = 'BetBuilderMaxSelectionsPerPickError',\n RewardTokensMaxTotalOddsError = 'RewardTokensMaxTotalOddsError',\n RewardTokensSelectionMaxOddsError = 'RewardTokensSelectionMaxOddsError',\n}\n","import { IRewardToken } from '../../../../reward-tokens/reward-tokens.model';\nimport { BetPlacementErrorIcon } from '../../bet-placement-error-icon';\nimport { BetslipError } from '../../betslip-error';\nimport { ClientErrorType } from '../../client-error-type';\n\nexport class RewardTokensBetBuilderError extends BetslipError {\n constructor(token: IRewardToken) {\n super();\n this.icon = BetPlacementErrorIcon.Error;\n this.type = ClientErrorType.RewardTokensBetBuilderError;\n this.hasClientValidation = true;\n this.token = token;\n }\n\n readonly token: IRewardToken;\n}\n","import { BetPlacementErrorIcon } from '../../bet-placement-error-icon';\nimport { BetslipError } from '../../betslip-error';\nimport { ClientErrorType } from '../../client-error-type';\n\nexport class RewardTokensMinStakeError extends BetslipError {\n constructor(minimumStake: number) {\n super();\n this.icon = BetPlacementErrorIcon.Error;\n this.type = ClientErrorType.RewardTokensMinStakeError;\n this.hasClientValidation = true;\n this.minimumStake = minimumStake;\n }\n\n readonly minimumStake: number;\n}\n","import { BetPlacementErrorIcon } from '../../bet-placement-error-icon';\nimport { BetslipError } from '../../betslip-error';\nimport { ClientErrorType } from '../../client-error-type';\n\nexport class RewardTokensSelectionLevelOddsError extends BetslipError {\n constructor(selectionLevelMinimumOdds: string) {\n super();\n this.icon = BetPlacementErrorIcon.Error;\n this.type = ClientErrorType.RewardTokensSelectionLevelOddsError;\n this.hasClientValidation = true;\n this.selectionLevelMinimumOdds = selectionLevelMinimumOdds;\n }\n\n readonly selectionLevelMinimumOdds: string;\n}\n","import { BetPlacementErrorIcon } from '../../bet-placement-error-icon';\nimport { BetslipError } from '../../betslip-error';\nimport { ClientErrorType } from '../../client-error-type';\n\nexport class RewardTokensTotalOddsError extends BetslipError {\n constructor(minimumOdds: string) {\n super();\n this.icon = BetPlacementErrorIcon.Error;\n this.type = ClientErrorType.RewardTokensTotalOddsError;\n this.hasClientValidation = true;\n this.minimumOdds = minimumOdds;\n }\n\n readonly minimumOdds: string;\n}\n","import { PickId } from '../../../../core/picks/pick-id';\nimport { BetslipError } from '../betslip-error';\nimport { ClientErrorType } from '../client-error-type';\nimport { PreCheckErrorMixin } from '../pre-check/pre-check-error';\nimport { ResultError } from '../result/result-error';\n\nexport abstract class GroupPickError extends BetslipError {\n protected constructor() {\n super();\n this.hasClientValidation = true;\n }\n}\n\nexport class GroupLinearPickError extends PreCheckErrorMixin(ResultError) {\n constructor(pickId: string) {\n super(pickId);\n }\n}\nexport class GroupMinSelectionsError extends GroupPickError {\n constructor() {\n super();\n }\n}\n\nexport class GroupMaxSelectionsError extends GroupPickError {\n constructor() {\n super();\n this.type = ClientErrorType.BetBuilderMaxSelectionsPerPickError;\n }\n}\n\nexport class GroupMaxSelectionsPreCheckError extends GroupLinearPickError {\n constructor(pickId: string) {\n super(pickId);\n }\n}\n\nexport class GroupPickHasLiveLegsPreCheckError extends GroupLinearPickError {\n constructor(pickId: string) {\n super(pickId);\n }\n}\n\nexport class GroupMinSelectionsPreCheckError extends GroupLinearPickError {\n constructor(pickId: string) {\n super(pickId);\n }\n}\n\nexport class GroupPickInvalidPricePreCheckError extends GroupLinearPickError {\n constructor(pickId: string) {\n super(pickId);\n }\n}\n\nexport class GroupWithUncombinableLegsError extends GroupPickError {\n constructor(\n public legIds: PickId[],\n public invalidPickIds: PickId[],\n ) {\n super();\n }\n}\n\nexport class GroupWithUncombinableLegsPreCheckError extends GroupLinearPickError {\n constructor(pickId: string) {\n super(pickId);\n }\n}\n\nexport class GroupWithClosedLegsError extends ResultError {\n constructor(\n public override pickId: string,\n public legIds: PickId[],\n ) {\n super(pickId);\n this.hasClientValidation = true;\n this.isHidden = true;\n }\n}\n\nexport class GroupWithSuspendedLegsError extends ResultError {\n constructor(\n public override pickId: string,\n public legIds: PickId[],\n ) {\n super(pickId);\n this.hasClientValidation = true;\n }\n}\n\nexport class GroupPickComboBetInvalidPrice extends GroupPickError {\n constructor(public invalidPickIds: PickId[]) {\n super();\n this.isHidden = true;\n }\n}\n\nexport class GroupPickHasLiveLegsError extends GroupPickError {\n constructor(public invalidPickIds: PickId[]) {\n super();\n }\n}\n","import { BetPlacementErrorIcon } from '../bet-placement-error-icon';\nimport { BetslipError } from '../betslip-error';\nimport { ClientErrorType } from '../client-error-type';\nimport { PreCheckErrorMixin } from '../pre-check/pre-check-error';\n\nexport class RewardTokensForbiddenError extends PreCheckErrorMixin(BetslipError) {\n constructor() {\n super();\n this.icon = BetPlacementErrorIcon.Error;\n this.type = ClientErrorType.RewardTokensForbiddenError;\n this.hasClientValidation = true;\n }\n}\n","import { GameType } from '@bpos/v1/sports-promo';\n\nimport { BetPlacementErrorIcon } from '../bet-placement-error-icon';\nimport { BetslipError } from '../betslip-error';\nimport { ClientErrorType } from '../client-error-type';\nimport { PreCheckErrorMixin } from '../pre-check/pre-check-error';\n\nexport class RewardTokensGameTypeError extends PreCheckErrorMixin(BetslipError) {\n constructor(gameType: GameType) {\n super();\n this.icon = BetPlacementErrorIcon.Error;\n this.type = ClientErrorType.RewardTokensGameTypeError;\n this.hasClientValidation = true;\n this.gameType = gameType;\n }\n\n readonly gameType: GameType;\n}\n","import { BetPlacementErrorIcon } from '../bet-placement-error-icon';\nimport { BetslipError } from '../betslip-error';\nimport { ClientErrorType } from '../client-error-type';\nimport { PreCheckErrorMixin } from '../pre-check/pre-check-error';\n\nexport class RewardTokensMaxStakeError extends PreCheckErrorMixin(BetslipError) {\n constructor(maximumStake: number) {\n super();\n this.icon = BetPlacementErrorIcon.Error;\n this.type = ClientErrorType.RewardTokensMaxStakeError;\n this.hasClientValidation = true;\n this.maximumStake = maximumStake;\n }\n\n readonly maximumStake: number;\n}\n","import { BetPlacementErrorIcon } from '../bet-placement-error-icon';\nimport { BetslipError } from '../betslip-error';\nimport { ClientErrorType } from '../client-error-type';\n\nexport class RewardTokensMaxTotalOddsError extends BetslipError {\n readonly maxOdds: string;\n\n constructor(maxOdds: string) {\n super();\n this.icon = BetPlacementErrorIcon.Error;\n this.type = ClientErrorType.RewardTokensMaxTotalOddsError;\n this.hasClientValidation = true;\n this.maxOdds = maxOdds;\n }\n}\n","import { BetPlacementErrorIcon } from '../bet-placement-error-icon';\nimport { BetslipError } from '../betslip-error';\nimport { ClientErrorType } from '../client-error-type';\nimport { PreCheckErrorMixin } from '../pre-check/pre-check-error';\n\nexport class RewardTokensMinLegsError extends PreCheckErrorMixin(BetslipError) {\n constructor(minimumLegs: number) {\n super();\n this.icon = BetPlacementErrorIcon.Error;\n this.type = ClientErrorType.RewardTokensMinLegsError;\n this.hasClientValidation = true;\n this.minimumLegs = minimumLegs;\n }\n\n readonly minimumLegs: number;\n}\n","import { BetPlacementErrorIcon } from '../bet-placement-error-icon';\nimport { BetslipError } from '../betslip-error';\nimport { ClientErrorType } from '../client-error-type';\n\nexport class RewardTokensSelectionLevelMaxOddsError extends BetslipError {\n readonly selectionLevelMaxOdds: string;\n\n constructor(maxOdds: string) {\n super();\n this.icon = BetPlacementErrorIcon.Error;\n this.type = ClientErrorType.RewardTokensSelectionMaxOddsError;\n this.hasClientValidation = true;\n this.selectionLevelMaxOdds = maxOdds;\n }\n}\n","import { BetSlipType } from '@bpos';\n\nimport { BetPlacementErrorIcon } from '../bet-placement-error-icon';\nimport { BetslipError } from '../betslip-error';\nimport { ClientErrorType } from '../client-error-type';\nimport { PreCheckErrorMixin } from '../pre-check/pre-check-error';\n\nexport class RewardTokensSlipTypeError extends PreCheckErrorMixin(BetslipError) {\n constructor(slipType: BetSlipType) {\n super();\n this.icon = BetPlacementErrorIcon.Error;\n this.type = ClientErrorType.RewardTokensSlipTypeError;\n this.hasClientValidation = true;\n this.slipType = slipType;\n }\n\n readonly slipType: BetSlipType;\n}\n","import { PlacementErrorType } from '@frontend/sports/types/betslip';\n\nimport { BetPlacementError } from '../bet-placement-error';\nimport { CleanStrategy } from '../betslip-error';\n\nexport class TechnicalError extends BetPlacementError {\n constructor() {\n super();\n this.cleanStrategy = CleanStrategy.Manually;\n this.type = PlacementErrorType.TechnicalError;\n }\n\n reason: string;\n}\n","import { PlacementErrorType } from '@frontend/sports/types/betslip';\n\nimport { BetPlacementError } from '../bet-placement-error';\n\nexport class IncorrectBetCount extends BetPlacementError {\n constructor(expected?: number, received?: number) {\n super();\n this.expectedBetCount = typeof expected === 'undefined' ? null : expected;\n this.receivedBetCount = typeof received === 'undefined' ? null : received;\n this.hasClientValidation = true;\n this.type = PlacementErrorType.IncorrectOptionCount;\n }\n\n readonly expectedBetCount: number | null;\n readonly receivedBetCount: number | null;\n}\n","import { IncorrectBetCount } from '../general/incorrect-bet-count';\nimport { ErrorDetailsSpecifier, PreCheckErrorMixin } from './pre-check-error';\n\nexport class IncorrectOptionsCountPreCheckError extends PreCheckErrorMixin(IncorrectBetCount) {\n constructor(\n expected: number,\n received: number,\n public errorDetails: ErrorDetailsSpecifier | null,\n ) {\n super(expected, received);\n }\n\n override readonly expectedBetCount: number;\n override readonly receivedBetCount: number;\n}\n","import { PlacementErrorType } from '@frontend/sports/types/betslip';\n\nimport { BetslipError } from '../betslip-error';\nimport { PreCheckErrorMixin } from './pre-check-error';\n\nexport class NoBetbuilderSystemPreCheckError extends PreCheckErrorMixin(BetslipError) {\n constructor() {\n super();\n this.type = PlacementErrorType.NoBetbuilderSystemAllowed;\n }\n}\n","import { PlacementErrorType } from '@frontend/sports/types/betslip';\n\nimport { BetPlacementErrorIcon } from '../bet-placement-error-icon';\nimport { StakeError } from '../stake-error';\n\nexport class NotEnoughMoney extends StakeError {\n constructor(\n readonly userMoney: number,\n public stake?: number,\n ) {\n super();\n this.icon = BetPlacementErrorIcon.Warning;\n this.priority = 1000; // Not Enough Money last error\n this.type = PlacementErrorType.InsufficientMoney;\n }\n}\n","import { PlacementErrorType } from '@frontend/sports/types/betslip';\n\nimport { BetPlacementErrorIcon } from '../bet-placement-error-icon';\nimport { UserError } from './user-error';\n\nexport class OverallMaxWinPerUser extends UserError {\n constructor(comboWin: number | undefined, maxComboWin: number | undefined) {\n super();\n this.comboWin = comboWin == null ? null : comboWin;\n this.maxComboWin = maxComboWin == null ? null : maxComboWin;\n this.icon = BetPlacementErrorIcon.Warning;\n this.type = PlacementErrorType.OverallMaxWinPerUserExceeded;\n }\n\n comboWin: number | null;\n maxComboWin: number | null;\n}\n","import { PlacementErrorType } from '@frontend/sports/types/betslip';\n\nimport { BetslipType } from '../../../../core/betslip-type';\nimport { SlipId } from '../../../bet-generation/models';\nimport { BetPlacementError } from '../../errors/bet-placement-error';\nimport { StakeHintInfo } from '../../models/stake-validation.models';\nimport { BetslipTypePickErrors } from '../../state';\n\nconst ignoreNewStakeHintType = [PlacementErrorType.UnderMinimumOddsBoostReward, PlacementErrorType.UnderMinimumRiskFreeCompensation];\n\nexport function getStakeLimitErrorInfo(errors: { pickErrors: BetslipTypePickErrors; slipErrors: Record }): {\n betslipStakeHintInfo: { newStakeHint: number; type: PlacementErrorType } | null;\n pickStakeHintInfos: { newStakeHint: number; pickId: string; type: PlacementErrorType }[];\n} {\n const betslipStakeLimitExceededError = Object.values(errors.slipErrors)\n .flatMap((e) => e)\n .find((betslipError) => shouldUpdateStake(betslipError));\n\n const pickStakeLimitExceededErrors = Object.values(errors.pickErrors)\n .flatMap((e) => Object.values(e))\n .flatMap((resultErrors) => resultErrors)\n .filter((e) => shouldUpdateStake(e));\n\n if (!betslipStakeLimitExceededError && pickStakeLimitExceededErrors.length === 0) {\n return { betslipStakeHintInfo: null, pickStakeHintInfos: [] };\n }\n\n if (betslipStakeLimitExceededError) {\n return {\n betslipStakeHintInfo: { newStakeHint: betslipStakeLimitExceededError.newStakeHint!, type: betslipStakeLimitExceededError.type },\n pickStakeHintInfos: [],\n };\n }\n\n return {\n betslipStakeHintInfo: null,\n pickStakeHintInfos: pickStakeLimitExceededErrors.map((pickError) => ({\n type: pickError.type,\n pickId: pickError.pickId,\n newStakeHint: pickError.newStakeHint!,\n })),\n };\n}\n\nexport function getStakeHints(errors: { pickErrors: BetslipTypePickErrors; slipErrors: Record }): StakeHintInfo {\n const slipStakeHintInfos = Object.entries(errors.slipErrors).flatMap(([key, slipErrors]) =>\n slipErrors.filter(shouldUpdateStake).map((error) => ({ newStakeHint: error.newStakeHint!, slipId: key, type: error.type })),\n );\n\n // This is only relevant for multi-singles\n const pickStakeHintInfos = Object.entries(errors.pickErrors[BetslipType.Single])\n .flatMap(([pickId, e]) => e.filter((error) => shouldUpdateStake(error)).map((error) => ({ pickId, error })))\n .map(({ pickId, error }) => ({\n type: error.type,\n // we cannot use error.pickId here\n // after bet placement we are also mapping normal betslip errors to ResultError and therefore error.pickId could be undefined\n pickId,\n newStakeHint: error.newStakeHint!,\n }));\n\n return {\n slipStakeHintInfos,\n pickStakeHintInfos,\n };\n}\n\nexport function shouldUpdateStake(error: BetPlacementError): boolean {\n return isNewStakeHintError(error) && !ignoreNewStakeHintType.includes(error.type);\n}\n\nexport function isNewStakeHintError(error: BetPlacementError): boolean {\n return typeof error.newStakeHint === 'number' && error.newStakeHint > 0;\n}\n","import { CashoutErrorType } from '@bpos/v1/cashout';\nimport { isDefined } from '@frontend/sports/common/core/utils/extended-types';\nimport { PlacementErrorType } from '@frontend/sports/types/betslip';\nimport { orderBy } from 'lodash-es';\n\nimport { BetslipType } from '../../../../core/betslip-type';\nimport { BaseBetslipGroupPick } from '../../../../core/picks/betslip-group-pick';\nimport { BetslipPick } from '../../../../core/picks/betslip-pick';\nimport { GroupEnabledPickIds } from '../../../../core/picks/pick-id';\nimport { PickOddsState } from '../../../../core/picks/pick-models';\nimport { StakeUpdateErrorModel } from '../../../betplacement/models';\nimport { hasUnacceptedPick } from '../../../picks/services/linear-betslip-pick.utils';\nimport { IBetslipTypeState } from '../../../types/state';\nimport { BetPlacementError } from '../../errors/bet-placement-error';\nimport { BetslipError, ErrorOrigin } from '../../errors/betslip-error';\nimport { ClientErrorType } from '../../errors/client-error-type';\nimport { RewardTokensBetBuilderError } from '../../errors/general/betandget/reward-tokens-bet-builder-error';\nimport { RewardTokensMinStakeError } from '../../errors/general/betandget/reward-tokens-min-stake-error';\nimport { RewardTokensSelectionLevelOddsError } from '../../errors/general/betandget/reward-tokens-selection-level-odds-error';\nimport { RewardTokensTotalOddsError } from '../../errors/general/betandget/reward-tokens-total-odds-error';\nimport {\n GroupLinearPickError,\n GroupPickComboBetInvalidPrice,\n GroupPickError,\n GroupPickHasLiveLegsError,\n GroupPickInvalidPricePreCheckError,\n GroupWithClosedLegsError,\n GroupWithSuspendedLegsError,\n GroupWithUncombinableLegsError,\n} from '../../errors/general/group-pick-error';\nimport { RewardTokensForbiddenError } from '../../errors/general/reward-tokens-forbidden-error';\nimport { RewardTokensGameTypeError } from '../../errors/general/reward-tokens-game-type-error';\nimport { RewardTokensMaxStakeError } from '../../errors/general/reward-tokens-max-stake-error';\nimport { RewardTokensMaxTotalOddsError } from '../../errors/general/reward-tokens-max-total-odds-error';\nimport { RewardTokensMinLegsError } from '../../errors/general/reward-tokens-min-legs-error';\nimport { RewardTokensSelectionLevelMaxOddsError } from '../../errors/general/reward-tokens-selection-level-max-odds-error';\nimport { RewardTokensSlipTypeError } from '../../errors/general/reward-tokens-slip-type-error';\nimport { TechnicalError } from '../../errors/general/technical-error';\nimport { NotifyUserError } from '../../errors/notify-user-error';\nimport { IncorrectOptionsCountPreCheckError } from '../../errors/pre-check/incorrect-options-count-pre-check-error';\nimport { NoBetbuilderSystemPreCheckError } from '../../errors/pre-check/no-betbuilder-system-pre-check-error';\nimport { OddsChangedPreCheckError } from '../../errors/pre-check/odds-changed-pre-check-error';\nimport { OverMaximumStakePreCheckError } from '../../errors/pre-check/over-maximum-stake-pre-check-error';\nimport { StakeNotDividablePreCheckError } from '../../errors/pre-check/stake-not-dividable-pre-check-error';\nimport { UnderMinimumStakePreCheckError } from '../../errors/pre-check/under-minimum-stake-pre-check-error';\nimport { UnderMinimumWinningsPreCheckError } from '../../errors/pre-check/under-minimum-winnings-pre-check-error';\nimport { ResultError } from '../../errors/result/result-error';\nimport { NotEnoughMoney } from '../../errors/user/not-enough-money';\nimport { OverallMaxWinPerUser } from '../../errors/user/overall-max-win-per-user';\nimport { BetslipTypePickErrors, IBetslipErrorsState, IBetslipPickErrors, SlipErrors } from '../../state';\nimport { getSlipErrorsForType } from './betslip-errors-state-utils';\nimport { isNewStakeHintError } from './stake-limit-errors-info-utils';\n\nexport const enum OverallMaxWinPerUserHintType {\n Any = 'Any',\n WithHint = 'WithHint',\n WithoutHint = 'WithoutHint',\n}\n\nexport const isPickStatusLocked = function (error: BetslipError): boolean {\n return (\n error.type === PlacementErrorType.OptionInvisible ||\n error.type === CashoutErrorType.ChangedOptionStatus ||\n error.type === PlacementErrorType.CutOffDateReached ||\n error.type === PlacementErrorType.InvalidOption ||\n error.type === PlacementErrorType.MissingInfo ||\n error.type === PlacementErrorType.NotPlayable ||\n error.type === PlacementErrorType.OpenDateNotReached ||\n error.type === PlacementErrorType.OptionError\n );\n};\n\nexport const isPickStatusClosed = function (error: BetslipError): boolean {\n return error.type === PlacementErrorType.CutOffDateReached || error.type === PlacementErrorType.OpenDateNotReached;\n};\n\nexport const isPickStatusOddsChanged = function (error: BetslipError): boolean {\n return error.type === PlacementErrorType.OddsChanged;\n};\n\n/**\n * Is the given error changes pick status and the user should accept changes.\n *\n * @param error\n */\nexport const isPickStatusChangeError = function (error: BetslipError): boolean {\n return isPickStatusLocked(error) || isPickStatusClosed(error) || isPickStatusOddsChanged(error) || isGroupPickOfferChange(error);\n};\n\nexport const isTeaserBetPickStatusChangeError = function (error: BetslipError): boolean {\n return isPickStatusLocked(error) || isPickStatusClosed(error);\n};\n\nexport const isImportantError = function (error: BetslipError): boolean {\n return !(isPickStatusChangeError(error) || isErrorComboPrevention(error));\n};\n\nexport const isBetslipStatusInsufficientMoney = function (error: BetslipError): boolean {\n return error.type === PlacementErrorType.InsufficientMoney;\n};\n\nexport const isNotifyUserChangeError = function (error: BetslipError): error is NotifyUserError {\n return 'userHasBeenNotified' in error;\n};\n\nexport const isPlaceBlockingError = function (error: BetslipError): boolean {\n return (\n isErrorComboPrevention(error) ||\n error.type === PlacementErrorType.BlockedAsInsider ||\n error.type === PlacementErrorType.IncorrectOptionCount ||\n error.type === PlacementErrorType.IncorrectBankerCount ||\n error.type === PlacementErrorType.UnderMinimumCompulsoryOdds ||\n isNotDividableStakeErrorPreCheck(error)\n );\n};\n\nexport const isUnderMinimumStakeErrorPreCheck = (error: BetslipError): error is UnderMinimumStakePreCheckError =>\n error.type === PlacementErrorType.StakeBelowMinimumLimit;\n\nexport const isUnderMinimumWinningsErrorPreCheck = (error: BetslipError): error is UnderMinimumWinningsPreCheckError =>\n error.type === PlacementErrorType.UnderMinimumWinnings;\n\nexport const isOverMaximumStakeError = (error: BetslipError): error is OverMaximumStakePreCheckError =>\n error.type === PlacementErrorType.OverMaxBetLimit;\n\nexport const isNotDividableStakeErrorPreCheck = (error: BetslipError): error is StakeNotDividablePreCheckError =>\n error.type === PlacementErrorType.NotWholeStake;\n\nexport const isPickStatusOddsChangedPreCheck = function (error: BetslipError): error is OddsChangedPreCheckError {\n return error.type === PlacementErrorType.OddsChanged;\n};\n\nexport const isNoBetBuilderSystemPreCheck = function (error: BetslipError): error is NoBetbuilderSystemPreCheckError {\n return error instanceof NoBetbuilderSystemPreCheckError;\n};\n\n/**\n * This function help us to check when we need to show yellow indication that has not compatible picks.\n */\nexport const isErrorComboPrevention = function (error: BetslipError): boolean {\n return error.type === PlacementErrorType.ComboPrevention || error.type === PlacementErrorType.MinimumCombo;\n};\n\nexport const isStakeError = function (error: BetslipError): boolean {\n return (\n error.type === PlacementErrorType.StakeBelowMinimumLimit ||\n error.type === PlacementErrorType.UnderMinimumWinnings ||\n error.type === PlacementErrorType.OverMaxBetLimit ||\n error.type === PlacementErrorType.NotWholeStake ||\n error.type === PlacementErrorType.InsufficientMoney\n );\n};\n\nexport const shouldThisPlacementErrorLockEditBet = (error: BetslipError): boolean => {\n return error.type === PlacementErrorType.TechnicalError;\n};\n\nexport const isIncorrectOptionsCountError = (error: BetslipError): error is IncorrectOptionsCountPreCheckError =>\n error instanceof IncorrectOptionsCountPreCheckError;\n\nexport const isRewardTokensSlipTypeError = (error: BetslipError): error is RewardTokensSlipTypeError => error instanceof RewardTokensSlipTypeError;\n\nexport const isRewardTokensMaxStakeError = (error: BetslipError): error is RewardTokensMaxStakeError => error instanceof RewardTokensMaxStakeError;\n\nexport const isRewardTokensError = (error: BetslipError): boolean =>\n [\n RewardTokensSlipTypeError,\n RewardTokensMaxStakeError,\n RewardTokensMinStakeError,\n RewardTokensMinLegsError,\n RewardTokensSelectionLevelOddsError,\n RewardTokensGameTypeError,\n RewardTokensForbiddenError,\n RewardTokensTotalOddsError,\n RewardTokensBetBuilderError,\n RewardTokensMaxTotalOddsError,\n RewardTokensSelectionLevelMaxOddsError,\n ].some((t) => error instanceof t);\n\nexport const isOverallMaxWinPerUserExceededError = (error: BetslipError, hint: OverallMaxWinPerUserHintType): error is OverallMaxWinPerUser => {\n if (error instanceof OverallMaxWinPerUser) {\n switch (hint) {\n case OverallMaxWinPerUserHintType.Any:\n return true;\n case OverallMaxWinPerUserHintType.WithHint:\n return !!error.newStakeHint && error.newStakeHint > 0;\n case OverallMaxWinPerUserHintType.WithoutHint:\n return error.newStakeHint === 0;\n }\n }\n\n return false;\n};\n\nexport const hasCriticalBetslipAndTypeErrorForType = (errorState: IBetslipErrorsState, type: BetslipType): boolean =>\n errorState.betslipErrors.some(isCriticalError) || errorState.betslipTypeErrors[type].some(isCriticalError);\n\nexport const hasCriticalSlipErrorsForType = (\n errorState: IBetslipErrorsState,\n type: BetslipType,\n types: IBetslipTypeState,\n isLinear: boolean,\n): boolean => getSlipErrorsForType(type, errorState.slipErrors, types, isLinear).some(isCriticalError);\n\nexport const hasCriticalErrorForSlip = (errorState: IBetslipErrorsState, slipId: string): boolean =>\n (errorState.slipErrors[slipId] ?? []).some(isCriticalError);\n\nexport const isCriticalError = (error: BetslipError): boolean => {\n return (\n !isStakeError(error) &&\n !(error.type === PlacementErrorType.NoMultiSinglesAllowed) &&\n !(error instanceof BetPlacementError && isNewStakeHintError(error)) &&\n !(error instanceof TechnicalError)\n );\n};\n\nexport const hasInsufficientMoneyError = (errors: BetslipError[]): boolean => {\n return errors.some(isBetslipStatusInsufficientMoney);\n};\n\nexport const getStakeUpdateErrors = (slipErrors: SlipErrors): StakeUpdateErrorModel[] => {\n return Object.entries(slipErrors).flatMap(([key, errors]) =>\n errors.filter((e) => isUnderMinimumStakeErrorPreCheck(e)).map((e) => ({ error: e as UnderMinimumStakePreCheckError, slipId: key })),\n );\n};\n\n/**\n * Push a new error in the errors collection\n *\n * @param currentErrors\n * @param newError\n */\nexport const errorCollectionPusher = (currentErrors: TError[], newError: TError): TError[] => {\n if (currentErrors.some((e) => newError.equals(e))) {\n // Error already exists.\n return currentErrors;\n }\n\n return currentErrors.filter((e) => e.constructor !== newError.constructor).concat(newError);\n};\n\nexport function getPlacementError(errors: BetslipError[]): BetslipError | null {\n const filteredErrors = filterBetPlacementErrors(errors);\n\n return filteredErrors[0] || null;\n}\n\nexport function getPlacementErrorFromAllErrors(\n betslipErrors: BetslipError[],\n typePickErrors: BetslipTypePickErrors,\n slipErrors: SlipErrors,\n): BetslipError | null {\n const allPickErrors = Object.values(typePickErrors).flatMap((pickErrors) => Object.values(pickErrors).flatMap((errors) => errors));\n\n const allSlipErrors = Object.values(slipErrors).flatMap((errors) => errors);\n\n return getPlacementError([...betslipErrors, ...allPickErrors, ...allSlipErrors]);\n}\n\nfunction filterBetPlacementErrors(errors: BetslipError[]): BetslipError[] {\n const filteredErrors = errors.filter((error) => !error.isHidden && error.origin === ErrorOrigin.BetPlacement);\n\n return orderBy(filteredErrors, (x) => x.priority);\n}\n\nconst exitFreebetErrors: (PlacementErrorType | CashoutErrorType | ClientErrorType)[] = [\n PlacementErrorType.InvalidFreeBet,\n PlacementErrorType.FreeBetExpired,\n PlacementErrorType.FreeBetNotActive,\n];\n\nexport function isExitFreeBetError(error: BetslipError | null): boolean {\n return !!error && exitFreebetErrors.includes(error.type);\n}\n\nexport function isFinishedError(error: BetslipError): boolean {\n return error.code === 'COS' || error.code === 'CODR';\n}\n\nexport function canShowPickErrorAsBetslipError(betslipErrors: BetslipError[], currentSelectedBetslipType: BetslipType | null): boolean {\n // For certain NewStakeHintErrors we receive them per pick from BPOS but in specific situations we need to show them for the entire betslip\n return currentSelectedBetslipType !== BetslipType.Single && betslipErrors.every((error) => isStakeError(error));\n}\n\nexport function isNewStakeHintPlacementError(error: BetslipError): boolean {\n return error instanceof BetPlacementError && error.origin === ErrorOrigin.BetPlacement && typeof error.newStakeHint === 'number';\n}\n\nexport const isNotEnoughMoney = (error: BetslipError): error is NotEnoughMoney => error instanceof NotEnoughMoney;\n\nexport function checkOfferChanged(pickErrors: ResultError[]): boolean {\n return pickErrors.some((e) => isPickStatusChangeError(e));\n}\n\nexport function checkOfferChangedLinear(picks: BetslipPick[], typeState: IBetslipTypeState, pickErrors: ResultError[]): boolean {\n const oddsChanged = pickErrors.some((e) => isPickStatusOddsChanged(e));\n\n return oddsChanged || hasUnacceptedPick(typeState, picks);\n}\n\nexport function isGroupLockedPickError(error: ResultError): boolean {\n return error instanceof GroupWithSuspendedLegsError;\n}\n\nexport function isGroupOfferChangedError(error: BetslipError): boolean {\n return error instanceof ResultError && isGroupLockedPickError(error);\n}\n\nexport function isBetPlacementBlockingError(error: BetslipError): boolean {\n return (\n ![\n PlacementErrorType.TechnicalError,\n PlacementErrorType.OverAskOfferError,\n PlacementErrorType.InsufficientMoney,\n PlacementErrorType.DoubleBetPrevention,\n ].includes(error.type as PlacementErrorType) && !isNewStakeHintError(error as BetPlacementError)\n );\n}\n\nexport function pickErrorsHasBlockingErrors(pickErrors: IBetslipPickErrors): boolean {\n return Object.values(pickErrors).some((errors) => errors.some((err) => err instanceof GroupWithSuspendedLegsError));\n}\n\nexport function getClosedLegIdsWithinValidGroups(picks: BetslipPick[], picksErrors: IBetslipPickErrors) {\n return (\n picks\n .filter(BaseBetslipGroupPick.isPick)\n //ignore closed picks within a group if the group is suspended\n .filter((g) => g.oddsState === PickOddsState.Open)\n .map((g) => picksErrors[g.id.toString()]?.find((error) => error instanceof GroupWithClosedLegsError) as GroupWithClosedLegsError)\n .filter(isDefined)\n .flatMap((g) => g.legIds as GroupEnabledPickIds[])\n );\n}\n\nexport function getSuspendedLegIdsWithinValidGroups(picks: BetslipPick[], picksErrors: IBetslipPickErrors) {\n return (\n picks\n .filter(BaseBetslipGroupPick.isPick)\n //ignore suspended picks within a group if the group is suspended\n .filter((g) => g.oddsState === PickOddsState.Open)\n .map((g) => picksErrors[g.id.toString()]?.find((error) => error instanceof GroupWithSuspendedLegsError) as GroupWithSuspendedLegsError)\n .filter(isDefined)\n .flatMap((g) => g.legIds as GroupEnabledPickIds[])\n );\n}\n\nexport function isGroupPickOfferChange(error: BetslipError): error is GroupWithSuspendedLegsError | GroupWithClosedLegsError {\n return error instanceof GroupWithSuspendedLegsError || error instanceof GroupWithClosedLegsError;\n}\n\n/**\n * Checks if a pick should be displayed on the pick level in the betslip\n * @param error The error to check\n * @returns True if the error should be displayed on the pick level\n */\nexport function isDisplayablePickError(error: ResultError): boolean {\n return (\n !error.isHidden &&\n !(\n error.type === PlacementErrorType.ComboPrevention ||\n error.type === PlacementErrorType.CutOffDateReached ||\n error.type === PlacementErrorType.OptionInvisible ||\n error.type === PlacementErrorType.OddsChanged\n )\n );\n}\n\nexport function isGroupPickError(error: BetslipError): error is GroupPickError {\n return error instanceof GroupPickError || error instanceof GroupLinearPickError;\n}\n\nexport function isGroupPickInvalidPriceError(error: BetslipError): error is GroupPickComboBetInvalidPrice {\n return error instanceof GroupPickComboBetInvalidPrice || error instanceof GroupPickInvalidPricePreCheckError;\n}\n\nexport function isGroupWithUncombinableLegsError(error: BetslipError): error is GroupWithUncombinableLegsError {\n return error instanceof GroupWithUncombinableLegsError;\n}\n\nexport function isDrawerGroupError(\n error: BetslipError,\n): error is GroupWithUncombinableLegsError | GroupPickComboBetInvalidPrice | GroupPickHasLiveLegsError {\n return isGroupWithUncombinableLegsError(error) || isGroupPickInvalidPriceError(error) || isGroupPickHasLiveLegsError(error);\n}\n\nexport function isGroupPickHasLiveLegsError(error: BetslipError): error is GroupPickHasLiveLegsError {\n return error instanceof GroupPickHasLiveLegsError;\n}\n\nexport function groupPickHasWarning(error: BetslipError): error is GroupPickHasLiveLegsError | GroupPickComboBetInvalidPrice {\n return isGroupPickInvalidPriceError(error) || isGroupPickHasLiveLegsError(error);\n}\n","import { PlacementErrorType } from '@frontend/sports/types/betslip';\nimport { createSelector } from '@ngrx/store';\n\nimport { betslipSelector, selectIsLinearBetslip } from '../../base/store/selectors';\nimport { BetslipType } from '../../core/betslip-type';\nimport { PickId } from '../../core/picks/pick-id';\nimport { SlipId } from '../bet-generation/models';\nimport { getBetBuilderSlipId, getSingleSlipId } from '../bet-generation/services/slip.utils';\nimport { LinearBetBuilderPickId } from '../linear-bet-builder/models';\nimport { selectBetBuilderPickStakeFactory } from '../linear-bet-builder/selectors';\nimport { betslipPicksListSelector, selectBetslipFlattenedPicksList } from '../picks/selectors';\nimport { selectSingleBetAllSameStake, selectSingleBetPickStakeFactory, singleBetPicksGeneralStakeSelector } from '../single-bet/selectors';\nimport { selectLinearStakeForSlip } from '../stake/selectors';\nimport { selectIsSystemBetExpanded } from '../system-bet/selectors';\nimport { betslipCurrentTypeSelector } from '../types/base/selectors';\nimport { betslipTypeStateSelector, editBetAddedPicksListSelector, editBetPicksListSelector, selectIsSingleBetSelected } from '../types/selectors';\nimport { BetslipError, ErrorOrigin } from './errors/betslip-error';\nimport { MinSelectionsBetbuilderError } from './errors/general/min-selections-betbuilder-error';\nimport { getSlipErrorsForType } from './services/utils/betslip-errors-state-utils';\nimport {\n canShowPickErrorAsBetslipError,\n checkOfferChanged,\n checkOfferChangedLinear,\n isBetslipStatusInsufficientMoney,\n isDisplayablePickError,\n isDrawerGroupError,\n isErrorComboPrevention,\n isGroupPickError,\n isGroupPickOfferChange,\n isNotEnoughMoney,\n isPickStatusChangeError,\n isRewardTokensError,\n isStakeError,\n} from './services/utils/betslip-errors-utils';\nimport { BetslipTypePickErrors, SlipErrors } from './state';\n\n// State\n\n/** Selects the entire error state */\nexport const selectErrorsState = createSelector(betslipSelector, (state) => state.errors);\n\n/** Selects the state for errors that effect the entire betslip */\nexport const selectBetslipErrors = createSelector(selectErrorsState, (state) => state.betslipErrors);\n\n/** Selects the state for errors that affect individual picks*/\nexport const selectPickErrorsState = createSelector(selectErrorsState, (errorsState) => errorsState.pickErrors);\n\n/** Selects the state for errors that affect individual slips (multi-singles, combo, system) */\nexport const selectSlipErrorsState = createSelector(selectErrorsState, (errorState) => errorState.slipErrors);\n\n/** Selects the state for errors that affect individual betslip types (single, combo, system, teaser) */\nexport const selectTypeErrorsState = createSelector(selectErrorsState, (errorState) => errorState.betslipTypeErrors);\n\n// Slip Errors\n\nexport const selectSlipErrorsFactory = (slipKey: SlipId) =>\n createSelector(selectSlipErrorsState, (slipErrors: SlipErrors): BetslipError[] | undefined => slipErrors[slipKey]);\n\nexport const selectSlipErrorsForTypeFactory = (activeType: BetslipType) =>\n createSelector(selectSlipErrorsState, betslipTypeStateSelector, selectIsLinearBetslip, (state, types, isLinear) => {\n return getSlipErrorsForType(activeType, state, types, isLinear);\n });\n\nexport const selectCurrentSlipErrors = createSelector(\n selectSlipErrorsState,\n betslipTypeStateSelector,\n selectIsLinearBetslip,\n (state, types, isLinear) => {\n const currentType = types.base.currentSelectedType;\n\n return !currentType ? [] : getSlipErrorsForType(currentType, state, types, isLinear);\n },\n);\n\n/**\n * Selects all slip errors for a multi single pick with the given PickId\n */\nexport const selectSingleSlipErrorsFactory = (pickId: PickId) =>\n createSelector(selectSlipErrorsState, (errorState) => errorState[getSingleSlipId(pickId)] ?? []);\n\nexport const selectBetBuilderSlipErrorsFactory = (pickId: PickId) =>\n createSelector(selectSlipErrorsState, (errorState) => errorState[getBetBuilderSlipId(pickId)] ?? []);\n\nexport const selectHasAnySlipErrors = createSelector(selectSlipErrorsState, (state) => Object.values(state).some((p) => p.length > 0));\n\n// Betslip Type Errors\n\nexport const selectBetslipTypeErrorsFactory = (type: BetslipType) => createSelector(selectTypeErrorsState, (state) => state[type] ?? []);\n\nexport const selectCurrentBetslipTypeErrors = createSelector(selectTypeErrorsState, betslipCurrentTypeSelector, (state, type) =>\n type ? state[type] : [],\n);\n\nexport const selectHasAnyTypeErrors = createSelector(selectTypeErrorsState, (state) => Object.values(state).some((p) => p.length > 0));\n\n// Betslip Errors\n\nexport const selectAllBetslipErrorsForType = (type: BetslipType) =>\n createSelector(\n selectBetslipErrors,\n selectBetslipTypeErrorsFactory(type),\n selectSlipErrorsForTypeFactory(type),\n (betslipErrors, typeErrors, slipErrors) => {\n return [...betslipErrors, ...typeErrors, ...slipErrors];\n },\n );\n\nexport const selectAllCurrentBetslipErrors = createSelector(\n selectBetslipErrors,\n selectCurrentBetslipTypeErrors,\n selectCurrentSlipErrors,\n (betslipErrors, typeErrors, slipErrors) => {\n return [...betslipErrors, ...typeErrors, ...slipErrors];\n },\n);\n\nexport const selectAllBetBuilderErrors = createSelector(\n selectBetslipErrors,\n selectSlipErrorsForTypeFactory(BetslipType.BetBuilder),\n selectBetslipTypeErrorsFactory(BetslipType.BetBuilder),\n (betslipErrors, typeErrors, slipErrors) => {\n return [...betslipErrors, ...typeErrors, ...slipErrors];\n },\n);\n\nexport const selectHasAnyBetslipErrors = createSelector(selectBetslipErrors, (errors) => errors.length > 0);\n\n// Pick Errors\n\nexport const selectPickErrorsForTypeFactory = (type: BetslipType) =>\n createSelector(selectErrorsState, (state) => {\n return state.pickErrors[type] ?? {};\n });\n\nexport const selectAllPickErrorsForTypeFactory = (type: BetslipType) =>\n createSelector(selectPickErrorsForTypeFactory(type), (pickErrors) => {\n return Object.values(pickErrors).flatMap((errors) => errors) ?? [];\n });\n\nexport const selectAllPickErrors = createSelector(selectPickErrorsState, (pickErrors) =>\n Object.values(pickErrors).flatMap((errors) => Object.values(errors).flatMap((e) => e)),\n);\n\nexport const selectTypePickErrors = createSelector(selectErrorsState, (state) => {\n return state.pickErrors;\n});\n\n/**\n * Selects all errors for a pick with the given PickId for the currently selected betslip type (tabbed betslip)\n */\nexport const selectCurrentPickErrorsForPickFactory = (pickId: PickId) =>\n createSelector(selectPickErrorsState, betslipCurrentTypeSelector, (pickErrors, type) => {\n if (!type) {\n return [];\n }\n\n return pickErrors[type][pickId.toString()] ?? [];\n });\n\n/** Selects all errors for a pick with the given PickId for the given betslip type */\nexport const selectTypePickErrorsForPickFactory = (pickId: PickId, type: BetslipType) =>\n createSelector(selectPickErrorsState, (pickErrors) => {\n return pickErrors[type][pickId.toString()] ?? [];\n });\n\nexport const selectAllPickErrorsForPickFactory = (pickId: PickId) =>\n createSelector(selectPickErrorsState, (pickErrors) => {\n const pickIdString = pickId.toString();\n\n return Object.values(pickErrors).flatMap((errors) => errors[pickIdString] ?? []);\n });\n\nexport const selectCurrentPicksErrors = createSelector(selectPickErrorsState, betslipCurrentTypeSelector, (pickErrors, type) =>\n type ? pickErrors[type] : {},\n);\n\nexport const selectAllCurrentPickErrors = createSelector(selectCurrentPicksErrors, (currentPicks) =>\n Object.values(currentPicks).flatMap((picks) => picks),\n);\n\nexport const selectCurrentPicksErrorsForAllPicks = createSelector(selectCurrentPicksErrors, (picksErrors) =>\n Object.values(picksErrors).flatMap((p) => p),\n);\n\nexport const selectHasAnyPickErrors = createSelector(selectPickErrorsState, (pickErrors) =>\n Object.values(pickErrors).some((p) => Object.values(p).some((e) => e.length > 0)),\n);\n\nexport const selectPicksWithCurrentErrors = createSelector(betslipPicksListSelector, selectCurrentPicksErrors, (pickList, pickErrors) =>\n pickList.map((pick) => ({\n pick,\n errors: pickErrors[pick.id.toString()] || [],\n })),\n);\n\nexport const selectPicksWithAllErrors = createSelector(selectBetslipFlattenedPicksList, selectPickErrorsState, (pickList, pickErrors) =>\n pickList.map((pick) => ({\n pick,\n errors: Object.values(pickErrors).flatMap((errors) => errors[pick.id.toString()] ?? []),\n })),\n);\n\nexport const selectEditBetPicksWithErrors = createSelector(\n editBetPicksListSelector,\n editBetAddedPicksListSelector,\n selectPickErrorsForTypeFactory(BetslipType.EditBet),\n (pickList, pickAddedList, pickErrors) =>\n [...pickList, ...pickAddedList].map((pick) => ({\n pick,\n errors: pickErrors[pick.id.toString()] || [],\n })),\n);\n\n// Specific selectors\nexport const selectAllSingleBetBetslipErrors = createSelector(selectAllBetslipErrorsForType(BetslipType.Single), (errors) => errors);\n\nexport const selectAllCurrentErrors = createSelector(selectAllCurrentBetslipErrors, selectAllCurrentPickErrors, (betslipErrors, pickErrors) => [\n ...betslipErrors,\n ...pickErrors,\n]);\n\nexport const selectNotEnoughMoneyError = createSelector(selectBetslipErrors, (errors) => errors.find(isNotEnoughMoney));\n\nexport const selectCurrentVisibleBetslipErrorsByPriority = createSelector(selectAllCurrentBetslipErrors, (errors) =>\n errors.filter((e) => !e.isHidden).sort((a, b) => b.priority - a.priority),\n);\n\nexport const selectHasCurrentComboPreventionError = createSelector(selectCurrentPicksErrorsForAllPicks, (errors) =>\n errors.some(isErrorComboPrevention),\n);\n\nexport const selectHasComboPreventionForTypeFactory = (type: BetslipType) =>\n createSelector(selectAllPickErrorsForTypeFactory(type), (errors) => errors.some(isErrorComboPrevention));\n\nexport const selectHasInsufficientMoneyError = createSelector(selectBetslipErrors, (errors) => errors.some(isBetslipStatusInsufficientMoney));\n\nexport const selectHasNonPickErrors = createSelector(\n selectHasAnyBetslipErrors,\n selectHasAnyTypeErrors,\n selectHasAnySlipErrors,\n (hasBetslipErrors, hasTypeErrors, hasSlipErrors) => hasBetslipErrors || hasTypeErrors || hasSlipErrors,\n);\n\nexport const selectHasAnyErrors = createSelector(\n selectHasNonPickErrors,\n selectHasAnyPickErrors,\n (hasNonPickErrors, hasPickErrors) => hasNonPickErrors || hasPickErrors,\n);\n\nexport const selectHasMinSelectionsBetBuilderError = createSelector(selectBetslipErrors, (errors) =>\n errors.some((error) => error instanceof MinSelectionsBetbuilderError),\n);\n\nexport const selectCurrentStakeError = createSelector(\n selectAllCurrentBetslipErrors,\n selectIsSingleBetSelected,\n selectSingleBetAllSameStake,\n (errors, isSingleBet, singleBetAllSameStake) =>\n isSingleBet && !singleBetAllSameStake\n ? errors.find((betslipError) => betslipError.type === PlacementErrorType.InsufficientMoney)\n : errors.find(isStakeError),\n);\n\nexport const selectCanShowPickErrorAsBetslipError = createSelector(selectAllCurrentBetslipErrors, betslipCurrentTypeSelector, (betslipErrors, type) =>\n canShowPickErrorAsBetslipError(betslipErrors, type),\n);\n\nexport const selectHasOfferChanged = createSelector(\n selectAllPickErrors,\n betslipPicksListSelector,\n selectIsLinearBetslip,\n betslipTypeStateSelector,\n (pickErrors, picks, isLinear, types) => (isLinear ? checkOfferChangedLinear(picks, types, pickErrors) : checkOfferChanged(pickErrors)),\n);\n\nexport const selectHasOfferChangedFromBetPlacement = createSelector(selectAllPickErrors, (errors) =>\n errors.some((e) => isPickStatusChangeError(e) && e.origin === ErrorOrigin.BetPlacement),\n);\n\nexport const selectDisplayablePickErrorsFactory = (pickId: PickId, betslipType: BetslipType) =>\n createSelector(selectTypePickErrorsForPickFactory(pickId, betslipType), (errors) => errors.filter((e) => isDisplayablePickError(e)));\n\nexport const selectTabbedPickErrorsFactory = (pickId: PickId, betslipType: BetslipType) =>\n createSelector(selectDisplayablePickErrorsFactory(pickId, betslipType), selectCanShowPickErrorAsBetslipError, (errors, hidePickErrors) =>\n !hidePickErrors ? errors : [],\n );\n\nexport const selectCurrentSummarySlipErrors = createSelector(\n selectSlipErrorsState,\n betslipTypeStateSelector,\n selectIsLinearBetslip,\n (state, types, isLinear) => {\n const currentType = types.base.currentSelectedType;\n\n return !currentType || currentType === BetslipType.Single ? [] : getSlipErrorsForType(currentType, state, types, isLinear);\n },\n);\n\nexport const selectAllCurrentSummaryErrors = createSelector(\n selectBetslipErrors,\n selectCurrentBetslipTypeErrors,\n selectCurrentSummarySlipErrors,\n selectAllCurrentPickErrors,\n selectCanShowPickErrorAsBetslipError,\n (betslipErrors, typeErrors, slipErrors, pickErrors, showPickErrors) => {\n return !showPickErrors ? [...betslipErrors, ...typeErrors, ...slipErrors] : [...betslipErrors, ...typeErrors, ...slipErrors, ...pickErrors];\n },\n);\n\nexport const selectSingleBetPickErrorsFactory = (pickId: PickId) =>\n createSelector(\n selectTypePickErrorsForPickFactory(pickId, BetslipType.Single),\n selectSingleSlipErrorsFactory(pickId),\n (pickErrors, slipErrors) => [...pickErrors, ...slipErrors],\n );\n\nexport const selectSinglePickErrorStateFactory = (pickId: PickId) =>\n createSelector(selectSingleBetPickErrorsFactory(pickId), selectSingleBetPickStakeFactory(pickId), (errors, stake) => ({\n errors,\n stake,\n }));\n\nexport const selectBetBuilderBetPickErrorsFactory = (pickId: PickId) =>\n createSelector(\n selectDisplayablePickErrorsFactory(pickId, BetslipType.BetBuilder),\n selectBetBuilderSlipErrorsFactory(pickId),\n (pickErrors, slipErrors) => [...pickErrors, ...slipErrors],\n );\n\nexport const selectBetBuilderPickErrorStateFactory = (pickId: LinearBetBuilderPickId) =>\n createSelector(selectBetBuilderBetPickErrorsFactory(pickId), selectBetBuilderPickStakeFactory(pickId), (errors, stake) => ({\n errors,\n stake,\n }));\n\nexport const selectSinglePickStakeErrorStateFactory = (pickId: PickId) =>\n createSelector(\n selectSinglePickErrorStateFactory(pickId),\n singleBetPicksGeneralStakeSelector,\n selectHasInsufficientMoneyError,\n (state, generalStake, hasInsufficientFunds) => ({\n stakeError: state.errors.find(isStakeError),\n stake: state.stake,\n showInsufficientFundsError: hasInsufficientFunds && !!state.stake?.actualStake && !generalStake?.actualStake,\n }),\n );\n\nexport const selectLinearSlipErrorStateFactory = (slipId: SlipId, betslipType: BetslipType) =>\n createSelector(\n selectSlipErrorsFactory(slipId),\n selectLinearStakeForSlip(slipId, betslipType),\n // We should not have any slip errors displayed when we have a combo prevention error\n selectHasComboPreventionForTypeFactory(betslipType),\n (errors, stake, hasComboPrevention) => ({ errors: hasComboPrevention ? [] : errors, stake }),\n );\n\nconst selectDrawerGroupErrorFactory = (pickId: PickId) =>\n createSelector(selectGroupErrors, (typeErrors) => {\n return typeErrors\n .filter(isDrawerGroupError)\n .find((invalidPriceError) => invalidPriceError.invalidPickIds.some((invalidid) => invalidid.isEqual(pickId)));\n });\n\nconst selectGroupErrors = createSelector(selectCurrentBetslipTypeErrors, (typeErrors) => {\n return typeErrors.filter(isGroupPickError);\n});\n\nconst selectGroupOfferChangeErrorsFactory = (pickId: PickId) =>\n createSelector(selectAllPickErrorsForPickFactory(pickId), (pickErrors) => {\n return pickErrors.filter(isGroupPickOfferChange);\n });\n\nexport const selectRelevantGroupErrorsForPickFactory = (pickId: PickId) =>\n createSelector(selectDrawerGroupErrorFactory(pickId), selectGroupOfferChangeErrorsFactory(pickId), (groupPickError, groupOfferChange) => ({\n groupPickError,\n groupOfferChange,\n }));\n\nexport const selectAllDisplayablePickErrorsForTypeFactory = (type: BetslipType) =>\n createSelector(selectAllPickErrorsForTypeFactory(type), (pickErrors) => {\n return pickErrors.filter(isDisplayablePickError);\n });\n\nexport const selectTypeErrors = (type: BetslipType) =>\n createSelector(\n selectBetslipTypeErrorsFactory(type),\n selectSlipErrorsForTypeFactory(type),\n selectAllDisplayablePickErrorsForTypeFactory(type),\n (typeErrors, slipErrors, pickErrors) => {\n return [...typeErrors, ...slipErrors, ...pickErrors];\n },\n );\n\nexport const selectSystemContainerErrors = createSelector(\n selectBetslipTypeErrorsFactory(BetslipType.System),\n selectAllDisplayablePickErrorsForTypeFactory(BetslipType.System),\n (typeErrors, pickErrors) => {\n return [...typeErrors, ...pickErrors];\n },\n);\n\nexport const selectContainerErrors = (type: BetslipType) =>\n createSelector(selectIsLinearBetslip, selectSystemContainerErrors, selectTypeErrors(type), (isLinear, systemContainerErrors, typeErrors) => {\n return isLinear && type === BetslipType.System ? systemContainerErrors : typeErrors;\n });\n\nexport const selectComboContainerModuleErrors = createSelector(selectTypeErrors(BetslipType.Combo), (errors) => {\n return errors;\n});\nexport const selectCurrentSlipTokenErrors = createSelector(selectCurrentSlipErrors, (slipErrors) => slipErrors.filter(isRewardTokensError));\nexport const selectBetBuilderDrawerErrors = createSelector(selectCurrentBetslipTypeErrors, selectCurrentSlipTokenErrors, (errors, slipErrors) => [\n ...errors,\n ...slipErrors,\n]);\n\nexport const selectLinearGlobalIndiactorErrorsForPickFactory = (pickId: PickId) =>\n createSelector(selectPickErrorsState, selectIsSystemBetExpanded, (pickErrors, isSystemBetExpanded) => {\n const pickIdString = pickId.toString();\n\n const globalIndicatorErrors: BetslipTypePickErrors = {\n ...pickErrors,\n SYSTEM: isSystemBetExpanded ? pickErrors.SYSTEM : {},\n };\n\n return Object.values(globalIndicatorErrors).flatMap((errors) => errors[pickIdString] ?? []);\n });\n","import { BetslipType } from '../../../core/betslip-type';\nimport { PickId, pickIdFactory } from '../../../core/picks/pick-id';\nimport { IBetBuilderPickState } from '../../bet-builder/models';\nimport { IComboBetState } from '../../combo-bet/state';\nimport { ISingleBetPickState } from '../../single-bet/state';\nimport { ITeaserBetState } from '../../teaser-bet/state';\nimport { IBetslipTypeState } from '../../types/state';\nimport { AccaBoostToken, IRewardToken, RewardTokenContext, SelectedRewardTokenState } from '../reward-tokens.model';\nimport { IRewardTokenEligibility, IRewardTokenEligibilityState, TokensRecord } from '../state';\nimport { byAccaPriority, isAccaBoostToken } from './reward-tokens.utils';\n\nexport type RewardTokenAwareState = IComboBetState | ISingleBetPickState | ITeaserBetState | IBetBuilderPickState;\nexport type TokenAwarePicksState = Record;\n\nexport function getSelectedToken(tokens: TokensRecord, type: RewardTokenAwareState): IRewardToken | null {\n if (!type?.rewardTokenId) {\n return null;\n }\n\n return tokens[type.rewardTokenId] ?? null;\n}\n\nexport function getSelectedTokenForContext(context: RewardTokenContext, tokens: TokensRecord, types: IBetslipTypeState): IRewardToken | null {\n switch (context.betslipType) {\n case BetslipType.Single:\n return getSelectedTokenForPick(context.pickId, tokens, types.singleBet.picks);\n case BetslipType.Combo:\n return getSelectedToken(tokens, types.comboBet);\n case BetslipType.Teaser:\n return getSelectedToken(tokens, types.teaserBet);\n case BetslipType.BetBuilder:\n return getSelectedTokenForPick(context.pickId, tokens, types.betBuilder.picks);\n default:\n return null;\n }\n}\n\nexport function getSelectedTokenAndEligibilityForContext(\n context: RewardTokenContext,\n eligibilityState: IRewardTokenEligibilityState,\n tokens: TokensRecord,\n types: IBetslipTypeState,\n): [IRewardToken | null, IRewardTokenEligibility | null] {\n switch (context.betslipType) {\n case BetslipType.Single: {\n return getSelectedTokenAndEligibilityForPick(context.pickId, tokens, types.singleBet.picks, eligibilityState.singleBet);\n }\n case BetslipType.Combo: {\n return getSelectedTokenAndEligibility(tokens, types.comboBet, eligibilityState.comboBet);\n }\n case BetslipType.Teaser: {\n return getSelectedTokenAndEligibility(tokens, types.teaserBet, eligibilityState.teaserBet);\n }\n case BetslipType.BetBuilder: {\n return getSelectedTokenAndEligibilityForPick(context.pickId, tokens, types.betBuilder.picks, eligibilityState.betBuilder);\n }\n default:\n return [null, null];\n }\n}\n\nexport function getAllAssignedTokens(tokens: TokensRecord, types: IBetslipTypeState): IRewardToken[] {\n return getAllAssignedTokensWithFilter(tokens, types, false);\n}\n\nexport function getAllSelectedTokens(tokens: TokensRecord, types: IBetslipTypeState): IRewardToken[] {\n return getAllAssignedTokensWithFilter(tokens, types, true);\n}\n\nexport function getTokenSelectionContext(tokenId: string, types: IBetslipTypeState): RewardTokenContext | undefined {\n if (types.comboBet.rewardTokenId === tokenId) {\n return {\n betslipType: BetslipType.Combo,\n };\n }\n\n if (types.teaserBet.rewardTokenId === tokenId) {\n return {\n betslipType: BetslipType.Teaser,\n };\n }\n\n const selectedSinglePickId = getPickIdBySelectedToken(tokenId, types.singleBet.picks);\n\n if (selectedSinglePickId) {\n return {\n pickId: selectedSinglePickId,\n betslipType: BetslipType.Single,\n };\n }\n\n const selectedBetBuilderPickId = getPickIdBySelectedToken(tokenId, types.betBuilder.picks);\n\n if (selectedBetBuilderPickId) {\n return {\n pickId: selectedBetBuilderPickId,\n betslipType: BetslipType.BetBuilder,\n };\n }\n\n return undefined;\n}\n\nexport function getSelectedTokenState(\n rewardTokenId: string,\n tokens: Record,\n eligibilityState: IRewardTokenEligibility[],\n): SelectedRewardTokenState {\n const token = tokens[rewardTokenId] ?? null;\n const tokenEligibility = (eligibilityState ?? []).find((e) => rewardTokenId && e.tokenId === rewardTokenId) ?? null;\n\n return {\n tokenId: rewardTokenId,\n token,\n tokenEligibility,\n };\n}\n\nexport function getEligibilityStateForContext(\n context: RewardTokenContext,\n eligibilityState: IRewardTokenEligibilityState,\n): IRewardTokenEligibility[] {\n switch (context.betslipType) {\n case BetslipType.Single: {\n if (!context.pickId) {\n return [];\n }\n\n return eligibilityState.singleBet[context.pickId.toString()] ?? [];\n }\n case BetslipType.Combo:\n return eligibilityState.comboBet;\n case BetslipType.Teaser:\n return eligibilityState.teaserBet;\n case BetslipType.BetBuilder: {\n if (!context.pickId) {\n return [];\n }\n\n return eligibilityState.betBuilder[context.pickId.toString()] ?? [];\n }\n default:\n return [];\n }\n}\n\nexport function getSelectedAndEligibleAccaBoost(\n eligibilityState: IRewardTokenEligibilityState,\n tokens: TokensRecord,\n types: IBetslipTypeState,\n): AccaBoostToken | null {\n // Acca Boost token can only be applied to a combo bet\n const selectedToken = getSelectedTokenForContext({ betslipType: BetslipType.Combo }, tokens, types);\n\n if (!isAccaBoostToken(selectedToken)) {\n return null;\n }\n\n const isEligible = eligibilityState.comboBet.find((e) => e.tokenId === selectedToken.userTokenId)?.isEligible;\n\n return isEligible ? selectedToken : null;\n}\n\nexport function getEligibleAccaBoost(eligibilityState: IRewardTokenEligibilityState, tokens: TokensRecord): AccaBoostToken | null {\n const allEligibleTokens = eligibilityState.comboBet.filter((e) => e.isEligible).map((e) => tokens[e.tokenId]);\n\n return allEligibleTokens.filter(isAccaBoostToken).sort(byAccaPriority)[0] ?? null;\n}\n\nexport function isTokenRecoverable(tokenId: string, tokens: Record, eligibilityState: IRewardTokenEligibility[]): boolean {\n const token = tokens[tokenId];\n\n if (!token) {\n return false;\n }\n\n const eligibility = eligibilityState.find((e) => e.tokenId === tokenId);\n\n return !!eligibility && eligibility.isEligible;\n}\n\n// Helper functions to avoid code duplication\n\nfunction getSelectedTokenForPick(pickId: PickId | undefined, tokens: TokensRecord, awareStates: TokenAwarePicksState): IRewardToken | null {\n if (!pickId) {\n return null;\n }\n const pickState = awareStates[pickId.toString()];\n\n return getSelectedToken(tokens, pickState);\n}\n\nfunction getSelectedTokenAndEligibilityForPick(\n pickId: PickId | undefined,\n tokens: TokensRecord,\n awareStates: TokenAwarePicksState,\n eligibilityStates: Record,\n): [IRewardToken | null, IRewardTokenEligibility | null] {\n if (!pickId) {\n return [null, null];\n }\n\n const pickState = awareStates[pickId.toString()];\n const eligibilities = eligibilityStates[pickId.toString()] ?? [];\n\n return getSelectedTokenAndEligibility(tokens, pickState, eligibilities);\n}\n\nfunction getSelectedTokenAndEligibility(\n tokens: TokensRecord,\n awareState: RewardTokenAwareState,\n eligibilities: IRewardTokenEligibility[],\n): [IRewardToken | null, IRewardTokenEligibility | null] {\n const token = getSelectedToken(tokens, awareState);\n const tokenEligibility = token ? (eligibilities.find((e) => e.tokenId === token.userTokenId) ?? null) : null;\n\n return [token, tokenEligibility];\n}\n\nfunction getAllAssignedTokensWithFilter(tokens: TokensRecord, types: IBetslipTypeState, filterUnselectedPicks: boolean = false): IRewardToken[] {\n const singlePicks = Object.values(types.singleBet.picks);\n const betBuilderPicks = Object.values(types.betBuilder.picks);\n\n const picks = [...singlePicks, ...betBuilderPicks];\n\n const filteredPicks = filterUnselectedPicks ? picks.filter((pick) => pick.isSelected) : picks;\n\n const pickTokenIds = filteredPicks.map((pick) => pick.rewardTokenId);\n\n const tokenIds = [...pickTokenIds, types.comboBet.rewardTokenId, types.teaserBet.rewardTokenId].filter((id) => !!id) as string[];\n\n return tokenIds.map((id) => tokens[id]).filter((token) => !!token);\n}\n\nfunction getPickIdBySelectedToken(selectedTokenId: string, awarePicksState: TokenAwarePicksState): PickId | undefined {\n const pick = Object.entries(awarePicksState).find(([, value]) => value.rewardTokenId === selectedTokenId);\n\n return pick ? pickIdFactory(pick[0]) : undefined;\n}\n","import { Nullable, Optional, isDefined } from '@frontend/sports/common/core/utils/extended-types';\nimport { BestOddsFixtureLikeInput } from 'packages/sports/web/app/src/best-odds-guarantee/models/models';\nimport { IRewardTokenData, RewardTokenType } from 'packages/sports/web/app/src/tokens-base/token-base.models';\n\nimport { ParticipantPickType } from '../../../core/picks/pick-models';\nimport {\n BetslipV2HorseRaceOptionMarketPick,\n BetslipV2HorseRaceWinParticipantPick,\n BetslipV2HorseRaceXCastPick,\n} from '../../../core/picks/sport-specific/betslip-v2-horse-race-picks';\nimport { IBetslipTypeState } from '../../types/state';\nimport {\n BestOddsGuaranteedToken,\n BetStationFreeBetToken,\n EdsToken,\n EdsTokenWithEligiblePicks,\n IRewardToken,\n RewardTokenContext,\n} from '../reward-tokens.model';\nimport { IBetstationTokenState, IBetstationTokensState, IEdsTokenState, IRewardTokensState, TokensRecord } from '../state';\nimport { getSelectedTokenForContext } from './linear-reward-tokens.utils';\nimport { convertToBestOddsFixtureLikeInput } from './reward-tokens.utils';\nimport { TokenValidationInput } from './validators/criteria-validator-base.service';\n\nexport function isBetStationFreebetToken(token: IRewardToken | IRewardTokenData | null): token is BetStationFreeBetToken {\n return !!token && token.rewardTokenType === RewardTokenType.BetStationFreebet;\n}\n\nexport function isBestOddsGuaranteedToken(token: IRewardToken | IRewardTokenData | null): token is BestOddsGuaranteedToken {\n return !!token && token.rewardTokenType === RewardTokenType.BestOddsGuaranteed;\n}\n\nexport function isEdsToken(token: Optional): token is EdsToken {\n return !!token && token.rewardTokenType === RewardTokenType.Eds;\n}\n\nexport function getBestOddsGuaranteedTokensContext(state: IRewardTokensState): { valid: boolean; token: BestOddsGuaranteedToken | undefined } {\n //It is guaranteed in SPC service that only one BOG token will be returned\n const token = Object.values(state.tokens).find(isBestOddsGuaranteedToken);\n\n const eligibility = state.betstationTokens.bogTokenState.eligibility;\n\n return {\n valid: !!eligibility?.isEligible,\n token,\n };\n}\n\nexport function toBestOddsLikeFixtures(context: TokenValidationInput): Nullable[] {\n const picks = context.allPicks;\n\n return picks.map((pick) => {\n if (\n (BetslipV2HorseRaceOptionMarketPick.isPick(pick) &&\n pick.pickType === ParticipantPickType.Win &&\n (pick.isStartingPriceAvailable() || pick.isFixedPriceAvailable())) ||\n BetslipV2HorseRaceWinParticipantPick.isPick(pick) ||\n BetslipV2HorseRaceXCastPick.isPick(pick)\n ) {\n const fixture = convertToBestOddsFixtureLikeInput(pick);\n\n return { ...fixture, pickId: pick.id.toString() };\n } else {\n return null;\n }\n });\n}\n\nexport function getSelectedBogToken(bogTokenState: IBetstationTokenState, tokens: Record): BestOddsGuaranteedToken | null {\n const token = bogTokenState.selectedTokenId ? tokens[bogTokenState.selectedTokenId] : null;\n\n return isBestOddsGuaranteedToken(token) ? token : null;\n}\n\nexport function getSelectedBetStationFreeBetToken(\n extendedTokens: IBetstationTokensState,\n tokens: Record,\n): BetStationFreeBetToken | null {\n const tokenId = extendedTokens.betStationFreeBet.selectedTokenId;\n\n if (!tokenId) {\n return null;\n }\n\n const token = tokens[tokenId];\n\n return isBetStationFreebetToken(token) ? token : null;\n}\n\nexport function getSelectedBetStationTokenForContext(\n context: RewardTokenContext,\n types: IBetslipTypeState,\n betstationTokensState: IBetstationTokensState,\n tokens: TokensRecord,\n): IRewardToken | null {\n const tokenId = betstationTokensState.betStationFreeBet.selectedTokenId;\n\n if (tokenId) {\n const token = tokens[tokenId];\n\n return isBetStationFreebetToken(token) ? token : null;\n }\n\n return getSelectedTokenForContext(context, tokens, types);\n}\n\nexport function getSelectedEdsTokensWithEligiblePicksIds(state: IEdsTokenState, tokens: Record): EdsTokenWithEligiblePicks[] {\n return state.selectedTokenIds\n ?.map((id) => {\n const token = tokens[id];\n\n return isEdsToken(token)\n ? {\n token,\n eligiblePicksIds: state.eligibilities?.find((e) => e.tokenId === id)?.eligiblePicksIds || [],\n }\n : undefined;\n })\n .filter(isDefined);\n}\n","import { RewardTargetType } from '@bpos/v1/sports-promo/tokens';\nimport { createSelector } from '@ngrx/store';\n\nimport { betslipSelector, selectIsLinearBetslip } from '../../base/store/selectors';\nimport { BetslipType } from '../../core/betslip-type';\nimport { BetslipV2OptionMarketPick } from '../../core/picks/betslip-v2-option-market-pick';\nimport { PickId, pickIdFactory } from '../../core/picks/pick-id';\nimport { betslipPicksListSelector } from '../picks/selectors';\nimport {\n selectSingleBetSinglePickId,\n singleBetPickSelectorFactory,\n singleBetPicksSelector,\n singleBetSelectedPicksSelector,\n} from '../single-bet/selectors';\nimport { betslipCurrentTypeSelector } from '../types/base/selectors';\nimport { betslipTypeStateSelector } from '../types/selectors';\nimport { selectHasComboPreventionForTypeFactory, selectHasMinSelectionsBetBuilderError } from '../validation/selectors';\nimport { RewardTokenContext } from './reward-tokens.model';\nimport {\n getBestOddsGuaranteedTokensContext,\n getSelectedBetStationFreeBetToken,\n getSelectedBogToken,\n} from './services/betstation-reward-tokens.utils';\nimport {\n getAllAssignedTokens,\n getAllSelectedTokens,\n getEligibilityStateForContext,\n getSelectedTokenAndEligibilityForContext,\n getSelectedTokenForContext,\n getTokenSelectionContext,\n} from './services/linear-reward-tokens.utils';\nimport {\n getAcquisitionRewardToken,\n getAllEligibleTokens,\n getNonAccaTokens,\n isAcquisitionEligible,\n isFreebetToken,\n isOddsBoostToken,\n} from './services/reward-tokens.utils';\n\nexport const rewardTokensStateSelector = createSelector(betslipSelector, (state) => state.rewardTokens);\nexport const rewardTokensOnBoardingSelector = createSelector(rewardTokensStateSelector, (state) => state.onboardingState);\n\nexport const selectRewardTokens = createSelector(rewardTokensStateSelector, (state) => state.tokens);\nexport const selectRewardTokensList = createSelector(selectRewardTokens, (tokens) => Object.values(tokens));\n\nexport const selectTokensEligibilityState = createSelector(rewardTokensStateSelector, (state) => state.eligibilityState);\n\nexport const selectHasNewCustomerOfferPick = createSelector(betslipPicksListSelector, (pickList) =>\n pickList.some((pick) => pick.isNewCustomerOfferPick),\n);\n\nexport const selectTokensStateContext = createSelector(\n selectTokensEligibilityState,\n selectRewardTokens,\n betslipTypeStateSelector,\n (eligibilityState, tokens, types) => ({\n eligibilityState,\n tokens,\n types,\n }),\n);\n\nexport const selectAllEligibleTokens = createSelector(selectTokensEligibilityState, selectRewardTokens, (eligibilityState, tokens) =>\n getAllEligibleTokens(eligibilityState, tokens),\n);\n\nexport const selectNonAccaTokens = createSelector(selectRewardTokensList, (tokens) => {\n return getNonAccaTokens(tokens);\n});\n\nexport const selectTokenForContextFactory = (context: RewardTokenContext) => {\n return createSelector(selectTokensStateContext, ({ tokens, types }) => getSelectedTokenForContext(context, tokens, types));\n};\n\nexport const selectTokenAndEligibilityFactory = (context: RewardTokenContext) => {\n return createSelector(selectTokensStateContext, ({ tokens, eligibilityState, types }) =>\n getSelectedTokenAndEligibilityForContext(context, eligibilityState, tokens, types),\n );\n};\n\nexport const selectEligibleTokenFactory = (context: RewardTokenContext) => {\n return createSelector(selectTokenAndEligibilityFactory(context), ([token, eligibility]) => (eligibility?.isEligible ? token : null));\n};\n\nexport const selectFreeBetTokenFactory = (context: RewardTokenContext) => {\n return createSelector(selectEligibleTokenFactory(context), (token) => (isFreebetToken(token) ? token : null));\n};\n\nexport const selectOddsBoostTokenFactory = (context: RewardTokenContext) => {\n return createSelector(selectEligibleTokenFactory(context), (token) => (isOddsBoostToken(token) ? token : null));\n};\n\nexport const selectAllSelectedTokens = createSelector(selectTokensStateContext, ({ tokens, types }) => getAllSelectedTokens(tokens, types));\n\nexport const selectAllAssignedTokens = createSelector(selectTokensStateContext, ({ tokens, types }) => getAllAssignedTokens(tokens, types));\n\nexport const selectEligibilityStateFactory = (context: RewardTokenContext) => {\n return createSelector(selectTokensEligibilityState, (eligibilityState) => getEligibilityStateForContext(context, eligibilityState));\n};\n\nexport const selectAllTokensAndEligibilityStateFactory = (context: RewardTokenContext) => {\n return createSelector(selectRewardTokens, selectEligibilityStateFactory(context), (tokens, eligibilityState) => ({\n tokens,\n eligibilityState,\n }));\n};\n\nexport const selectSelectedTokenForContext = (context: RewardTokenContext) => {\n return createSelector(selectRewardTokens, betslipTypeStateSelector, (tokens, types) => {\n return getSelectedTokenForContext(context, tokens, types);\n });\n};\n\nexport const selectHasVisibleTokensForContext = (context: RewardTokenContext) => {\n return createSelector(\n selectAllSelectedTokens,\n selectTokenForContextFactory(context),\n selectEligibilityStateFactory(context),\n (allSelectedTokens, selectedToken, eligibilityState) => {\n return eligibilityState.some((eligibility) => {\n const currentSelected = selectedToken?.userTokenId === eligibility.tokenId;\n\n // this makes sure that already selected tokens in a different slip don't get shown\n return currentSelected || (eligibility.isEligible && allSelectedTokens.every((token) => token.userTokenId !== eligibility.tokenId));\n });\n },\n );\n};\n\nexport const selectUnselectedSingleBetPickForAcquisitionToken = createSelector(\n singleBetPicksSelector,\n selectRewardTokensList,\n betslipCurrentTypeSelector,\n selectIsLinearBetslip,\n (singleBetPicks, tokens, currentSelectedType, isLinear) => {\n const acquisitionRewardToken = getAcquisitionRewardToken(tokens);\n\n if (!acquisitionRewardToken || (!isLinear && currentSelectedType !== BetslipType.Single)) {\n return;\n }\n\n const unselectedPicks = Object.entries(singleBetPicks)\n .filter(([, pick]) => !pick.isSelected && pick.rewardTokenId === acquisitionRewardToken.userTokenId)\n .map(([k]) => k);\n\n return unselectedPicks.length > 0 ? { pickId: unselectedPicks[0] } : undefined;\n },\n);\n\nexport const selectSingleBetPickForAcquisitionToken = createSelector(\n singleBetSelectedPicksSelector,\n selectRewardTokensList,\n selectTokensEligibilityState,\n betslipCurrentTypeSelector,\n selectIsLinearBetslip,\n selectAllSelectedTokens,\n selectHasMinSelectionsBetBuilderError,\n (selectedPicks, tokens, eligibilityState, currentSelectedType, isLinear, allSelectedTokens, hasMinSelectionsBetBuilderError) => {\n const acquisitionRewardToken = getAcquisitionRewardToken(tokens);\n const isAcquisitionTokenSelected = allSelectedTokens.some((x) => x.rewardTargetType === RewardTargetType.WelcomeOffer);\n\n if (\n !acquisitionRewardToken ||\n (!isLinear && currentSelectedType !== BetslipType.Single) ||\n isAcquisitionTokenSelected ||\n hasMinSelectionsBetBuilderError\n ) {\n return;\n }\n\n const acquisitionEligiblePicks = Object.entries(eligibilityState.singleBet)\n .filter(\n ([pickId, eligibilityTokens]) =>\n selectedPicks[pickId] &&\n !selectedPicks[pickId].rewardTokenId &&\n eligibilityTokens.some((x) => isAcquisitionEligible(x, acquisitionRewardToken.userTokenId)),\n )\n .map(([k]) => k);\n\n return acquisitionEligiblePicks.length > 0 ? { pickId: acquisitionEligiblePicks[0], tokenId: acquisitionRewardToken.userTokenId } : undefined;\n },\n);\n\nexport const selectSingleBetStandardRewardTokensContext = createSelector(singleBetPicksSelector, (singleBetPicks) => {\n const selectedPicks = Object.entries(singleBetPicks)\n .filter(([, p]) => p.isSelected)\n .map(([k]) => k);\n\n return selectedPicks.length > 0\n ? {\n betslipType: BetslipType.Single,\n pickId: pickIdFactory(selectedPicks[0]),\n }\n : undefined;\n});\n\nexport const selectHasEachWayOptionPick = createSelector(\n betslipPicksListSelector,\n betslipTypeStateSelector,\n (pickList, { base, singleBet, comboBet, systemBet }) => {\n if (!pickList.some(BetslipV2OptionMarketPick.isPick)) {\n return false;\n }\n\n switch (base.currentSelectedType) {\n case BetslipType.Single:\n return pickList.filter(BetslipV2OptionMarketPick.isPick).some((pick) => {\n const pickState = singleBet.picks[pick.id.toString()];\n\n return !!pickState && pickState.isEachWay && pickState.isSelected;\n });\n\n case BetslipType.Combo:\n return comboBet.isEachWay;\n case BetslipType.System:\n return systemBet.isEachWay;\n default:\n return false;\n }\n },\n);\n\nexport const selectRewardTokensOnboardingContext = createSelector(\n selectIsLinearBetslip,\n selectSingleBetStandardRewardTokensContext,\n betslipCurrentTypeSelector,\n (isLinear, singleBetRewardTokensContext, currentType) => {\n if (isLinear) {\n return singleBetRewardTokensContext;\n }\n\n switch (currentType) {\n case BetslipType.Combo:\n return {\n betslipType: BetslipType.Combo,\n };\n\n case BetslipType.Single:\n return singleBetRewardTokensContext;\n\n case BetslipType.Teaser:\n return {\n betslipType: BetslipType.Teaser,\n };\n\n default:\n return undefined;\n }\n },\n);\n\nexport const selectRewardTokensOnboardingEligibility = createSelector(\n selectTokensEligibilityState,\n selectRewardTokensOnboardingContext,\n (eligibility, context) => {\n return context ? getEligibilityStateForContext(context, eligibility) : [];\n },\n);\n\nexport const selectRewardTokensOnboardingEligibileTokens = createSelector(\n selectRewardTokensOnboardingEligibility,\n selectRewardTokens,\n (eligibility, tokens) => {\n return eligibility.filter((e) => e.isEligible).map((e) => tokens[e.tokenId]);\n },\n);\n\nexport const selectAcquisitionRewardToken = createSelector(selectRewardTokensList, (tokens) => getAcquisitionRewardToken(tokens));\nexport const selectRewardTokensOnboardingRewardSelectorVisible = createSelector(\n selectHasEachWayOptionPick,\n selectHasNewCustomerOfferPick,\n selectRewardTokensOnboardingEligibileTokens,\n (hasEachWayOption, hasNewCustomerOffer, tokens) => !hasEachWayOption && !hasNewCustomerOffer && tokens.length > 0,\n);\n\nexport const selectRewardTokensOnboardingDisplayState = createSelector(\n rewardTokensOnBoardingSelector,\n selectRewardTokensOnboardingEligibileTokens,\n selectRewardTokensOnboardingRewardSelectorVisible,\n selectAcquisitionRewardToken,\n (onboarding, onboardingTokens, rewardsSelectorVisible, acquisitionRewardToken) => {\n return { isVisible: onboarding.visible && rewardsSelectorVisible && !acquisitionRewardToken, tokens: onboardingTokens };\n },\n);\n\nexport const selectAcquisitionRewardOnboardingDisplayState = createSelector(\n rewardTokensOnBoardingSelector,\n selectRewardTokensOnboardingRewardSelectorVisible,\n (onboarding, rewardsSelectorVisible) => ({\n acquisitionHidden: onboarding.acquisitionHidden && rewardsSelectorVisible,\n }),\n);\n\nexport const selectHasAcquisitionRewardOnboardingForContext = (context: RewardTokenContext) => {\n return createSelector(selectSelectedTokenForContext(context), selectAcquisitionRewardOnboardingDisplayState, (selectedToken, displayState) => {\n return selectedToken?.rewardTargetType === RewardTargetType.WelcomeOffer && !displayState.acquisitionHidden;\n });\n};\n\nexport const selectBetstationTokensState = createSelector(rewardTokensStateSelector, (state) => state.betstationTokens);\nexport const selectEdsTokensState = createSelector(selectBetstationTokensState, (state) => state.edsState);\nexport const selectSelectedEdsTokens = createSelector(selectEdsTokensState, (state) => state.selectedTokenIds);\nexport const selectBogTokensState = createSelector(selectBetstationTokensState, (state) => state.bogTokenState);\n\nexport const selectSelectedBogToken = createSelector(selectBogTokensState, selectRewardTokens, (bogState, tokens) =>\n getSelectedBogToken(bogState, tokens),\n);\n\nexport const selectHasBogTokenSelected = createSelector(selectBogTokensState, (bogState) => !!bogState.selectedTokenId);\n\nexport const selectBogTokenContext = createSelector(rewardTokensStateSelector, (state) => getBestOddsGuaranteedTokensContext(state));\n\nexport const selectHasBetstationFreeBetSelected = createSelector(\n selectBetstationTokensState,\n (extended) => !!extended.betStationFreeBet.selectedTokenId,\n);\n\nexport const selectHasEdsTokenSelected = createSelector(selectEdsTokensState, (edsState) => edsState.selectedTokenIds.length > 0);\n\nexport const selectSelectedBetstationFreeBetToken = createSelector(selectBetstationTokensState, selectRewardTokens, (betstationState, tokens) =>\n getSelectedBetStationFreeBetToken(betstationState, tokens),\n);\n\nexport const selectSingleBetPickRewardTokenFactory = (pickId: PickId) =>\n createSelector(singleBetPickSelectorFactory(pickId), selectRewardTokens, (pick, tokens) => {\n if (!pick) {\n return null;\n }\n\n return pick.rewardTokenId ? tokens[pick.rewardTokenId] : null;\n });\n\nexport const selectSelectedFreeBetTokenForContextFactory = (context: RewardTokenContext) =>\n createSelector(selectTokenForContextFactory(context), (token) => {\n return isFreebetToken(token) ? token : null;\n });\n\nexport const selectHasComboPreventionForContextFactory = (context: RewardTokenContext) => {\n return createSelector(\n selectHasComboPreventionForTypeFactory(context.betslipType!),\n (hasComboPrevention) => hasComboPrevention && context.betslipType !== BetslipType.Single,\n );\n};\n\nexport const selectMultiSingleTokenRewardContext = createSelector(betslipCurrentTypeSelector, singleBetPicksSelector, (type, picks) => {\n if (type !== BetslipType.Single) {\n return undefined;\n }\n\n const selectedPicks = Object.entries(picks).filter(([_, pick]) => pick.isSelected);\n\n const picksWithTokens = selectedPicks.filter(([, pick]) => !!pick.rewardTokenId);\n const singleTokenSelected = picksWithTokens.length === 1;\n\n return singleTokenSelected\n ? {\n betslipType: BetslipType.Single,\n pickId: pickIdFactory(picksWithTokens[0][0]),\n }\n : undefined;\n});\n\nexport const selectRecoverableTokens = createSelector(rewardTokensStateSelector, (tokens) => tokens.tokenRecoveryState);\n\nexport const selectCurrentEligibilityState = createSelector(\n selectTokensEligibilityState,\n betslipCurrentTypeSelector,\n selectSingleBetSinglePickId,\n (eligibilities, currentType, singleBetSinglePickId) => {\n switch (currentType) {\n case BetslipType.Single:\n if (!singleBetSinglePickId) {\n return [];\n }\n\n return eligibilities.singleBet[singleBetSinglePickId] ?? [];\n\n case BetslipType.Combo:\n return eligibilities.comboBet;\n\n case BetslipType.Teaser:\n return eligibilities.teaserBet;\n\n default:\n return [];\n }\n },\n);\n\nexport const selectAcquisitionSelectionTokenContext = createSelector(betslipTypeStateSelector, selectRewardTokensList, (types, tokens) => {\n const acquisitionRewardToken = getAcquisitionRewardToken(tokens);\n if (!acquisitionRewardToken) {\n return;\n }\n const acquisitionSelectionTokenContext = getTokenSelectionContext(acquisitionRewardToken.userTokenId, types);\n\n return { userTokenId: acquisitionRewardToken.userTokenId, ...acquisitionSelectionTokenContext };\n});\n\nexport const selectShowRewardsNotEligibleMessage = createSelector(\n selectIsLinearBetslip,\n betslipCurrentTypeSelector,\n selectNonAccaTokens,\n (isLinear, betslipCurrentType, tokens) => !isLinear && tokens.length > 0 && betslipCurrentType === BetslipType.System,\n);\n\nexport const selectHasEachWayOptionPickForContext = (context: RewardTokenContext) => {\n return createSelector(betslipPicksListSelector, betslipTypeStateSelector, (pickList, { singleBet, comboBet, systemBet }) => {\n if (!pickList.some(BetslipV2OptionMarketPick.isPick)) {\n return false;\n }\n\n switch (context.betslipType) {\n case BetslipType.Single:\n const pickId = context.pickId?.toString();\n\n if (!pickId) {\n return false;\n }\n\n const pick = pickList.find((p) => p.id.toString() === pickId);\n\n const pickState = singleBet.picks[pickId];\n\n if (!pick || !pickState) {\n return false;\n }\n\n return pick && pickState && BetslipV2OptionMarketPick.isPick(pick) && pickState.isEachWay && pickState.isSelected;\n case BetslipType.Combo:\n return comboBet.isEachWay;\n case BetslipType.System:\n return systemBet.isEachWay;\n default:\n return false;\n }\n });\n};\n\nexport const selectIsRewardTokensSelectorVisible = (context: RewardTokenContext) => {\n return createSelector(\n selectHasEachWayOptionPickForContext(context),\n selectHasNewCustomerOfferPick,\n selectHasVisibleTokensForContext(context),\n selectHasComboPreventionForContextFactory(context),\n (eachWayOptionPick, newCustomerOffer, hasVisibleTokens, hasComboPrevention) => {\n return hasVisibleTokens && !newCustomerOffer && !eachWayOptionPick && !hasComboPrevention;\n },\n );\n};\n\nexport const selectRewardsTokensSelectorState = (context: RewardTokenContext) =>\n createSelector(\n selectIsRewardTokensSelectorVisible(context),\n selectTokenAndEligibilityFactory(context),\n selectHasAcquisitionRewardOnboardingForContext(context),\n (isVisible, [selectedToken, eligibility], hasAcquisitionOnboarding) => ({\n isVisible,\n selectedToken,\n isTokenInvalid:\n !eligibility || !!eligibility.invalidHardCriteria || !!Object.values(eligibility.softCriteriasValidity).some((x) => x === false),\n hasAcquisitionOnboarding,\n }),\n );\n","import { createSelector } from '@ngrx/store';\n\nimport { BetslipType } from '../../../../common/betslip/core/betslip-type';\nimport { selectTokensStateContext } from '../../../../common/betslip/modules/reward-tokens/selectors';\nimport { getSelectedAndEligibleAccaBoost } from '../../../../common/betslip/modules/reward-tokens/services/linear-reward-tokens.utils';\nimport { betslipCurrentTypeSelector } from '../../../../common/betslip/modules/types/base/selectors';\n\nexport const accaBoostTokenSelector = createSelector(selectTokensStateContext, ({ eligibilityState, tokens, types }) => {\n return getSelectedAndEligibleAccaBoost(eligibilityState, tokens, types);\n});\n\nexport const accaBoostTokenForCurrentTypeSelector = createSelector(accaBoostTokenSelector, betslipCurrentTypeSelector, (accaBoost, type) => {\n return type && type === BetslipType.Combo ? accaBoost : null;\n});\n\nexport const isAccaBoostTokenSelector = createSelector(accaBoostTokenSelector, (token) => !!token);\n","import { Injectable, inject } from '@angular/core';\n\nimport { BetslipBarConfig } from '@frontend/sports/common/client-config-data-access';\nimport { RouteTag } from '@frontend/sports/common/core/data-access/route';\nimport { RouterEventsService } from '@frontend/sports/common/core/utils/router-events';\nimport { SportsProductService } from '@frontend/sports/host-app/sports-product/feature/sports-product-service';\nimport { DslService, HtmlNode } from '@frontend/vanilla/core';\nimport { Store } from '@ngrx/store';\nimport { BehaviorSubject, combineLatest, map, startWith, tap } from 'rxjs';\n\nimport { betslipPicksListCountSelector } from '../../picks/selectors';\n\n@Injectable({ providedIn: 'root' })\nexport class BetslipBarVisibilityService {\n private readonly visibilitySubject = new BehaviorSubject(true);\n private readonly isSportsActive$ = inject(SportsProductService).isSportsProductActive$;\n\n private readonly BETSLIP_BAR_VISIBLE_CLASS = 'betslip-bar-visible';\n\n private readonly htmlNodeService = inject(HtmlNode);\n private readonly betslipBarConfig = inject(BetslipBarConfig);\n\n private readonly hasPicks$ = inject(Store)\n .select(betslipPicksListCountSelector)\n .pipe(\n startWith(0),\n map((count) => count > 0),\n );\n\n private readonly excludedRoutes$ = inject(RouterEventsService).currentActivationEnd.pipe(\n map((route) => route?.snapshot.data?.tag === RouteTag.MyBets),\n );\n\n readonly enableDsl$ = inject(DslService).evaluateExpression(this.betslipBarConfig.isEnabledDsl);\n\n readonly componentVisible$ = combineLatest([\n this.visibilitySubject,\n this.enableDsl$,\n this.excludedRoutes$,\n this.hasPicks$,\n this.isSportsActive$,\n ]).pipe(\n map(\n ([isVisible, enabled, onExcludedRoute, hasPicks, isSportsActive]) =>\n isSportsActive && enabled && isVisible && !onExcludedRoute && (!this.betslipBarConfig.hideOnEmptyBetslip || hasPicks),\n ),\n tap((visible) => {\n this.htmlNodeService.setCssClass(this.BETSLIP_BAR_VISIBLE_CLASS, visible);\n }),\n );\n\n hideComponent(): void {\n this.visibilitySubject.next(false);\n }\n\n showComponent(): void {\n this.visibilitySubject.next(true);\n }\n}\n","import { createSelector } from '@ngrx/store';\n\nimport { betslipSelector } from '../../base/store/selectors';\n\nexport const hiddenMarketStateSelector = createSelector(betslipSelector, (state) => state.hiddenMarket);\nexport const hasPlacedFirstBetSelector = createSelector(hiddenMarketStateSelector, (state) => state.hasPlacedFirstBet);\n","import { MessageEnvelope } from '@cds/push';\nimport { BetslipDisplayMode } from '@frontend/sports/types/components/bet-placement';\nimport { createAction, props } from '@ngrx/store';\n\nimport { BetslipState } from '../../core/betslip-state';\n\nexport default class BetslipActions {\n static readonly ACTION_SCHEMA = '@betslip';\n\n private static readonly SET_STATE = `${BetslipActions.ACTION_SCHEMA}/SET_STATE`;\n private static readonly EVENT_UPDATE = `${BetslipActions.ACTION_SCHEMA}/EVENT_UPDATE`;\n private static readonly FIXTURE_UPDATE = `${BetslipActions.ACTION_SCHEMA}/FIXTURE_UPDATE`;\n private static readonly TOGGLE_BETSLIP_DISPLAY_MODE = `${BetslipActions.ACTION_SCHEMA}/TOGGLE_BETSLIP_DISPLAY_MODE`;\n\n static setState = createAction(BetslipActions.SET_STATE, props<{ state: BetslipState }>());\n static eventUpdate = createAction(BetslipActions.EVENT_UPDATE, props<{ message: MessageEnvelope }>());\n static fixtureUpdate = createAction(BetslipActions.FIXTURE_UPDATE, props<{ message: MessageEnvelope }>());\n static betBuilderGroupUpdate = createAction(`${BetslipActions.ACTION_SCHEMA}/BETBUILDER_GROUP_UPDATE`, props<{ message: MessageEnvelope }>());\n static toggleBetslipDisplayMode = createAction(BetslipActions.TOGGLE_BETSLIP_DISPLAY_MODE, props<{ mode: BetslipDisplayMode }>());\n}\n","import { OddsAcceptanceMode } from '@bpos';\nimport { createAction, props } from '@ngrx/store';\n\nimport BetslipActions from '../../base/store/actions';\n\nexport default class SettingsActions {\n static readonly ACTION_SCHEMA = `${BetslipActions.ACTION_SCHEMA}/settings`;\n\n private static readonly REQUEST_SET_ODD_ACCEPTANCE = `${SettingsActions.ACTION_SCHEMA}/REQUEST_SET_ODD_ACCEPTANCE`;\n private static readonly SET_ODD_ACCEPTANCE = `${SettingsActions.ACTION_SCHEMA}/SET_ODD_ACCEPTANCE`;\n\n private static readonly REQUEST_SET_APP_NOTIFY = `${SettingsActions.ACTION_SCHEMA}/REQUEST_SET_APP_NOTIFY`;\n private static readonly SET_APP_NOTIFY = `${SettingsActions.ACTION_SCHEMA}/SET_APP_NOTIFY`;\n\n private static readonly REQUEST_SET_EMAIL_NOTIFY = `${SettingsActions.ACTION_SCHEMA}/REQUEST_SET_EMAIL_NOTIFY`;\n private static readonly SET_EMAIL_NOTIFY = `${SettingsActions.ACTION_SCHEMA}/SET_EMAIL_NOTIFY`;\n private static readonly UPDATE_NOTIFICATION_SETTINGS = `${SettingsActions.ACTION_SCHEMA}/UPDATE_NOTIFICATION_SETTINGS`;\n\n /**\n * We can set odds acceptance if user is authenticated if not then we redirect to login.\n */\n static requestSetOddAcceptance = createAction(SettingsActions.REQUEST_SET_ODD_ACCEPTANCE, props<{ acceptance: OddsAcceptanceMode }>());\n static setOddAcceptance = createAction(SettingsActions.SET_ODD_ACCEPTANCE, props<{ acceptance: OddsAcceptanceMode }>());\n\n static requestSetAppNotify = createAction(SettingsActions.REQUEST_SET_APP_NOTIFY, props<{ value: boolean }>());\n static setAppNotify = createAction(SettingsActions.SET_APP_NOTIFY, props<{ value: boolean }>());\n\n static requestSetEmailNotify = createAction(SettingsActions.REQUEST_SET_EMAIL_NOTIFY, props<{ value: boolean }>());\n static setEmailNotify = createAction(SettingsActions.SET_EMAIL_NOTIFY, props<{ value: boolean }>());\n\n static updateNotificationSettings = createAction(\n SettingsActions.UPDATE_NOTIFICATION_SETTINGS,\n props<{ oddsAcceptance?: OddsAcceptanceMode; emailNotify?: boolean; appNotify?: boolean }>(),\n );\n}\n","import { PlaceBetRequest } from '@bpos/v2/bet-placement';\nimport { MessageEnvelope } from '@cds/push';\nimport { Nullable } from '@frontend/sports/common/core/utils/extended-types';\nimport { pprops } from '@frontend/sports/common/core/utils/redux';\nimport { createAction, props } from '@ngrx/store';\nimport { IEarlyPayoutData } from 'packages/sports/web/app/src/my-bets-base/models/early-payout-types';\nimport { IEarlyPayoutResultedPicks, MyBetsBetSlip } from 'packages/sports/web/app/src/my-bets-base/models/my-bets-viewmodels';\nimport { NumpadAction } from 'packages/sports/web/app/src/numpad/model';\n\nimport BetslipActions from '../../base/store/actions';\nimport { IBetslipState } from '../../base/store/state';\nimport { BetslipPick } from '../../core/picks/betslip-pick';\nimport { PickId, V1PickId, V2OptionMarketPickId, V2OptionMarketXCastPickId, V2ParticipantPickId } from '../../core/picks/pick-id';\nimport { ResultStatus } from '../../core/picks/pick-models';\nimport { EditMyBetPicksPayload, IEditMyBetInitPayload } from '../../model/edit-mybet/edit-bet-init';\nimport { IBetPicks } from '../betplacement/models';\nimport { IEditMyBetPickState } from './state';\n\nexport interface IFillEditMyBetPayload {\n pickState: { [pos: number]: { pick: BetslipPick; state: IEditMyBetPickState } };\n sourceBetslip: MyBetsBetSlip;\n}\n\nexport type IEditBetUpdatedEarlyPayout = IEarlyPayoutData & IEarlyPayoutResultedPicks;\n\nexport interface IEditBetUpdatedPayload {\n earlyPayout: Nullable;\n v1FixtureMessages: MessageEnvelope[]; // We can combine them in one collection at some point of time.\n v2FixtureMessages: MessageEnvelope[];\n}\n\nexport interface IPlaceBetPayload {\n requestId: string;\n}\n\nexport interface IPlaceBetResponsePayload {\n guid: string;\n requestPicks: IBetPicks;\n state: IBetslipState;\n timeout: Date;\n placeBetRequest: PlaceBetRequest;\n}\n\nexport interface IEditBetPickResultsPayload {\n pickId: PickId;\n newState: ResultStatus;\n oldState: ResultStatus;\n}\n\nexport interface ISwapPickPayload {\n oldPick: BetslipPick;\n newPick: BetslipPick;\n highlightNewPick: boolean;\n}\n\nexport class EditBetActions {\n static readonly ACTION_SCHEMA = `${BetslipActions.ACTION_SCHEMA}/edit-bet`;\n\n private static readonly INIT = `${EditBetActions.ACTION_SCHEMA}/INIT`;\n private static readonly SET_DEFAULT = `${EditBetActions.ACTION_SCHEMA}/SET_DEFAULT`;\n private static readonly FILL = `${EditBetActions.ACTION_SCHEMA}/FILL`;\n private static readonly SWAP_EDIT_BET_PICK = `${EditBetActions.ACTION_SCHEMA}/SWAP_EDIT_BET_PICK`;\n\n private static readonly START_ADDING_STAKE = `${EditBetActions.ACTION_SCHEMA}/START_ADDING_STAKE`;\n private static readonly CONFIRM_ADDING_STAKE = `${EditBetActions.ACTION_SCHEMA}/CONFIRM_ADDING_STAKE`;\n private static readonly CANCEL_ADDING_STAKE = `${EditBetActions.ACTION_SCHEMA}/CANCEL_ADDING_STAKE`;\n private static readonly SET_ADDED_STAKE = `${EditBetActions.ACTION_SCHEMA}/SET_ADDED_STAKE`;\n\n private static readonly DEPOSIT = `${EditBetActions.ACTION_SCHEMA}/DEPOSIT`;\n private static readonly MAKE_DEPOSIT = `${EditBetActions.ACTION_SCHEMA}/MAKE_DEPOSIT`;\n\n private static readonly ADD_PICK = `${EditBetActions.ACTION_SCHEMA}/ADD_PICK`;\n private static readonly REMOVE_PICK = `${EditBetActions.ACTION_SCHEMA}/REMOVE_PICK`;\n private static readonly DELETE_PICK = `${EditBetActions.ACTION_SCHEMA}/DELETE_PICK`;\n private static readonly UNDO_REMOVE_PICK = `${EditBetActions.ACTION_SCHEMA}/UNDO_REMOVE_PICK`;\n private static readonly START_UNDOING_REMOVE_PICK = `${EditBetActions.ACTION_SCHEMA}/START_UNDOING_REMOVE_PICK`;\n private static readonly REQUEST_DISCARD = `${EditBetActions.ACTION_SCHEMA}/REQUEST_DISCARD`;\n private static readonly CANCEL_DISCARD_REQUEST = `${EditBetActions.ACTION_SCHEMA}/CANCEL_DISCARD_REQUEST`;\n private static readonly EXIT_EDIT_BET = `${EditBetActions.ACTION_SCHEMA}/EXIT_EDIT_BET`;\n private static readonly SWITCH_TO_ADD_SELECTION_MODE = `${EditBetActions.ACTION_SCHEMA}/SWITCH_TO_ADD_SELECTION_MODE`;\n private static readonly TOGGLE_PICKS_CONTAINER = `${EditBetActions.ACTION_SCHEMA}/TOGGLE_PICKS_CONTAINER`;\n private static readonly CANCEL_ADD_SELECTION_MODE = `${EditBetActions.ACTION_SCHEMA}/CANCEL_ADD_SELECTION_MODE`;\n private static readonly CONFIRM_ADD_SELECTION_MODE = `${EditBetActions.ACTION_SCHEMA}/CONFIRM_ADD_SELECTION_MODE`;\n private static readonly START_ADDING_PICK_TO_CONTAINER = `${EditBetActions.ACTION_SCHEMA}/START_ADDING_PICK_TO_CONTAINER`;\n private static readonly ADD_PICK_TO_CONTAINER = `${EditBetActions.ACTION_SCHEMA}/ADD_PICK_TO_CONTAINER`;\n private static readonly REMOVE_PICK_FROM_CONTAINER = `${EditBetActions.ACTION_SCHEMA}/REMOVE_PICK_FROM_CONTAINER`;\n private static readonly SET_FIXED_PRICE = `${EditBetActions.ACTION_SCHEMA}/SET_FIXED_PRICE`;\n private static readonly SET_STARTING_PRICE = `${EditBetActions.ACTION_SCHEMA}/SET_STARTING_PRICE`;\n private static readonly SWITCH_TO_SWAP_SELECTION_MODE = `${EditBetActions.ACTION_SCHEMA}/SWITCH_TO_SWAP_SELECTION_MODE`;\n private static readonly START_ADDING_PICK_TO_SWAP = `${EditBetActions.ACTION_SCHEMA}/START_ADDING_PICK_TO_SWAP`;\n private static readonly ADD_PICK_TO_SWAP = `${EditBetActions.ACTION_SCHEMA}/ADD_PICK_TO_SWAP`;\n private static readonly CANCEL_SWAP_SELECTION_MODE = `${EditBetActions.ACTION_SCHEMA}/CANCEL_SWAP_SELECTION_MODE`;\n private static readonly UPDATE_PICK_TO_SWAP = `${EditBetActions.ACTION_SCHEMA}/UPDATE_PICK_TO_SWAP`;\n private static readonly HANDLE_SWAP_ERROR = `${EditBetActions.ACTION_SCHEMA}/HANDLE_SWAP_ERROR`;\n private static readonly PLACE_BET = `${EditBetActions.ACTION_SCHEMA}/PLACE_BET`;\n private static readonly EARLY_PAYOUT_UPDATED = `${EditBetActions.ACTION_SCHEMA}/EARLY_PAYOUT_UPDATED`;\n private static readonly EDIT_BET_UPDATED = `${EditBetActions.ACTION_SCHEMA}/EDIT_BET_UPDATED`;\n private static readonly GET_PLACE_BET_STATUS = `${EditBetActions.ACTION_SCHEMA}/GET_PLACE_BET_STATUS`;\n private static readonly ACCEPT_CURRENT_EARLY_PAYOUT_VALUE = `${EditBetActions.ACTION_SCHEMA}/ACCEPT_CURRENT_EARLY_PAYOUT_VALUE`;\n private static readonly DISABLE_EARLY_PAYOUT = `${EditBetActions.ACTION_SCHEMA}/DISABLE_EARLY_PAYOUT`;\n private static readonly APPLY_EDIT_BET_PICK_RESULTS = `${EditBetActions.ACTION_SCHEMA}/APPLY_EDIT_BET_PICK_RESULTS`;\n private static readonly START_EDIT_MY_BET = `${EditBetActions.ACTION_SCHEMA}/START_EDIT_MY_BET`;\n private static readonly SUBSCRIBE_TO_EARLY_PAYOUT = `${EditBetActions.ACTION_SCHEMA}/SUBSCRIBE_TO_EARLY_PAYOUT`;\n private static readonly UNSUBSCRIBE_FROM_EARLY_PAYOUT = `${EditBetActions.ACTION_SCHEMA}/UNSUBSCRIBE_FROM_EARLY_PAYOUT`;\n\n /**\n * Init Edit Bet\n */\n static init = createAction(EditBetActions.INIT, pprops());\n\n /**\n * Add new pick to the slip.\n */\n static addPick = createAction(EditBetActions.ADD_PICK, props<{ pick: BetslipPick }>());\n\n /**\n * Fill Edit bet with data\n */\n static fillEditMyBet = createAction(EditBetActions.FILL, pprops());\n\n /**\n * Swap a pick in edit bet.\n */\n static swapPick = createAction(EditBetActions.SWAP_EDIT_BET_PICK, pprops());\n\n /**\n * Start adding top-up stake. Shows the Stake keypad.\n */\n static startAddingStake = createAction(EditBetActions.START_ADDING_STAKE);\n\n /**\n * Notify action when user stopped changing top-up stake. On numpad OK button click.\n * null is canceling of adding stake.\n */\n static confirmAddingStake = createAction(EditBetActions.CONFIRM_ADDING_STAKE, props<{ stake: number }>());\n\n /**\n * Close stake numpad - do nothing.\n */\n static cancelAddingStake = createAction(EditBetActions.CANCEL_ADDING_STAKE);\n\n /**\n * Start adding funds through quick-deposit\n */\n static deposit = createAction(EditBetActions.DEPOSIT);\n\n /**\n * Set current entered stake.\n */\n static setEditMyBetAddedStake = createAction(EditBetActions.SET_ADDED_STAKE, pprops<{ stake: number; action?: NumpadAction }>());\n\n /**\n * Ask for confirmation to leave EMB and redirect to deposit page or open quickDeposit\n */\n static makeDeposit = createAction(EditBetActions.MAKE_DEPOSIT);\n\n /**\n * Removes pick from edit bet (marks it as not used)\n */\n static removePick = createAction(EditBetActions.REMOVE_PICK, props<{ pickId: string }>());\n\n /**\n * Deletes pick from edit bet (marks it as not used)\n */\n static deletePick = createAction(EditBetActions.DELETE_PICK, props<{ pickId: PickId }>());\n\n /**\n * Start process of Undo remove pick. Checks if the betslip is not full.\n */\n static startUndoingRemovePick = createAction(EditBetActions.START_UNDOING_REMOVE_PICK, props<{ pickId: string }>());\n\n /**\n * Undo remove (enable back) the pick in edit bet.\n */\n static undoRemovePick = createAction(EditBetActions.UNDO_REMOVE_PICK, props<{ pickId: string }>());\n\n /**\n * User tries to exit edit bet (Clicks on Cancel button).\n */\n static requestDiscard = createAction(EditBetActions.REQUEST_DISCARD);\n\n /**\n * User cancels his/her request to edit bet. Decides to continue editing\n */\n static cancelDiscardRequest = createAction(EditBetActions.CANCEL_DISCARD_REQUEST);\n\n /**\n * User exits edit bet mode.\n */\n static exitEditBet = createAction(EditBetActions.EXIT_EDIT_BET);\n\n /**\n * Set default edit bet\n */\n static setDefault = createAction(EditBetActions.SET_DEFAULT);\n\n /**\n * User start adding picks to edit bet.\n */\n static switchToAddSelectionMode = createAction(EditBetActions.SWITCH_TO_ADD_SELECTION_MODE);\n\n /**\n * Toggles the visibility of edit bet picks staging area. Show/Hides the pick list.\n */\n static togglePicksContainer = createAction(EditBetActions.TOGGLE_PICKS_CONTAINER);\n\n // /**\n // * Cancel adding new picks to edit bet.\n // */\n // static cancelAddSelectionMode = createAction(EditBetActions.CANCEL_ADD_SELECTION_MODE, pprops());\n /**\n * Cancel adding new picks to edit bet.\n */\n static cancelAddSelectionMode = createAction(EditBetActions.CANCEL_ADD_SELECTION_MODE, pprops());\n\n /**\n * Adds the staging picks to edit bet.\n */\n static confirmAddSelectionMode = createAction(EditBetActions.CONFIRM_ADD_SELECTION_MODE, pprops<{ picks: BetslipPick[] }>());\n\n /**\n * Start adding pick to staging area. Creates request to BCD to resolve pick data.\n */\n static startAddingPickToContainer = createAction(\n EditBetActions.START_ADDING_PICK_TO_CONTAINER,\n props<{ pickId: V1PickId | V2OptionMarketPickId | V2ParticipantPickId | V2OptionMarketXCastPickId }>(),\n );\n\n /**\n * Add pick to edit bet staging area. After we have bcd data.\n */\n static addPickToContainer = createAction(EditBetActions.ADD_PICK_TO_CONTAINER, props<{ pick: BetslipPick }>());\n\n /**\n * Remove pick from staging area.\n */\n static removeFromContainer = createAction(EditBetActions.REMOVE_PICK_FROM_CONTAINER, props<{ pickId: string }>());\n\n /**\n * Set fixed price for pick\n */\n static setFixedPrice = createAction(EditBetActions.SET_FIXED_PRICE, props<{ pickId: PickId }>());\n\n /**\n * Set starting price for pick\n */\n static setStartingPrice = createAction(EditBetActions.SET_STARTING_PRICE, props<{ pickId: PickId }>());\n\n /**\n * Start swapping pick. Shows swap pick dialog.\n */\n static switchToSwapSelectionMode = createAction(EditBetActions.SWITCH_TO_SWAP_SELECTION_MODE, props<{ pick: BetslipPick }>());\n\n /**\n * Cancel swapping pick. Closes the swap pick dialog.\n */\n static cancelSwapSelectionMode = createAction(EditBetActions.CANCEL_SWAP_SELECTION_MODE);\n\n /**\n * Select pick to swap. Creates request to BCD to resolve pick data.\n */\n static startAddingPickToSwap = createAction(EditBetActions.START_ADDING_PICK_TO_SWAP, props<{ pick: BetslipPick }>());\n\n /**\n * Swap temporary pick.\n */\n static addPickToSwap = createAction(EditBetActions.ADD_PICK_TO_SWAP, props<{ pick: BetslipPick }>());\n\n static updatePickToSwap = createAction(EditBetActions.UPDATE_PICK_TO_SWAP, props<{ pick: BetslipPick }>());\n\n static handleSwapError = createAction(EditBetActions.HANDLE_SWAP_ERROR, props<{ error: string; pickId?: PickId }>());\n\n /**\n * Place edit bet.\n */\n static placeBet = createAction(EditBetActions.PLACE_BET, pprops());\n\n /**\n * Get current place edit bet status.\n */\n static getPlaceBetStatus = createAction(EditBetActions.GET_PLACE_BET_STATUS, pprops());\n\n /**\n * Action fired when Early payout is updated. Has push message regarding Early payout\n */\n static earlyPayoutUpdated = createAction(EditBetActions.EARLY_PAYOUT_UPDATED, pprops>());\n\n /**\n * Action that modifies the Edit bet (Changes Odds, Pick visibility, Early payout value)\n */\n static editBetUpdated = createAction(EditBetActions.EDIT_BET_UPDATED, pprops());\n\n /**\n * Accept current early payout value\n */\n static acceptCurrentEarlyPayoutValue = createAction(EditBetActions.ACCEPT_CURRENT_EARLY_PAYOUT_VALUE);\n\n /**\n * Disable early payout because of error\n */\n static disableEarlyPayout = createAction(EditBetActions.DISABLE_EARLY_PAYOUT);\n\n static applyEditBetPickResults = createAction(EditBetActions.APPLY_EDIT_BET_PICK_RESULTS, pprops());\n\n /**\n * Subscribe to Early Payout\n */\n static subscribeToEarlyPayout = createAction(EditBetActions.SUBSCRIBE_TO_EARLY_PAYOUT);\n\n /**\n * Unsubscribe from Early payout.\n */\n static unsubscribeFromEarlyPayout = createAction(EditBetActions.UNSUBSCRIBE_FROM_EARLY_PAYOUT);\n\n /**\n * Action called when bet editing is started\n */\n static startEditMyBet = createAction(EditBetActions.START_EDIT_MY_BET, props<{ payload: EditMyBetPicksPayload }>());\n}\n\n// @ts-ignore\n// window.editBetAction = EditBetActions;\n","import { InjectionToken } from '@angular/core';\n\nimport { BetslipModuleLoaderService } from './betslip-module-loader.service';\nimport IOddsSettingsService from './services/odds-settings.service';\n\nexport const BETSLIP_MODULE_LOADER_SERVICE = new InjectionToken('Betslip module loader token');\n\n/* Services */\n\nexport const ODDS_SETTINGS_SERVICE = new InjectionToken('BET_STATION_ODDS_SETTINGS_SERVICE');\n\nexport const ODDS_VIEW_MODE = new InjectionToken('ODDS_VIEW_MODE');\n","import { Location } from '@angular/common';\nimport { Inject, Injectable } from '@angular/core';\n\nimport { OddsAcceptanceMode } from '@bpos';\nimport { DispatcherService } from '@frontend/sports/common/core/utils/dispatcher';\nimport { NonRootToken } from '@frontend/sports/host-app/sports-product/feature/non-root-token';\nimport { filterSports } from '@frontend/sports/host-app/sports-product/feature/utils';\nimport { UrlService } from '@frontend/vanilla/core';\nimport { Store, createSelector } from '@ngrx/store';\nimport { isAccaBoostTokenSelector } from 'packages/sports/web/app/src/acca-boost-base/acca-boost-utils';\nimport { AdaptiveLayoutService } from 'packages/sports/web/app/src/layout/adaptive-layout.service';\nimport { MarketUrlParam } from 'packages/sports/web/app/src/navigation-core/url-helper.service';\nimport StoragePersister from 'packages/sports/web/app/src/store-persist/storage-persister';\nimport {\n Observable,\n Subject,\n combineLatest,\n distinctUntilChanged,\n distinctUntilKeyChanged,\n filter,\n map,\n mergeMap,\n of,\n startWith,\n switchMap,\n take,\n tap,\n} from 'rxjs';\n\nimport { IBetslipRootState, IBetslipState, betslipFeatureKey } from '../base/store/state';\nimport { BetslipState } from '../core/betslip-state';\nimport { BetslipType } from '../core/betslip-type';\nimport { BetslipHost, ExternalBetslipActions, PickAddPayload } from '../core/external-betslip-actions';\nimport { BetslipBetBuilderPick } from '../core/picks/betslip-bet-builder-pick';\nimport { BetBuilderPickId, PickId } from '../core/picks/pick-id';\nimport { PickType, PriceType } from '../core/picks/pick-models';\nimport { flattenGroupPicksIfAny } from '../core/utils';\nimport { BetslipBarVisibilityService } from '../modules/betslip-bar/services/betslip-bar-visibility.service';\nimport { hasPlacedFirstBetSelector } from '../modules/hidden-market/hidden-market.selectors';\nimport { IRewardToken } from '../modules/reward-tokens/reward-tokens.model';\nimport { selectRewardTokensList } from '../modules/reward-tokens/selectors';\nimport SettingsActions from '../modules/settings/actions';\nimport { EditBetActions } from './../modules/edit-bet/actions';\nimport { BetslipModule, BetslipModuleLoaderService } from './betslip-module-loader.service';\nimport { BETSLIP_MODULE_LOADER_SERVICE } from './sports-injection-services';\n\n@Injectable({\n providedIn: 'root',\n})\nexport class BetslipIntegrationService {\n private betslipHost?: BetslipHost;\n\n private betslipPickSelectedSelectorFactory = (\n module: BetslipModule,\n pickId: PickId,\n priceType?: PriceType,\n longId?: string,\n siblingPickId?: PickId,\n ) =>\n createSelector(\n module.exported.selectors.betslipTypeCurrentTypeSelector,\n module.exported.selectors.betslipPicksListSelector,\n module.exported.selectors.editBetPicksListSelector,\n module.exported.selectors.editBetAddedPicksListSelector,\n module.exported.selectors.editBetPickStateSelector,\n (type, picks, editBetPicks, editBetAddedPicks, pickstate) => {\n if (type === BetslipType.EditBet) {\n const editBetPick =\n editBetPicks.find((p) => p.id.toString() === pickId.toString() && pickstate[pickId.toString()].isRemoved === false) ||\n editBetAddedPicks.find((p) => p.id.toString() === pickId.toString());\n\n return !!editBetPick && (editBetPick.priceType === priceType || priceType === undefined);\n }\n\n if (pickId.getPickType() === PickType.BetBuilderPick && longId) {\n const bbPickId = pickId as BetBuilderPickId;\n\n return picks.some(\n (p) =>\n BetslipBetBuilderPick.isEntainUiBetBuilderPick(p) &&\n p.eventId === bbPickId.eventId &&\n p.sportcastOptions.map((so) => so.longId).some((id) => id === longId),\n );\n }\n\n const pick = flattenGroupPicksIfAny(picks).find((p) => p.id.isEqual(pickId));\n const siblingPick = siblingPickId && picks.find((t) => t.id.isEqual(siblingPickId));\n\n return (!!pick && (pick.priceType === priceType || priceType === undefined)) || !!siblingPick;\n },\n );\n\n private locationUrl$: Observable;\n\n constructor(\n private store: Store,\n private dispatcher: DispatcherService,\n private storagePersister: StoragePersister,\n private adaptiveLayoutService: AdaptiveLayoutService,\n private urlService: UrlService,\n private location: Location,\n @Inject(NonRootToken(BETSLIP_MODULE_LOADER_SERVICE)) private betslipModuleLoader: BetslipModuleLoaderService,\n private betslipBarVisibility: BetslipBarVisibilityService,\n ) {\n const locationUrl = new Subject();\n\n this.locationUrl$ = locationUrl.pipe(distinctUntilChanged(), filterSports());\n\n this.location.onUrlChange((url) => locationUrl.next(url));\n this.betslipInitialized$().subscribe();\n }\n\n betslipInitialized$(): Observable {\n if (this.betslipHost) {\n return of(this.betslipHost);\n }\n\n return this.dispatcher.on('MODULE_LOADED').pipe(\n filter((value) => value === 'BetslipBetStationModule' || value === 'BetslipDigitalModule'),\n take(1),\n map((value) => (value === 'BetslipDigitalModule' ? BetslipHost.Digital : BetslipHost.BetStation)),\n tap((host) => {\n this.betslipHost = host;\n }),\n );\n }\n\n isEditBetActive$(betslipId: string): Observable {\n return this.betslipInitialized$().pipe(\n mergeMap(() => this.betslipModuleLoader.loadBetslipModule()),\n mergeMap((module) =>\n this.store.select(module.exported.selectors.betslipTypeStateSelector).pipe(\n map((state) => {\n return state.base.currentSelectedType === BetslipType.EditBet && state.editBet.betslipId === betslipId;\n }),\n ),\n ),\n );\n }\n\n isAccaBoostToken$(): Observable {\n return this.store.select(isAccaBoostTokenSelector);\n }\n\n betslipState$(): Observable {\n return this.betslipInitialized$().pipe(\n mergeMap(() => this.betslipModuleLoader.loadBetslipModule()),\n mergeMap((module) => this.store.select(module.exported.selectors.betslipBaseStateSelector)),\n );\n }\n\n /**\n * Indicates whether the user has placed a bet in the current application run.\n * Used to determine whether to show hidden markets.\n */\n hasPlacedFirstBet$(): Observable {\n return this.store.select(hasPlacedFirstBetSelector);\n }\n\n rewardTokens$(): Observable {\n return this.store.select(selectRewardTokensList);\n }\n\n requestExitEditBet(): void {\n this.store.dispatch(EditBetActions.requestDiscard());\n }\n\n /**\n * Define when to show betstation betslip:\n * 1. Not initialized ? Check if we have something in the savedState and if we have picks there load it.\n * 2. Initialized ? Check current betslip count.\n */\n shouldShowBetslipInBetStation$(): Observable {\n const betslipPicksCountWatcher$ = this.betslipPicksCount$().pipe(map((count) => !!count));\n if (!this.betslipHost) {\n // Betslip not loaded, check storage if has some picks then load it.\n const savedState = this.storagePersister.load();\n if (savedState && betslipFeatureKey in savedState) {\n const betslipState = savedState[betslipFeatureKey] as IBetslipState;\n if (betslipState?.picks?.pickList?.length) {\n return betslipPicksCountWatcher$.pipe(startWith(true));\n }\n }\n }\n\n // if betslip is loaded or no picks in the storage then check picks count.\n return betslipPicksCountWatcher$;\n }\n\n /**\n * Define when to show QuickBet:\n * Show Quick Bet if the layout's quick bet active is set to true.\n */\n shouldShowQuickBet$(): Observable {\n return this.adaptiveLayoutService.stateChange$.pipe(\n distinctUntilKeyChanged('quickBetActive'),\n map((t) => t.quickBetActive),\n );\n }\n\n /**\n * Define when to show Betslip returns messages:\n * 1. when bottom navigation is visible and\n * 2. when the user is not in the Bet Builder tab\n */\n shouldShowBetslipReturnsMessage$(): Observable {\n const isInBetBuilderTab$ = this.betslipInitialized$().pipe(\n switchMap(() => {\n return this.locationUrl$.pipe(\n map((url) => url && this.urlService.parse(url).search.get('market') === MarketUrlParam.BetBuilder),\n startWith(this.urlService.current().search.get('market') === MarketUrlParam.BetBuilder),\n );\n }),\n );\n\n const hasBottomNavigation$ = this.adaptiveLayoutService.stateChange$.pipe(map((state) => !!state.bottomNavigation));\n\n return combineLatest([isInBetBuilderTab$, hasBottomNavigation$]).pipe(\n map(([isInBetBuilderTab, hasBottomNavigation]) => hasBottomNavigation && !isInBetBuilderTab),\n distinctUntilChanged(),\n );\n }\n\n /**\n * Define when to show QuickBetBuilder in Entain UI:\n * Show Quick Bet Builder Drawer if the layout's quick bet active is set to true.\n */\n shouldShowQuickBetBuilder$(): Observable {\n return this.adaptiveLayoutService.stateChange$.pipe(\n distinctUntilKeyChanged('quickBetBuilderActive'),\n map((t) => t.quickBetBuilderActive),\n );\n }\n\n betslipPicksCount$(): Observable {\n return this.betslipInitialized$().pipe(\n mergeMap((_) => this.betslipModuleLoader.loadBetslipModule()),\n mergeMap((module) => this.store.select(module.exported.selectors.betslipPickIdsSelector).pipe(map((picks) => picks.length))),\n );\n }\n\n betslipPicksAndLegsCount$(): Observable {\n return this.betslipInitialized$().pipe(\n mergeMap((_) => this.betslipModuleLoader.loadBetslipModule()),\n mergeMap((module) => this.store.select(module.exported.selectors.betslipPicksListCountSelectorIncludeBetbuilderLegs)),\n );\n }\n\n betslipPickIds$(): Observable {\n return this.betslipInitialized$().pipe(\n mergeMap((_) => this.betslipModuleLoader.loadBetslipModule()),\n mergeMap((module) => this.store.select(module.exported.selectors.betslipPickIdsSelector).pipe(map((picks) => picks))),\n );\n }\n\n pickSelected$(pickId: PickId, priceType?: PriceType, longId?: string, siblingPickId?: PickId): Observable {\n return this.betslipInitialized$().pipe(\n mergeMap((_) => this.betslipModuleLoader.loadBetslipModule()),\n mergeMap((module) => this.store.select(this.betslipPickSelectedSelectorFactory(module, pickId, priceType, longId, siblingPickId))),\n );\n }\n\n get betslipBarEnabled(): Observable {\n return this.betslipBarVisibility.enableDsl$;\n }\n\n addMultiplePicks(payload: { picks: PickAddPayload[]; stake?: number }): void {\n this.store.dispatch(ExternalBetslipActions.addMultiplePicks(payload));\n }\n\n addPick(payload: PickAddPayload): void {\n this.store.dispatch(ExternalBetslipActions.addPick(payload));\n }\n\n removeMultiplePicks(payload: { pickIds: PickId[] }): void {\n this.store.dispatch(ExternalBetslipActions.removeMultiplePicks(payload));\n }\n\n setOddsAcceptance(oddsAcceptance: string = OddsAcceptanceMode.No): void {\n if (!this.betslipHost) {\n // If betslip is not initialized, don't do anything when we init betslip we will read what is the current odds acceptance.\n return;\n }\n this.store.dispatch(SettingsActions.setOddAcceptance({ acceptance: oddsAcceptance as OddsAcceptanceMode }));\n }\n}\n","import { Injectable } from '@angular/core';\nimport { Router } from '@angular/router';\n\nimport { PrettyUrlsConfig } from '@frontend/sports/common/client-config-data-access';\nimport { RouterEventsService } from '@frontend/sports/common/core/utils/router-events';\nimport { ParsedUrl, UrlService } from '@frontend/vanilla/core';\nimport { capitalize, cloneDeep, isEqual, toInteger } from 'lodash-es';\n\nimport { CompetitionRoute } from '../navigation/navigation.models';\n\ntype RegExpMapper = (result: RegExpExecArray) => CompetitionRoute;\n\n@Injectable({\n providedIn: 'root',\n})\nexport class CompetitionRouteService {\n private parsedUrl: ParsedUrl;\n private parsedRoute: CompetitionRoute = {};\n private matchers = new Map();\n private mappers = new Map();\n protected get base(): string {\n return '(/.+/sports)';\n }\n\n constructor(\n routerEvents: RouterEventsService,\n private router: Router,\n private urlService: UrlService,\n private urlConfig: PrettyUrlsConfig,\n ) {\n const context = (...options: string[]) => `(/(${options.join('|')}))`;\n const register = (key: string, regex: string, mapper: RegExpMapper) => {\n this.matchers.set(key, new RegExp(regex, 'i'));\n this.mappers.set(key, mapper);\n };\n\n const couponContext = context(this.urlConfig.translations.coupons);\n const competitionContext = context(this.urlConfig.translations.competitions);\n const conferencesContext = context(this.urlConfig.translations.conferences);\n const tournamentContext = context(this.urlConfig.translations.tournaments);\n const teampagesContext = context(this.urlConfig.translations.teamPages);\n const betsContext = '/' + this.urlConfig.translations.bets;\n const standingsContext = '/' + this.urlConfig.translations.standings;\n const liveContext = context(this.urlConfig.translations.live);\n const worldCupHubContext = context(this.urlConfig.translations.worldCupHub);\n\n const baseOptions = [\n this.urlConfig.translations.betting,\n this.urlConfig.translations.calendar,\n this.urlConfig.translations.in30minutes,\n this.urlConfig.translations.in60minutes,\n this.urlConfig.translations.in180minutes,\n this.urlConfig.translations.today,\n this.urlConfig.translations.nextRaces,\n this.urlConfig.translations.tomorrow,\n this.urlConfig.translations.after2days,\n this.urlConfig.translations.after3days,\n this.urlConfig.translations.next2days,\n this.urlConfig.translations.next3days,\n this.urlConfig.translations.next5days,\n this.urlConfig.translations.midWeek,\n this.urlConfig.translations.thisWeekend,\n ];\n const eventContext = context(...baseOptions, this.urlConfig.translations.coupons);\n const baseContext = context(...baseOptions);\n\n const node = '(/(([^/]+)-)?(\\\\d+))'; // name-identifier with optional name\n const virtualNode = '(/(([^/]+)-)?(0:(\\\\d+)))';\n\n register('bets', `${this.base}${betsContext}${node}${node}?${node}?${node}?$`, this.getBetsRoute);\n register('bets-virtual', `${this.base}${betsContext}${node}${node}${virtualNode}$`, this.getBetsVirtualRoute);\n register('sport', `${this.base}${node}${eventContext}?${competitionContext}?$`, this.getSportRoute);\n register('sport-modular', `${this.base}${node}/modular$`, this.getSportRoute); // temporary, needs to be removed after the route switch technique will be included\n register('calendar', `${this.base}${eventContext}$`, this.getCalendarRoute);\n register('conferences', `${this.base}${node}${conferencesContext}${node}?${node}?${node}?$`, this.getConferencesRoute);\n register('betting', `${this.base}${node}${baseContext}${node}?${node}?$`, this.getBettingRoute);\n register('betting-virtual', `${this.base}${node}${baseContext}${node}${virtualNode}${node}?$`, this.getVirtualBettingRoute);\n register('live', `${this.base}${liveContext}${node}${node}?${node}?$`, this.getLiveRoute);\n register('live-conference', `${this.base}${liveContext}${node}${node}?${node}?${node}?$`, this.getLiveConferenceRoute);\n register('live-virtual', `${this.base}${liveContext}${node}${node}${virtualNode}${node}?$`, this.getVirtualLiveRoute);\n register('betting-multi', `${this.base}${node}${baseContext}/(\\\\d+(,\\\\d+)*)$`, this.getBettingSportRoute);\n register('coupon', `${this.base}${node}${couponContext}(/${this.urlConfig.translations.betBuilder}|${node})?$`, this.getCouponRoute);\n register('tournament', `${this.base}${node}${tournamentContext}(/([^/]+))?${node}?$`, this.getTournamentRoute);\n register('teamPages', `${this.base}${node}${teampagesContext}(/([^/]+))?${node}?$`, this.getCommon);\n register('standings', `${this.base}${standingsContext}${node}${node}${node}?$`, this.getStandingsRoute);\n register('standings-virtual', `${this.base}${standingsContext}${node}${node}${virtualNode}${node}?$`, this.getStandingsVirtualRoute);\n register(\n 'coupon-competition',\n `${this.base}${node}${couponContext}${node}${node}(/${this.urlConfig.translations.betBuilder}|${node})$`,\n this.getCouponCompetitionRoute,\n );\n register(\n 'coupon-virtual',\n `${this.base}${node}${couponContext}${node}${virtualNode}(/${this.urlConfig.translations.betBuilder}|${node})$`,\n this.getCouponVirtualCompetitionRoute,\n );\n register('worldCupHub', `${this.base}${node}${worldCupHubContext}`, this.getWorldCupHubRoute);\n\n routerEvents.currentRoutesRecognized.subscribe((event) => {\n if (event) {\n const url = event.urlAfterRedirects || event.url || this.router.url;\n const parsed = this.urlService.parse(url);\n\n if (!this.parsedUrl || !isEqual(this.parsedUrl.path(), parsed.path()) || !isEqual(this.parsedUrl.search, parsed.search)) {\n this.parsedUrl = parsed;\n this.parsedRoute = this.parseUrl(parsed);\n }\n }\n });\n }\n\n current(): string {\n // as from vanilla code, they depend on window.location.pathname which is always encoded, so we need to decode\n return decodeURI(this.parsedUrl.url());\n }\n\n path(): string {\n // as from vanilla code, they depend on window.location.pathname which is always encoded, so we need to decode\n return decodeURI(this.parsedUrl.path());\n }\n\n params(): CompetitionRoute {\n return cloneDeep(this.parsedRoute);\n }\n\n parse(url: string): CompetitionRoute {\n const parsed = this.urlService.parse(url);\n\n return this.parseUrl(parsed);\n }\n\n private parseUrl(url: ParsedUrl): CompetitionRoute {\n let result: CompetitionRoute = {};\n\n // Root-level secondary outlets take the form e.g. /en/sports(overlay:menu/subpath)\n const decodedPathWithSecondaryOutletsRemoved = decodeURI(url.path()).replace(/\\(.*\\)/giu, '');\n\n for (const type of this.matchers.keys()) {\n const matcher = this.matchers.get(type);\n const mapper = this.mappers.get(type);\n\n if (matcher && mapper) {\n const match = matcher.exec(decodedPathWithSecondaryOutletsRemoved);\n\n if (match) {\n result = mapper(match);\n\n break;\n }\n }\n }\n\n return result;\n }\n\n private getQuery = (key: string) => this.parsedUrl.search.get(key) || undefined;\n private getInteger = (value?: string) => toInteger(value) || undefined;\n private getIntegerArray = (value?: string) => {\n const values = (value || '')\n .split(',')\n .map(this.getInteger)\n .filter((current) => !!current) as number[];\n\n if (values.length === 0) {\n return;\n }\n\n if (values.length === 1) {\n return values.pop();\n }\n\n return values;\n };\n\n private getSportRoute: RegExpMapper = (result) => ({\n ...this.getCommon(result),\n subContext: result[9],\n });\n\n private getBettingRoute: RegExpMapper = (result) => ({\n ...this.getCommon(result),\n region: this.getInteger(result[11]),\n regionName: result[10],\n league: this.getIntegerArray(result[15]),\n leagueName: result[14],\n });\n\n private getConferencesRoute: RegExpMapper = (result) => ({\n ...this.getBettingRoute(result),\n conference: result[19] ? this.getInteger(result[19]) || 0 : undefined,\n conferenceName: result[18],\n });\n\n private getVirtualBettingRoute: RegExpMapper = (result) => ({\n ...this.getCommon(result),\n region: this.getInteger(result[11]),\n regionName: result[10],\n league: this.getIntegerArray(result[16]),\n leagueName: result[14],\n virtualCompetitionGroup: this.getInteger(result[20]),\n virtualCompetitionGroupName: result[19],\n isVirtual: true,\n });\n\n private getBettingSportRoute: RegExpMapper = (result) => ({\n ...this.getCommon(result),\n league: this.getIntegerArray(result[8]),\n });\n\n private getCouponRoute: RegExpMapper = (result) => ({\n ...this.getCommon(result),\n coupon: this.getInteger(result[12]),\n couponName: result[11],\n });\n\n private getTournamentRoute: RegExpMapper = (result) => ({\n ...this.getCommon(result),\n tournament: result[9],\n league: this.getIntegerArray(result[13]),\n leagueName: result[12],\n });\n\n private getStandingsRoute: RegExpMapper = (result) => ({\n ...this.getCommon(result),\n context: this.urlConfig.translations.standings,\n leagueName: result[12],\n league: toInteger(result[13]),\n region: toInteger(result[9]),\n regionName: result[8],\n });\n\n private getStandingsVirtualRoute: RegExpMapper = (result) => ({\n ...this.getCommon(result),\n context: this.urlConfig.translations.standings,\n virtualCompetitionGroup: result[18] !== undefined ? toInteger(result[18]) : undefined,\n virtualCompetitionGroupName: result[17],\n isVirtual: true,\n leagueName: result[12],\n league: toInteger(result[14]),\n region: toInteger(result[9]),\n regionName: result[8],\n });\n\n private getCalendarRoute: RegExpMapper = (result) => ({\n base: result[1],\n context: result[3],\n });\n\n private getWorldCupHubRoute: RegExpMapper = (result) => ({\n ...this.getCommon(result),\n context: this.urlConfig.translations.worldCupHub,\n });\n\n private getBetsRoute: RegExpMapper = (result) => ({\n ...this.getCommon(result),\n league: this.getInteger(result[13]),\n leagueName: result[12],\n region: this.getInteger(result[9]),\n regionName: result[8],\n conference: this.getInteger(result[17]),\n conferenceName: result[16],\n marketOffer: this.getQuery('category'),\n outrightCategory: this.getQuery('outrightCategory'),\n specialCategory: this.getQuery('specialCategory'),\n context: this.urlConfig.translations.bets,\n });\n\n private getBetsVirtualRoute: RegExpMapper = (result) => ({\n ...this.getCommon(result),\n marketOffer: this.getQuery('category'),\n outrightCategory: this.getQuery('outrightCategory'),\n specialCategory: this.getQuery('specialCategory'),\n context: this.urlConfig.translations.bets,\n isVirtual: true,\n leagueName: result[12],\n league: toInteger(result[14]),\n region: toInteger(result[9]),\n regionName: result[8],\n });\n\n private getCommon: RegExpMapper = (result) => ({\n base: result[1],\n sport: this.getInteger(result[5]),\n sportName: result[4],\n context: result[7],\n marketTemplate: this.getInteger(this.getQuery('marketTemplate')),\n marketCategory: this.getInteger(this.getQuery('marketCategory')),\n marketOffer: this.getQuery('tab'),\n dynamicOfferCategory: this.getQuery('dynamicCategory'),\n });\n\n private getLiveRoute: RegExpMapper = (result) => ({\n base: result[1],\n sport: this.getInteger(result[7]),\n sportName: result[6],\n context: result[3],\n region: this.getInteger(result[11]),\n regionName: result[10],\n league: this.getIntegerArray(result[15]),\n leagueName: result[14],\n marketTemplate: this.getInteger(this.getQuery('marketTemplate')),\n marketCategory: this.getInteger(this.getQuery('marketCategory')),\n marketOffer: this.getQuery('tab'),\n });\n\n private getLiveConferenceRoute: RegExpMapper = (result) => ({\n ...this.getLiveRoute(result),\n conference: this.getInteger(result[19]),\n conferenceName: result[18],\n });\n\n private getVirtualLiveRoute: RegExpMapper = (result) => ({\n ...this.getLiveRoute(result),\n league: this.getIntegerArray(result[16]),\n virtualCompetitionGroup: this.getInteger(result[20]),\n virtualCompetitionGroupName: result[19],\n isVirtual: true,\n });\n\n private getCouponCompetitionRoute: RegExpMapper = (result) => ({\n ...this.getBettingRoute(result),\n coupon: this.getInteger(result[20]),\n couponName: result[19],\n });\n\n private getCouponVirtualCompetitionRoute: RegExpMapper = (result) => ({\n base: result[1],\n sport: this.getInteger(result[5]),\n sportName: result[4],\n context: result[7],\n region: this.getInteger(result[11]),\n regionName: result[10],\n league: this.getIntegerArray(result[16]),\n leagueName: result[14],\n isVirtual: true,\n coupon: this.getInteger(result[21]),\n couponName: result[20],\n });\n\n getCompetitionRouteTracking(hasFixtures: boolean): string {\n const params = this.params() ?? {};\n if (params.isVirtual) {\n let groupName = params.virtualCompetitionGroupName;\n\n if (!groupName) {\n groupName = 'All';\n }\n\n return `EventList/League/${groupName}`;\n }\n\n if (hasFixtures && params.league) {\n return 'EventList/League';\n }\n\n const routeContext = params.context;\n\n return `EventList/${capitalize(routeContext || 'all')}`;\n }\n}\n","import * as i0 from '@angular/core';\nimport { InjectionToken, inject, ElementRef, NgZone, ANIMATION_MODULE_TYPE, booleanAttribute, Directive, Input, Renderer2, numberAttribute, Component, ViewEncapsulation, ChangeDetectionStrategy, NgModule } from '@angular/core';\nimport { FocusMonitor } from '@angular/cdk/a11y';\nimport { MatRippleLoader, _StructuralStylesLoader, MatCommonModule, MatRippleModule } from '@angular/material/core';\nimport { _CdkPrivateStyleLoader } from '@angular/cdk/private';\n\n/** Injection token that can be used to provide the default options the button component. */\nconst _c0 = [\"mat-button\", \"\"];\nconst _c1 = [[[\"\", 8, \"material-icons\", 3, \"iconPositionEnd\", \"\"], [\"mat-icon\", 3, \"iconPositionEnd\", \"\"], [\"\", \"matButtonIcon\", \"\", 3, \"iconPositionEnd\", \"\"]], \"*\", [[\"\", \"iconPositionEnd\", \"\", 8, \"material-icons\"], [\"mat-icon\", \"iconPositionEnd\", \"\"], [\"\", \"matButtonIcon\", \"\", \"iconPositionEnd\", \"\"]]];\nconst _c2 = [\".material-icons:not([iconPositionEnd]), mat-icon:not([iconPositionEnd]), [matButtonIcon]:not([iconPositionEnd])\", \"*\", \".material-icons[iconPositionEnd], mat-icon[iconPositionEnd], [matButtonIcon][iconPositionEnd]\"];\nconst _c3 = \".mat-mdc-button-base{text-decoration:none}.mdc-button{-webkit-user-select:none;user-select:none;position:relative;display:inline-flex;align-items:center;justify-content:center;box-sizing:border-box;min-width:64px;border:none;outline:none;line-height:inherit;-webkit-appearance:none;overflow:visible;vertical-align:middle;background:rgba(0,0,0,0);padding:0 8px}.mdc-button::-moz-focus-inner{padding:0;border:0}.mdc-button:active{outline:none}.mdc-button:hover{cursor:pointer}.mdc-button:disabled{cursor:default;pointer-events:none}.mdc-button[hidden]{display:none}.mdc-button .mdc-button__label{position:relative}.mat-mdc-button{padding:0 var(--mat-text-button-horizontal-padding, 12px);height:var(--mdc-text-button-container-height, 40px);font-family:var(--mdc-text-button-label-text-font, var(--mat-sys-label-large-font));font-size:var(--mdc-text-button-label-text-size, var(--mat-sys-label-large-size));letter-spacing:var(--mdc-text-button-label-text-tracking, var(--mat-sys-label-large-tracking));text-transform:var(--mdc-text-button-label-text-transform);font-weight:var(--mdc-text-button-label-text-weight, var(--mat-sys-label-large-weight))}.mat-mdc-button,.mat-mdc-button .mdc-button__ripple{border-radius:var(--mdc-text-button-container-shape, var(--mat-sys-corner-full))}.mat-mdc-button:not(:disabled){color:var(--mdc-text-button-label-text-color, var(--mat-sys-primary))}.mat-mdc-button[disabled],.mat-mdc-button.mat-mdc-button-disabled{cursor:default;pointer-events:none;color:var(--mdc-text-button-disabled-label-text-color, color-mix(in srgb, var(--mat-sys-on-surface) 38%, transparent))}.mat-mdc-button.mat-mdc-button-disabled-interactive{pointer-events:auto}.mat-mdc-button:has(.material-icons,mat-icon,[matButtonIcon]){padding:0 var(--mat-text-button-with-icon-horizontal-padding, 16px)}.mat-mdc-button>.mat-icon{margin-right:var(--mat-text-button-icon-spacing, 8px);margin-left:var(--mat-text-button-icon-offset, -4px)}[dir=rtl] .mat-mdc-button>.mat-icon{margin-right:var(--mat-text-button-icon-offset, -4px);margin-left:var(--mat-text-button-icon-spacing, 8px)}.mat-mdc-button .mdc-button__label+.mat-icon{margin-right:var(--mat-text-button-icon-offset, -4px);margin-left:var(--mat-text-button-icon-spacing, 8px)}[dir=rtl] .mat-mdc-button .mdc-button__label+.mat-icon{margin-right:var(--mat-text-button-icon-spacing, 8px);margin-left:var(--mat-text-button-icon-offset, -4px)}.mat-mdc-button .mat-ripple-element{background-color:var(--mat-text-button-ripple-color, color-mix(in srgb, var(--mat-sys-primary) calc(var(--mat-sys-pressed-state-layer-opacity) * 100%), transparent))}.mat-mdc-button .mat-mdc-button-persistent-ripple::before{background-color:var(--mat-text-button-state-layer-color, var(--mat-sys-primary))}.mat-mdc-button.mat-mdc-button-disabled .mat-mdc-button-persistent-ripple::before{background-color:var(--mat-text-button-disabled-state-layer-color, var(--mat-sys-on-surface-variant))}.mat-mdc-button:hover>.mat-mdc-button-persistent-ripple::before{opacity:var(--mat-text-button-hover-state-layer-opacity, var(--mat-sys-hover-state-layer-opacity))}.mat-mdc-button.cdk-program-focused>.mat-mdc-button-persistent-ripple::before,.mat-mdc-button.cdk-keyboard-focused>.mat-mdc-button-persistent-ripple::before,.mat-mdc-button.mat-mdc-button-disabled-interactive:focus>.mat-mdc-button-persistent-ripple::before{opacity:var(--mat-text-button-focus-state-layer-opacity, var(--mat-sys-focus-state-layer-opacity))}.mat-mdc-button:active>.mat-mdc-button-persistent-ripple::before{opacity:var(--mat-text-button-pressed-state-layer-opacity, var(--mat-sys-pressed-state-layer-opacity))}.mat-mdc-button .mat-mdc-button-touch-target{position:absolute;top:50%;height:48px;left:0;right:0;transform:translateY(-50%);display:var(--mat-text-button-touch-target-display, block)}.mat-mdc-unelevated-button{transition:box-shadow 280ms cubic-bezier(0.4, 0, 0.2, 1);height:var(--mdc-filled-button-container-height, 40px);font-family:var(--mdc-filled-button-label-text-font, var(--mat-sys-label-large-font));font-size:var(--mdc-filled-button-label-text-size, var(--mat-sys-label-large-size));letter-spacing:var(--mdc-filled-button-label-text-tracking, var(--mat-sys-label-large-tracking));text-transform:var(--mdc-filled-button-label-text-transform);font-weight:var(--mdc-filled-button-label-text-weight, var(--mat-sys-label-large-weight));padding:0 var(--mat-filled-button-horizontal-padding, 24px)}.mat-mdc-unelevated-button>.mat-icon{margin-right:var(--mat-filled-button-icon-spacing, 8px);margin-left:var(--mat-filled-button-icon-offset, -8px)}[dir=rtl] .mat-mdc-unelevated-button>.mat-icon{margin-right:var(--mat-filled-button-icon-offset, -8px);margin-left:var(--mat-filled-button-icon-spacing, 8px)}.mat-mdc-unelevated-button .mdc-button__label+.mat-icon{margin-right:var(--mat-filled-button-icon-offset, -8px);margin-left:var(--mat-filled-button-icon-spacing, 8px)}[dir=rtl] .mat-mdc-unelevated-button .mdc-button__label+.mat-icon{margin-right:var(--mat-filled-button-icon-spacing, 8px);margin-left:var(--mat-filled-button-icon-offset, -8px)}.mat-mdc-unelevated-button .mat-ripple-element{background-color:var(--mat-filled-button-ripple-color, color-mix(in srgb, var(--mat-sys-on-primary) calc(var(--mat-sys-pressed-state-layer-opacity) * 100%), transparent))}.mat-mdc-unelevated-button .mat-mdc-button-persistent-ripple::before{background-color:var(--mat-filled-button-state-layer-color, var(--mat-sys-on-primary))}.mat-mdc-unelevated-button.mat-mdc-button-disabled .mat-mdc-button-persistent-ripple::before{background-color:var(--mat-filled-button-disabled-state-layer-color, var(--mat-sys-on-surface-variant))}.mat-mdc-unelevated-button:hover>.mat-mdc-button-persistent-ripple::before{opacity:var(--mat-filled-button-hover-state-layer-opacity, var(--mat-sys-hover-state-layer-opacity))}.mat-mdc-unelevated-button.cdk-program-focused>.mat-mdc-button-persistent-ripple::before,.mat-mdc-unelevated-button.cdk-keyboard-focused>.mat-mdc-button-persistent-ripple::before,.mat-mdc-unelevated-button.mat-mdc-button-disabled-interactive:focus>.mat-mdc-button-persistent-ripple::before{opacity:var(--mat-filled-button-focus-state-layer-opacity, var(--mat-sys-focus-state-layer-opacity))}.mat-mdc-unelevated-button:active>.mat-mdc-button-persistent-ripple::before{opacity:var(--mat-filled-button-pressed-state-layer-opacity, var(--mat-sys-pressed-state-layer-opacity))}.mat-mdc-unelevated-button .mat-mdc-button-touch-target{position:absolute;top:50%;height:48px;left:0;right:0;transform:translateY(-50%);display:var(--mat-filled-button-touch-target-display, block)}.mat-mdc-unelevated-button:not(:disabled){color:var(--mdc-filled-button-label-text-color, var(--mat-sys-on-primary));background-color:var(--mdc-filled-button-container-color, var(--mat-sys-primary))}.mat-mdc-unelevated-button,.mat-mdc-unelevated-button .mdc-button__ripple{border-radius:var(--mdc-filled-button-container-shape, var(--mat-sys-corner-full))}.mat-mdc-unelevated-button[disabled],.mat-mdc-unelevated-button.mat-mdc-button-disabled{cursor:default;pointer-events:none;color:var(--mdc-filled-button-disabled-label-text-color, color-mix(in srgb, var(--mat-sys-on-surface) 38%, transparent));background-color:var(--mdc-filled-button-disabled-container-color, color-mix(in srgb, var(--mat-sys-on-surface) 12%, transparent))}.mat-mdc-unelevated-button.mat-mdc-button-disabled-interactive{pointer-events:auto}.mat-mdc-raised-button{transition:box-shadow 280ms cubic-bezier(0.4, 0, 0.2, 1);box-shadow:var(--mdc-protected-button-container-elevation-shadow, var(--mat-sys-level1));height:var(--mdc-protected-button-container-height, 40px);font-family:var(--mdc-protected-button-label-text-font, var(--mat-sys-label-large-font));font-size:var(--mdc-protected-button-label-text-size, var(--mat-sys-label-large-size));letter-spacing:var(--mdc-protected-button-label-text-tracking, var(--mat-sys-label-large-tracking));text-transform:var(--mdc-protected-button-label-text-transform);font-weight:var(--mdc-protected-button-label-text-weight, var(--mat-sys-label-large-weight));padding:0 var(--mat-protected-button-horizontal-padding, 24px)}.mat-mdc-raised-button>.mat-icon{margin-right:var(--mat-protected-button-icon-spacing, 8px);margin-left:var(--mat-protected-button-icon-offset, -8px)}[dir=rtl] .mat-mdc-raised-button>.mat-icon{margin-right:var(--mat-protected-button-icon-offset, -8px);margin-left:var(--mat-protected-button-icon-spacing, 8px)}.mat-mdc-raised-button .mdc-button__label+.mat-icon{margin-right:var(--mat-protected-button-icon-offset, -8px);margin-left:var(--mat-protected-button-icon-spacing, 8px)}[dir=rtl] .mat-mdc-raised-button .mdc-button__label+.mat-icon{margin-right:var(--mat-protected-button-icon-spacing, 8px);margin-left:var(--mat-protected-button-icon-offset, -8px)}.mat-mdc-raised-button .mat-ripple-element{background-color:var(--mat-protected-button-ripple-color, color-mix(in srgb, var(--mat-sys-primary) calc(var(--mat-sys-pressed-state-layer-opacity) * 100%), transparent))}.mat-mdc-raised-button .mat-mdc-button-persistent-ripple::before{background-color:var(--mat-protected-button-state-layer-color, var(--mat-sys-primary))}.mat-mdc-raised-button.mat-mdc-button-disabled .mat-mdc-button-persistent-ripple::before{background-color:var(--mat-protected-button-disabled-state-layer-color, var(--mat-sys-on-surface-variant))}.mat-mdc-raised-button:hover>.mat-mdc-button-persistent-ripple::before{opacity:var(--mat-protected-button-hover-state-layer-opacity, var(--mat-sys-hover-state-layer-opacity))}.mat-mdc-raised-button.cdk-program-focused>.mat-mdc-button-persistent-ripple::before,.mat-mdc-raised-button.cdk-keyboard-focused>.mat-mdc-button-persistent-ripple::before,.mat-mdc-raised-button.mat-mdc-button-disabled-interactive:focus>.mat-mdc-button-persistent-ripple::before{opacity:var(--mat-protected-button-focus-state-layer-opacity, var(--mat-sys-focus-state-layer-opacity))}.mat-mdc-raised-button:active>.mat-mdc-button-persistent-ripple::before{opacity:var(--mat-protected-button-pressed-state-layer-opacity, var(--mat-sys-pressed-state-layer-opacity))}.mat-mdc-raised-button .mat-mdc-button-touch-target{position:absolute;top:50%;height:48px;left:0;right:0;transform:translateY(-50%);display:var(--mat-protected-button-touch-target-display, block)}.mat-mdc-raised-button:not(:disabled){color:var(--mdc-protected-button-label-text-color, var(--mat-sys-primary));background-color:var(--mdc-protected-button-container-color, var(--mat-sys-surface))}.mat-mdc-raised-button,.mat-mdc-raised-button .mdc-button__ripple{border-radius:var(--mdc-protected-button-container-shape, var(--mat-sys-corner-full))}.mat-mdc-raised-button:hover{box-shadow:var(--mdc-protected-button-hover-container-elevation-shadow, var(--mat-sys-level2))}.mat-mdc-raised-button:focus{box-shadow:var(--mdc-protected-button-focus-container-elevation-shadow, var(--mat-sys-level1))}.mat-mdc-raised-button:active,.mat-mdc-raised-button:focus:active{box-shadow:var(--mdc-protected-button-pressed-container-elevation-shadow, var(--mat-sys-level1))}.mat-mdc-raised-button[disabled],.mat-mdc-raised-button.mat-mdc-button-disabled{cursor:default;pointer-events:none;color:var(--mdc-protected-button-disabled-label-text-color, color-mix(in srgb, var(--mat-sys-on-surface) 38%, transparent));background-color:var(--mdc-protected-button-disabled-container-color, color-mix(in srgb, var(--mat-sys-on-surface) 12%, transparent))}.mat-mdc-raised-button[disabled].mat-mdc-button-disabled,.mat-mdc-raised-button.mat-mdc-button-disabled.mat-mdc-button-disabled{box-shadow:var(--mdc-protected-button-disabled-container-elevation-shadow, var(--mat-sys-level0))}.mat-mdc-raised-button.mat-mdc-button-disabled-interactive{pointer-events:auto}.mat-mdc-outlined-button{border-style:solid;transition:border 280ms cubic-bezier(0.4, 0, 0.2, 1);height:var(--mdc-outlined-button-container-height, 40px);font-family:var(--mdc-outlined-button-label-text-font, var(--mat-sys-label-large-font));font-size:var(--mdc-outlined-button-label-text-size, var(--mat-sys-label-large-size));letter-spacing:var(--mdc-outlined-button-label-text-tracking, var(--mat-sys-label-large-tracking));text-transform:var(--mdc-outlined-button-label-text-transform);font-weight:var(--mdc-outlined-button-label-text-weight, var(--mat-sys-label-large-weight));border-radius:var(--mdc-outlined-button-container-shape, var(--mat-sys-corner-full));border-width:var(--mdc-outlined-button-outline-width, 1px);padding:0 var(--mat-outlined-button-horizontal-padding, 24px)}.mat-mdc-outlined-button>.mat-icon{margin-right:var(--mat-outlined-button-icon-spacing, 8px);margin-left:var(--mat-outlined-button-icon-offset, -8px)}[dir=rtl] .mat-mdc-outlined-button>.mat-icon{margin-right:var(--mat-outlined-button-icon-offset, -8px);margin-left:var(--mat-outlined-button-icon-spacing, 8px)}.mat-mdc-outlined-button .mdc-button__label+.mat-icon{margin-right:var(--mat-outlined-button-icon-offset, -8px);margin-left:var(--mat-outlined-button-icon-spacing, 8px)}[dir=rtl] .mat-mdc-outlined-button .mdc-button__label+.mat-icon{margin-right:var(--mat-outlined-button-icon-spacing, 8px);margin-left:var(--mat-outlined-button-icon-offset, -8px)}.mat-mdc-outlined-button .mat-ripple-element{background-color:var(--mat-outlined-button-ripple-color, color-mix(in srgb, var(--mat-sys-primary) calc(var(--mat-sys-pressed-state-layer-opacity) * 100%), transparent))}.mat-mdc-outlined-button .mat-mdc-button-persistent-ripple::before{background-color:var(--mat-outlined-button-state-layer-color, var(--mat-sys-primary))}.mat-mdc-outlined-button.mat-mdc-button-disabled .mat-mdc-button-persistent-ripple::before{background-color:var(--mat-outlined-button-disabled-state-layer-color, var(--mat-sys-on-surface-variant))}.mat-mdc-outlined-button:hover>.mat-mdc-button-persistent-ripple::before{opacity:var(--mat-outlined-button-hover-state-layer-opacity, var(--mat-sys-hover-state-layer-opacity))}.mat-mdc-outlined-button.cdk-program-focused>.mat-mdc-button-persistent-ripple::before,.mat-mdc-outlined-button.cdk-keyboard-focused>.mat-mdc-button-persistent-ripple::before,.mat-mdc-outlined-button.mat-mdc-button-disabled-interactive:focus>.mat-mdc-button-persistent-ripple::before{opacity:var(--mat-outlined-button-focus-state-layer-opacity, var(--mat-sys-focus-state-layer-opacity))}.mat-mdc-outlined-button:active>.mat-mdc-button-persistent-ripple::before{opacity:var(--mat-outlined-button-pressed-state-layer-opacity, var(--mat-sys-pressed-state-layer-opacity))}.mat-mdc-outlined-button .mat-mdc-button-touch-target{position:absolute;top:50%;height:48px;left:0;right:0;transform:translateY(-50%);display:var(--mat-outlined-button-touch-target-display, block)}.mat-mdc-outlined-button:not(:disabled){color:var(--mdc-outlined-button-label-text-color, var(--mat-sys-primary));border-color:var(--mdc-outlined-button-outline-color, var(--mat-sys-outline))}.mat-mdc-outlined-button[disabled],.mat-mdc-outlined-button.mat-mdc-button-disabled{cursor:default;pointer-events:none;color:var(--mdc-outlined-button-disabled-label-text-color, color-mix(in srgb, var(--mat-sys-on-surface) 38%, transparent));border-color:var(--mdc-outlined-button-disabled-outline-color, color-mix(in srgb, var(--mat-sys-on-surface) 12%, transparent))}.mat-mdc-outlined-button.mat-mdc-button-disabled-interactive{pointer-events:auto}.mat-mdc-button,.mat-mdc-unelevated-button,.mat-mdc-raised-button,.mat-mdc-outlined-button{-webkit-tap-highlight-color:rgba(0,0,0,0)}.mat-mdc-button .mat-mdc-button-ripple,.mat-mdc-button .mat-mdc-button-persistent-ripple,.mat-mdc-button .mat-mdc-button-persistent-ripple::before,.mat-mdc-unelevated-button .mat-mdc-button-ripple,.mat-mdc-unelevated-button .mat-mdc-button-persistent-ripple,.mat-mdc-unelevated-button .mat-mdc-button-persistent-ripple::before,.mat-mdc-raised-button .mat-mdc-button-ripple,.mat-mdc-raised-button .mat-mdc-button-persistent-ripple,.mat-mdc-raised-button .mat-mdc-button-persistent-ripple::before,.mat-mdc-outlined-button .mat-mdc-button-ripple,.mat-mdc-outlined-button .mat-mdc-button-persistent-ripple,.mat-mdc-outlined-button .mat-mdc-button-persistent-ripple::before{top:0;left:0;right:0;bottom:0;position:absolute;pointer-events:none;border-radius:inherit}.mat-mdc-button .mat-mdc-button-ripple,.mat-mdc-unelevated-button .mat-mdc-button-ripple,.mat-mdc-raised-button .mat-mdc-button-ripple,.mat-mdc-outlined-button .mat-mdc-button-ripple{overflow:hidden}.mat-mdc-button .mat-mdc-button-persistent-ripple::before,.mat-mdc-unelevated-button .mat-mdc-button-persistent-ripple::before,.mat-mdc-raised-button .mat-mdc-button-persistent-ripple::before,.mat-mdc-outlined-button .mat-mdc-button-persistent-ripple::before{content:\\\"\\\";opacity:0}.mat-mdc-button .mdc-button__label,.mat-mdc-button .mat-icon,.mat-mdc-unelevated-button .mdc-button__label,.mat-mdc-unelevated-button .mat-icon,.mat-mdc-raised-button .mdc-button__label,.mat-mdc-raised-button .mat-icon,.mat-mdc-outlined-button .mdc-button__label,.mat-mdc-outlined-button .mat-icon{z-index:1;position:relative}.mat-mdc-button .mat-focus-indicator,.mat-mdc-unelevated-button .mat-focus-indicator,.mat-mdc-raised-button .mat-focus-indicator,.mat-mdc-outlined-button .mat-focus-indicator{top:0;left:0;right:0;bottom:0;position:absolute}.mat-mdc-button:focus>.mat-focus-indicator::before,.mat-mdc-unelevated-button:focus>.mat-focus-indicator::before,.mat-mdc-raised-button:focus>.mat-focus-indicator::before,.mat-mdc-outlined-button:focus>.mat-focus-indicator::before{content:\\\"\\\"}.mat-mdc-button._mat-animation-noopable,.mat-mdc-unelevated-button._mat-animation-noopable,.mat-mdc-raised-button._mat-animation-noopable,.mat-mdc-outlined-button._mat-animation-noopable{transition:none !important;animation:none !important}.mat-mdc-button>.mat-icon,.mat-mdc-unelevated-button>.mat-icon,.mat-mdc-raised-button>.mat-icon,.mat-mdc-outlined-button>.mat-icon{display:inline-block;position:relative;vertical-align:top;font-size:1.125rem;height:1.125rem;width:1.125rem}.mat-mdc-outlined-button .mat-mdc-button-ripple,.mat-mdc-outlined-button .mdc-button__ripple{top:-1px;left:-1px;bottom:-1px;right:-1px}.mat-mdc-unelevated-button .mat-focus-indicator::before,.mat-mdc-raised-button .mat-focus-indicator::before{margin:calc(calc(var(--mat-focus-indicator-border-width, 3px) + 2px)*-1)}.mat-mdc-outlined-button .mat-focus-indicator::before{margin:calc(calc(var(--mat-focus-indicator-border-width, 3px) + 3px)*-1)}\";\nconst _c4 = \"@media(forced-colors: active){.mat-mdc-button:not(.mdc-button--outlined),.mat-mdc-unelevated-button:not(.mdc-button--outlined),.mat-mdc-raised-button:not(.mdc-button--outlined),.mat-mdc-outlined-button:not(.mdc-button--outlined),.mat-mdc-icon-button.mat-mdc-icon-button,.mat-mdc-outlined-button .mdc-button__ripple{outline:solid 1px}}\";\nconst _c5 = [\"mat-fab\", \"\"];\nconst _c6 = [\"mat-mini-fab\", \"\"];\nconst _c7 = \".mat-mdc-fab-base{-webkit-user-select:none;user-select:none;position:relative;display:inline-flex;align-items:center;justify-content:center;box-sizing:border-box;width:56px;height:56px;padding:0;border:none;fill:currentColor;text-decoration:none;cursor:pointer;-moz-appearance:none;-webkit-appearance:none;overflow:visible;transition:box-shadow 280ms cubic-bezier(0.4, 0, 0.2, 1),opacity 15ms linear 30ms,transform 270ms 0ms cubic-bezier(0, 0, 0.2, 1);flex-shrink:0;-webkit-tap-highlight-color:rgba(0,0,0,0)}.mat-mdc-fab-base .mat-mdc-button-ripple,.mat-mdc-fab-base .mat-mdc-button-persistent-ripple,.mat-mdc-fab-base .mat-mdc-button-persistent-ripple::before{top:0;left:0;right:0;bottom:0;position:absolute;pointer-events:none;border-radius:inherit}.mat-mdc-fab-base .mat-mdc-button-ripple{overflow:hidden}.mat-mdc-fab-base .mat-mdc-button-persistent-ripple::before{content:\\\"\\\";opacity:0}.mat-mdc-fab-base .mdc-button__label,.mat-mdc-fab-base .mat-icon{z-index:1;position:relative}.mat-mdc-fab-base .mat-focus-indicator{top:0;left:0;right:0;bottom:0;position:absolute}.mat-mdc-fab-base:focus>.mat-focus-indicator::before{content:\\\"\\\"}.mat-mdc-fab-base._mat-animation-noopable{transition:none !important;animation:none !important}.mat-mdc-fab-base::before{position:absolute;box-sizing:border-box;width:100%;height:100%;top:0;left:0;border:1px solid rgba(0,0,0,0);border-radius:inherit;content:\\\"\\\";pointer-events:none}.mat-mdc-fab-base[hidden]{display:none}.mat-mdc-fab-base::-moz-focus-inner{padding:0;border:0}.mat-mdc-fab-base:active,.mat-mdc-fab-base:focus{outline:none}.mat-mdc-fab-base:hover{cursor:pointer}.mat-mdc-fab-base>svg{width:100%}.mat-mdc-fab-base .mat-icon,.mat-mdc-fab-base .material-icons{transition:transform 180ms 90ms cubic-bezier(0, 0, 0.2, 1);fill:currentColor;will-change:transform}.mat-mdc-fab-base .mat-focus-indicator::before{margin:calc(calc(var(--mat-focus-indicator-border-width, 3px) + 2px)*-1)}.mat-mdc-fab-base[disabled],.mat-mdc-fab-base.mat-mdc-button-disabled{cursor:default;pointer-events:none}.mat-mdc-fab-base[disabled],.mat-mdc-fab-base[disabled]:focus,.mat-mdc-fab-base.mat-mdc-button-disabled,.mat-mdc-fab-base.mat-mdc-button-disabled:focus{box-shadow:none}.mat-mdc-fab-base.mat-mdc-button-disabled-interactive{pointer-events:auto}.mat-mdc-fab{background-color:var(--mdc-fab-container-color, var(--mat-sys-primary-container));border-radius:var(--mdc-fab-container-shape, var(--mat-sys-corner-large));color:var(--mat-fab-foreground-color, var(--mat-sys-on-primary-container, inherit));box-shadow:var(--mdc-fab-container-elevation-shadow, var(--mat-sys-level3))}.mat-mdc-fab:hover{box-shadow:var(--mdc-fab-hover-container-elevation-shadow, var(--mat-sys-level4))}.mat-mdc-fab:focus{box-shadow:var(--mdc-fab-focus-container-elevation-shadow, var(--mat-sys-level3))}.mat-mdc-fab:active,.mat-mdc-fab:focus:active{box-shadow:var(--mdc-fab-pressed-container-elevation-shadow, var(--mat-sys-level3))}.mat-mdc-fab[disabled],.mat-mdc-fab.mat-mdc-button-disabled{cursor:default;pointer-events:none;color:var(--mat-fab-disabled-state-foreground-color, color-mix(in srgb, var(--mat-sys-on-surface) 38%, transparent));background-color:var(--mat-fab-disabled-state-container-color, color-mix(in srgb, var(--mat-sys-on-surface) 12%, transparent))}.mat-mdc-fab.mat-mdc-button-disabled-interactive{pointer-events:auto}.mat-mdc-fab .mat-mdc-button-touch-target{position:absolute;top:50%;height:48px;left:50%;width:48px;transform:translate(-50%, -50%);display:var(--mat-fab-touch-target-display, block)}.mat-mdc-fab .mat-ripple-element{background-color:var(--mat-fab-ripple-color, color-mix(in srgb, var(--mat-sys-on-primary-container) calc(var(--mat-sys-pressed-state-layer-opacity) * 100%), transparent))}.mat-mdc-fab .mat-mdc-button-persistent-ripple::before{background-color:var(--mat-fab-state-layer-color, var(--mat-sys-on-primary-container))}.mat-mdc-fab.mat-mdc-button-disabled .mat-mdc-button-persistent-ripple::before{background-color:var(--mat-fab-disabled-state-layer-color)}.mat-mdc-fab:hover>.mat-mdc-button-persistent-ripple::before{opacity:var(--mat-fab-hover-state-layer-opacity, var(--mat-sys-hover-state-layer-opacity))}.mat-mdc-fab.cdk-program-focused>.mat-mdc-button-persistent-ripple::before,.mat-mdc-fab.cdk-keyboard-focused>.mat-mdc-button-persistent-ripple::before,.mat-mdc-fab.mat-mdc-button-disabled-interactive:focus>.mat-mdc-button-persistent-ripple::before{opacity:var(--mat-fab-focus-state-layer-opacity, var(--mat-sys-focus-state-layer-opacity))}.mat-mdc-fab:active>.mat-mdc-button-persistent-ripple::before{opacity:var(--mat-fab-pressed-state-layer-opacity, var(--mat-sys-pressed-state-layer-opacity))}.mat-mdc-mini-fab{width:40px;height:40px;background-color:var(--mdc-fab-small-container-color, var(--mat-sys-primary-container));border-radius:var(--mdc-fab-small-container-shape, var(--mat-sys-corner-medium));color:var(--mat-fab-small-foreground-color, var(--mat-sys-on-primary-container, inherit));box-shadow:var(--mdc-fab-small-container-elevation-shadow, var(--mat-sys-level3))}.mat-mdc-mini-fab:hover{box-shadow:var(--mdc-fab-small-hover-container-elevation-shadow, var(--mat-sys-level4))}.mat-mdc-mini-fab:focus{box-shadow:var(--mdc-fab-small-focus-container-elevation-shadow, var(--mat-sys-level3))}.mat-mdc-mini-fab:active,.mat-mdc-mini-fab:focus:active{box-shadow:var(--mdc-fab-small-pressed-container-elevation-shadow, var(--mat-sys-level3))}.mat-mdc-mini-fab[disabled],.mat-mdc-mini-fab.mat-mdc-button-disabled{cursor:default;pointer-events:none;color:var(--mat-fab-small-disabled-state-foreground-color, color-mix(in srgb, var(--mat-sys-on-surface) 38%, transparent));background-color:var(--mat-fab-small-disabled-state-container-color, color-mix(in srgb, var(--mat-sys-on-surface) 12%, transparent))}.mat-mdc-mini-fab.mat-mdc-button-disabled-interactive{pointer-events:auto}.mat-mdc-mini-fab .mat-mdc-button-touch-target{position:absolute;top:50%;height:48px;left:50%;width:48px;transform:translate(-50%, -50%);display:var(--mat-fab-small-touch-target-display)}.mat-mdc-mini-fab .mat-ripple-element{background-color:var(--mat-fab-small-ripple-color, color-mix(in srgb, var(--mat-sys-on-primary-container) calc(var(--mat-sys-pressed-state-layer-opacity) * 100%), transparent))}.mat-mdc-mini-fab .mat-mdc-button-persistent-ripple::before{background-color:var(--mat-fab-small-state-layer-color, var(--mat-sys-on-primary-container))}.mat-mdc-mini-fab.mat-mdc-button-disabled .mat-mdc-button-persistent-ripple::before{background-color:var(--mat-fab-small-disabled-state-layer-color)}.mat-mdc-mini-fab:hover>.mat-mdc-button-persistent-ripple::before{opacity:var(--mat-fab-small-hover-state-layer-opacity, var(--mat-sys-hover-state-layer-opacity))}.mat-mdc-mini-fab.cdk-program-focused>.mat-mdc-button-persistent-ripple::before,.mat-mdc-mini-fab.cdk-keyboard-focused>.mat-mdc-button-persistent-ripple::before,.mat-mdc-mini-fab.mat-mdc-button-disabled-interactive:focus>.mat-mdc-button-persistent-ripple::before{opacity:var(--mat-fab-small-focus-state-layer-opacity, var(--mat-sys-focus-state-layer-opacity))}.mat-mdc-mini-fab:active>.mat-mdc-button-persistent-ripple::before{opacity:var(--mat-fab-small-pressed-state-layer-opacity, var(--mat-sys-pressed-state-layer-opacity))}.mat-mdc-extended-fab{-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;border-radius:24px;padding-left:20px;padding-right:20px;width:auto;max-width:100%;line-height:normal;height:var(--mdc-extended-fab-container-height, 56px);border-radius:var(--mdc-extended-fab-container-shape, var(--mat-sys-corner-large));font-family:var(--mdc-extended-fab-label-text-font, var(--mat-sys-label-large-font));font-size:var(--mdc-extended-fab-label-text-size, var(--mat-sys-label-large-size));font-weight:var(--mdc-extended-fab-label-text-weight, var(--mat-sys-label-large-weight));letter-spacing:var(--mdc-extended-fab-label-text-tracking, var(--mat-sys-label-large-tracking));box-shadow:var(--mdc-extended-fab-container-elevation-shadow, var(--mat-sys-level3))}.mat-mdc-extended-fab:hover{box-shadow:var(--mdc-extended-fab-hover-container-elevation-shadow, var(--mat-sys-level4))}.mat-mdc-extended-fab:focus{box-shadow:var(--mdc-extended-fab-focus-container-elevation-shadow, var(--mat-sys-level3))}.mat-mdc-extended-fab:active,.mat-mdc-extended-fab:focus:active{box-shadow:var(--mdc-extended-fab-pressed-container-elevation-shadow, var(--mat-sys-level3))}.mat-mdc-extended-fab[disabled],.mat-mdc-extended-fab.mat-mdc-button-disabled{cursor:default;pointer-events:none}.mat-mdc-extended-fab[disabled],.mat-mdc-extended-fab[disabled]:focus,.mat-mdc-extended-fab.mat-mdc-button-disabled,.mat-mdc-extended-fab.mat-mdc-button-disabled:focus{box-shadow:none}.mat-mdc-extended-fab.mat-mdc-button-disabled-interactive{pointer-events:auto}[dir=rtl] .mat-mdc-extended-fab .mdc-button__label+.mat-icon,[dir=rtl] .mat-mdc-extended-fab .mdc-button__label+.material-icons,.mat-mdc-extended-fab>.mat-icon,.mat-mdc-extended-fab>.material-icons{margin-left:-8px;margin-right:12px}.mat-mdc-extended-fab .mdc-button__label+.mat-icon,.mat-mdc-extended-fab .mdc-button__label+.material-icons,[dir=rtl] .mat-mdc-extended-fab>.mat-icon,[dir=rtl] .mat-mdc-extended-fab>.material-icons{margin-left:12px;margin-right:-8px}.mat-mdc-extended-fab .mat-mdc-button-touch-target{width:100%}\";\nconst _c8 = [\"mat-icon-button\", \"\"];\nconst _c9 = [\"*\"];\nconst _c10 = \".mat-mdc-icon-button{-webkit-user-select:none;user-select:none;display:inline-block;position:relative;box-sizing:border-box;border:none;outline:none;background-color:rgba(0,0,0,0);fill:currentColor;color:inherit;text-decoration:none;cursor:pointer;z-index:0;overflow:visible;border-radius:50%;flex-shrink:0;text-align:center;width:var(--mdc-icon-button-state-layer-size, 40px);height:var(--mdc-icon-button-state-layer-size, 40px);padding:calc(calc(var(--mdc-icon-button-state-layer-size, 40px) - var(--mdc-icon-button-icon-size, 24px)) / 2);font-size:var(--mdc-icon-button-icon-size, 24px);color:var(--mdc-icon-button-icon-color, var(--mat-sys-on-surface-variant));-webkit-tap-highlight-color:rgba(0,0,0,0)}.mat-mdc-icon-button .mat-mdc-button-ripple,.mat-mdc-icon-button .mat-mdc-button-persistent-ripple,.mat-mdc-icon-button .mat-mdc-button-persistent-ripple::before{top:0;left:0;right:0;bottom:0;position:absolute;pointer-events:none;border-radius:inherit}.mat-mdc-icon-button .mat-mdc-button-ripple{overflow:hidden}.mat-mdc-icon-button .mat-mdc-button-persistent-ripple::before{content:\\\"\\\";opacity:0}.mat-mdc-icon-button .mdc-button__label,.mat-mdc-icon-button .mat-icon{z-index:1;position:relative}.mat-mdc-icon-button .mat-focus-indicator{top:0;left:0;right:0;bottom:0;position:absolute}.mat-mdc-icon-button:focus>.mat-focus-indicator::before{content:\\\"\\\"}.mat-mdc-icon-button .mat-ripple-element{background-color:var(--mat-icon-button-ripple-color, color-mix(in srgb, var(--mat-sys-on-surface-variant) calc(var(--mat-sys-pressed-state-layer-opacity) * 100%), transparent))}.mat-mdc-icon-button .mat-mdc-button-persistent-ripple::before{background-color:var(--mat-icon-button-state-layer-color, var(--mat-sys-on-surface-variant))}.mat-mdc-icon-button.mat-mdc-button-disabled .mat-mdc-button-persistent-ripple::before{background-color:var(--mat-icon-button-disabled-state-layer-color, var(--mat-sys-on-surface-variant))}.mat-mdc-icon-button:hover>.mat-mdc-button-persistent-ripple::before{opacity:var(--mat-icon-button-hover-state-layer-opacity, var(--mat-sys-hover-state-layer-opacity))}.mat-mdc-icon-button.cdk-program-focused>.mat-mdc-button-persistent-ripple::before,.mat-mdc-icon-button.cdk-keyboard-focused>.mat-mdc-button-persistent-ripple::before,.mat-mdc-icon-button.mat-mdc-button-disabled-interactive:focus>.mat-mdc-button-persistent-ripple::before{opacity:var(--mat-icon-button-focus-state-layer-opacity, var(--mat-sys-focus-state-layer-opacity))}.mat-mdc-icon-button:active>.mat-mdc-button-persistent-ripple::before{opacity:var(--mat-icon-button-pressed-state-layer-opacity, var(--mat-sys-pressed-state-layer-opacity))}.mat-mdc-icon-button .mat-mdc-button-touch-target{position:absolute;top:50%;height:48px;left:50%;width:48px;transform:translate(-50%, -50%);display:var(--mat-icon-button-touch-target-display, block)}.mat-mdc-icon-button._mat-animation-noopable{transition:none !important;animation:none !important}.mat-mdc-icon-button[disabled],.mat-mdc-icon-button.mat-mdc-button-disabled{cursor:default;pointer-events:none;color:var(--mdc-icon-button-disabled-icon-color, color-mix(in srgb, var(--mat-sys-on-surface) 38%, transparent))}.mat-mdc-icon-button.mat-mdc-button-disabled-interactive{pointer-events:auto}.mat-mdc-icon-button img,.mat-mdc-icon-button svg{width:var(--mdc-icon-button-icon-size, 24px);height:var(--mdc-icon-button-icon-size, 24px);vertical-align:baseline}.mat-mdc-icon-button .mat-mdc-button-persistent-ripple{border-radius:50%}.mat-mdc-icon-button[hidden]{display:none}.mat-mdc-icon-button.mat-unthemed:not(.mdc-ripple-upgraded):focus::before,.mat-mdc-icon-button.mat-primary:not(.mdc-ripple-upgraded):focus::before,.mat-mdc-icon-button.mat-accent:not(.mdc-ripple-upgraded):focus::before,.mat-mdc-icon-button.mat-warn:not(.mdc-ripple-upgraded):focus::before{background:rgba(0,0,0,0);opacity:1}\";\nconst MAT_BUTTON_CONFIG = /*#__PURE__*/new InjectionToken('MAT_BUTTON_CONFIG');\n/** Shared host configuration for all buttons */\nconst MAT_BUTTON_HOST = {\n '[attr.disabled]': '_getDisabledAttribute()',\n '[attr.aria-disabled]': '_getAriaDisabled()',\n '[class.mat-mdc-button-disabled]': 'disabled',\n '[class.mat-mdc-button-disabled-interactive]': 'disabledInteractive',\n '[class._mat-animation-noopable]': '_animationMode === \"NoopAnimations\"',\n // MDC automatically applies the primary theme color to the button, but we want to support\n // an unthemed version. If color is undefined, apply a CSS class that makes it easy to\n // select and style this \"theme\".\n '[class.mat-unthemed]': '!color',\n // Add a class that applies to all buttons. This makes it easier to target if somebody\n // wants to target all Material buttons.\n '[class.mat-mdc-button-base]': 'true',\n '[class]': 'color ? \"mat-\" + color : \"\"'\n};\n/** List of classes to add to buttons instances based on host attribute selector. */\nconst HOST_SELECTOR_MDC_CLASS_PAIR = [{\n attribute: 'mat-button',\n mdcClasses: ['mdc-button', 'mat-mdc-button']\n}, {\n attribute: 'mat-flat-button',\n mdcClasses: ['mdc-button', 'mdc-button--unelevated', 'mat-mdc-unelevated-button']\n}, {\n attribute: 'mat-raised-button',\n mdcClasses: ['mdc-button', 'mdc-button--raised', 'mat-mdc-raised-button']\n}, {\n attribute: 'mat-stroked-button',\n mdcClasses: ['mdc-button', 'mdc-button--outlined', 'mat-mdc-outlined-button']\n}, {\n attribute: 'mat-fab',\n mdcClasses: ['mdc-fab', 'mat-mdc-fab-base', 'mat-mdc-fab']\n}, {\n attribute: 'mat-mini-fab',\n mdcClasses: ['mdc-fab', 'mat-mdc-fab-base', 'mdc-fab--mini', 'mat-mdc-mini-fab']\n}, {\n attribute: 'mat-icon-button',\n mdcClasses: ['mdc-icon-button', 'mat-mdc-icon-button']\n}];\n/** Base class for all buttons. */\nlet MatButtonBase = /*#__PURE__*/(() => {\n class MatButtonBase {\n _elementRef = inject(ElementRef);\n _ngZone = inject(NgZone);\n _animationMode = inject(ANIMATION_MODULE_TYPE, {\n optional: true\n });\n _focusMonitor = inject(FocusMonitor);\n /**\n * Handles the lazy creation of the MatButton ripple.\n * Used to improve initial load time of large applications.\n */\n _rippleLoader = inject(MatRippleLoader);\n /** Whether this button is a FAB. Used to apply the correct class on the ripple. */\n _isFab = false;\n /**\n * Theme color of the button. This API is supported in M2 themes only, it has\n * no effect in M3 themes. For color customization in M3, see https://material.angular.io/components/button/styling.\n *\n * For information on applying color variants in M3, see\n * https://material.angular.io/guide/material-2-theming#optional-add-backwards-compatibility-styles-for-color-variants\n */\n color;\n /** Whether the ripple effect is disabled or not. */\n get disableRipple() {\n return this._disableRipple;\n }\n set disableRipple(value) {\n this._disableRipple = value;\n this._updateRippleDisabled();\n }\n _disableRipple = false;\n /** Whether the button is disabled. */\n get disabled() {\n return this._disabled;\n }\n set disabled(value) {\n this._disabled = value;\n this._updateRippleDisabled();\n }\n _disabled = false;\n /** `aria-disabled` value of the button. */\n ariaDisabled;\n /**\n * Natively disabled buttons prevent focus and any pointer events from reaching the button.\n * In some scenarios this might not be desirable, because it can prevent users from finding out\n * why the button is disabled (e.g. via tooltip).\n *\n * Enabling this input will change the button so that it is styled to be disabled and will be\n * marked as `aria-disabled`, but it will allow the button to receive events and focus.\n *\n * Note that by enabling this, you need to set the `tabindex` yourself if the button isn't\n * meant to be tabbable and you have to prevent the button action (e.g. form submissions).\n */\n disabledInteractive;\n constructor() {\n inject(_CdkPrivateStyleLoader).load(_StructuralStylesLoader);\n const config = inject(MAT_BUTTON_CONFIG, {\n optional: true\n });\n const element = this._elementRef.nativeElement;\n const classList = element.classList;\n this.disabledInteractive = config?.disabledInteractive ?? false;\n this.color = config?.color ?? null;\n this._rippleLoader?.configureRipple(element, {\n className: 'mat-mdc-button-ripple'\n });\n // For each of the variant selectors that is present in the button's host\n // attributes, add the correct corresponding MDC classes.\n for (const {\n attribute,\n mdcClasses\n } of HOST_SELECTOR_MDC_CLASS_PAIR) {\n if (element.hasAttribute(attribute)) {\n classList.add(...mdcClasses);\n }\n }\n }\n ngAfterViewInit() {\n this._focusMonitor.monitor(this._elementRef, true);\n }\n ngOnDestroy() {\n this._focusMonitor.stopMonitoring(this._elementRef);\n this._rippleLoader?.destroyRipple(this._elementRef.nativeElement);\n }\n /** Focuses the button. */\n focus(origin = 'program', options) {\n if (origin) {\n this._focusMonitor.focusVia(this._elementRef.nativeElement, origin, options);\n } else {\n this._elementRef.nativeElement.focus(options);\n }\n }\n _getAriaDisabled() {\n if (this.ariaDisabled != null) {\n return this.ariaDisabled;\n }\n return this.disabled && this.disabledInteractive ? true : null;\n }\n _getDisabledAttribute() {\n return this.disabledInteractive || !this.disabled ? null : true;\n }\n _updateRippleDisabled() {\n this._rippleLoader?.setDisabled(this._elementRef.nativeElement, this.disableRipple || this.disabled);\n }\n static ɵfac = function MatButtonBase_Factory(__ngFactoryType__) {\n return new (__ngFactoryType__ || MatButtonBase)();\n };\n static ɵdir = /* @__PURE__ */i0.ɵɵdefineDirective({\n type: MatButtonBase,\n inputs: {\n color: \"color\",\n disableRipple: [2, \"disableRipple\", \"disableRipple\", booleanAttribute],\n disabled: [2, \"disabled\", \"disabled\", booleanAttribute],\n ariaDisabled: [2, \"aria-disabled\", \"ariaDisabled\", booleanAttribute],\n disabledInteractive: [2, \"disabledInteractive\", \"disabledInteractive\", booleanAttribute]\n }\n });\n }\n return MatButtonBase;\n})();\n/*#__PURE__*/(() => {\n (typeof ngDevMode === \"undefined\" || ngDevMode) && void 0;\n})();\n/** Shared host configuration for buttons using the `` tag. */\nconst MAT_ANCHOR_HOST = {\n // Note that this is basically a noop on anchors,\n // but it appears that some internal apps depend on it.\n '[attr.disabled]': '_getDisabledAttribute()',\n '[class.mat-mdc-button-disabled]': 'disabled',\n '[class.mat-mdc-button-disabled-interactive]': 'disabledInteractive',\n '[class._mat-animation-noopable]': '_animationMode === \"NoopAnimations\"',\n // Note that we ignore the user-specified tabindex when it's disabled for\n // consistency with the `mat-button` applied on native buttons where even\n // though they have an index, they're not tabbable.\n '[attr.tabindex]': 'disabled && !disabledInteractive ? -1 : tabIndex',\n '[attr.aria-disabled]': '_getAriaDisabled()',\n // MDC automatically applies the primary theme color to the button, but we want to support\n // an unthemed version. If color is undefined, apply a CSS class that makes it easy to\n // select and style this \"theme\".\n '[class.mat-unthemed]': '!color',\n // Add a class that applies to all buttons. This makes it easier to target if somebody\n // wants to target all Material buttons.\n '[class.mat-mdc-button-base]': 'true',\n '[class]': 'color ? \"mat-\" + color : \"\"'\n};\n/**\n * Anchor button base.\n */\nlet MatAnchorBase = /*#__PURE__*/(() => {\n class MatAnchorBase extends MatButtonBase {\n _renderer = inject(Renderer2);\n _cleanupClick;\n tabIndex;\n ngOnInit() {\n this._ngZone.runOutsideAngular(() => {\n this._cleanupClick = this._renderer.listen(this._elementRef.nativeElement, 'click', this._haltDisabledEvents);\n });\n }\n ngOnDestroy() {\n super.ngOnDestroy();\n this._cleanupClick?.();\n }\n _haltDisabledEvents = event => {\n // A disabled button shouldn't apply any actions\n if (this.disabled) {\n event.preventDefault();\n event.stopImmediatePropagation();\n }\n };\n _getAriaDisabled() {\n if (this.ariaDisabled != null) {\n return this.ariaDisabled;\n }\n return this.disabled || null;\n }\n static ɵfac = /* @__PURE__ */(() => {\n let ɵMatAnchorBase_BaseFactory;\n return function MatAnchorBase_Factory(__ngFactoryType__) {\n return (ɵMatAnchorBase_BaseFactory || (ɵMatAnchorBase_BaseFactory = i0.ɵɵgetInheritedFactory(MatAnchorBase)))(__ngFactoryType__ || MatAnchorBase);\n };\n })();\n static ɵdir = /* @__PURE__ */i0.ɵɵdefineDirective({\n type: MatAnchorBase,\n inputs: {\n tabIndex: [2, \"tabIndex\", \"tabIndex\", value => {\n return value == null ? undefined : numberAttribute(value);\n }]\n },\n features: [i0.ɵɵInheritDefinitionFeature]\n });\n }\n return MatAnchorBase;\n})();\n/*#__PURE__*/(() => {\n (typeof ngDevMode === \"undefined\" || ngDevMode) && void 0;\n})();\n\n/**\n * Material Design button component. Users interact with a button to perform an action.\n * See https://material.io/components/buttons\n *\n * The `MatButton` class applies to native button elements and captures the appearances for\n * \"text button\", \"outlined button\", and \"contained button\" per the Material Design\n * specification. `MatButton` additionally captures an additional \"flat\" appearance, which matches\n * \"contained\" but without elevation.\n */\nlet MatButton = /*#__PURE__*/(() => {\n class MatButton extends MatButtonBase {\n static ɵfac = /* @__PURE__ */(() => {\n let ɵMatButton_BaseFactory;\n return function MatButton_Factory(__ngFactoryType__) {\n return (ɵMatButton_BaseFactory || (ɵMatButton_BaseFactory = i0.ɵɵgetInheritedFactory(MatButton)))(__ngFactoryType__ || MatButton);\n };\n })();\n static ɵcmp = /* @__PURE__ */i0.ɵɵdefineComponent({\n type: MatButton,\n selectors: [[\"button\", \"mat-button\", \"\"], [\"button\", \"mat-raised-button\", \"\"], [\"button\", \"mat-flat-button\", \"\"], [\"button\", \"mat-stroked-button\", \"\"]],\n hostVars: 14,\n hostBindings: function MatButton_HostBindings(rf, ctx) {\n if (rf & 2) {\n i0.ɵɵattribute(\"disabled\", ctx._getDisabledAttribute())(\"aria-disabled\", ctx._getAriaDisabled());\n i0.ɵɵclassMap(ctx.color ? \"mat-\" + ctx.color : \"\");\n i0.ɵɵclassProp(\"mat-mdc-button-disabled\", ctx.disabled)(\"mat-mdc-button-disabled-interactive\", ctx.disabledInteractive)(\"_mat-animation-noopable\", ctx._animationMode === \"NoopAnimations\")(\"mat-unthemed\", !ctx.color)(\"mat-mdc-button-base\", true);\n }\n },\n exportAs: [\"matButton\"],\n features: [i0.ɵɵInheritDefinitionFeature],\n attrs: _c0,\n ngContentSelectors: _c2,\n decls: 7,\n vars: 4,\n consts: [[1, \"mat-mdc-button-persistent-ripple\"], [1, \"mdc-button__label\"], [1, \"mat-focus-indicator\"], [1, \"mat-mdc-button-touch-target\"]],\n template: function MatButton_Template(rf, ctx) {\n if (rf & 1) {\n i0.ɵɵprojectionDef(_c1);\n i0.ɵɵelement(0, \"span\", 0);\n i0.ɵɵprojection(1);\n i0.ɵɵelementStart(2, \"span\", 1);\n i0.ɵɵprojection(3, 1);\n i0.ɵɵelementEnd();\n i0.ɵɵprojection(4, 2);\n i0.ɵɵelement(5, \"span\", 2)(6, \"span\", 3);\n }\n if (rf & 2) {\n i0.ɵɵclassProp(\"mdc-button__ripple\", !ctx._isFab)(\"mdc-fab__ripple\", ctx._isFab);\n }\n },\n styles: [\".mat-mdc-button-base{text-decoration:none}.mdc-button{-webkit-user-select:none;user-select:none;position:relative;display:inline-flex;align-items:center;justify-content:center;box-sizing:border-box;min-width:64px;border:none;outline:none;line-height:inherit;-webkit-appearance:none;overflow:visible;vertical-align:middle;background:rgba(0,0,0,0);padding:0 8px}.mdc-button::-moz-focus-inner{padding:0;border:0}.mdc-button:active{outline:none}.mdc-button:hover{cursor:pointer}.mdc-button:disabled{cursor:default;pointer-events:none}.mdc-button[hidden]{display:none}.mdc-button .mdc-button__label{position:relative}.mat-mdc-button{padding:0 var(--mat-text-button-horizontal-padding, 12px);height:var(--mdc-text-button-container-height, 40px);font-family:var(--mdc-text-button-label-text-font, var(--mat-sys-label-large-font));font-size:var(--mdc-text-button-label-text-size, var(--mat-sys-label-large-size));letter-spacing:var(--mdc-text-button-label-text-tracking, var(--mat-sys-label-large-tracking));text-transform:var(--mdc-text-button-label-text-transform);font-weight:var(--mdc-text-button-label-text-weight, var(--mat-sys-label-large-weight))}.mat-mdc-button,.mat-mdc-button .mdc-button__ripple{border-radius:var(--mdc-text-button-container-shape, var(--mat-sys-corner-full))}.mat-mdc-button:not(:disabled){color:var(--mdc-text-button-label-text-color, var(--mat-sys-primary))}.mat-mdc-button[disabled],.mat-mdc-button.mat-mdc-button-disabled{cursor:default;pointer-events:none;color:var(--mdc-text-button-disabled-label-text-color, color-mix(in srgb, var(--mat-sys-on-surface) 38%, transparent))}.mat-mdc-button.mat-mdc-button-disabled-interactive{pointer-events:auto}.mat-mdc-button:has(.material-icons,mat-icon,[matButtonIcon]){padding:0 var(--mat-text-button-with-icon-horizontal-padding, 16px)}.mat-mdc-button>.mat-icon{margin-right:var(--mat-text-button-icon-spacing, 8px);margin-left:var(--mat-text-button-icon-offset, -4px)}[dir=rtl] .mat-mdc-button>.mat-icon{margin-right:var(--mat-text-button-icon-offset, -4px);margin-left:var(--mat-text-button-icon-spacing, 8px)}.mat-mdc-button .mdc-button__label+.mat-icon{margin-right:var(--mat-text-button-icon-offset, -4px);margin-left:var(--mat-text-button-icon-spacing, 8px)}[dir=rtl] .mat-mdc-button .mdc-button__label+.mat-icon{margin-right:var(--mat-text-button-icon-spacing, 8px);margin-left:var(--mat-text-button-icon-offset, -4px)}.mat-mdc-button .mat-ripple-element{background-color:var(--mat-text-button-ripple-color, color-mix(in srgb, var(--mat-sys-primary) calc(var(--mat-sys-pressed-state-layer-opacity) * 100%), transparent))}.mat-mdc-button .mat-mdc-button-persistent-ripple::before{background-color:var(--mat-text-button-state-layer-color, var(--mat-sys-primary))}.mat-mdc-button.mat-mdc-button-disabled .mat-mdc-button-persistent-ripple::before{background-color:var(--mat-text-button-disabled-state-layer-color, var(--mat-sys-on-surface-variant))}.mat-mdc-button:hover>.mat-mdc-button-persistent-ripple::before{opacity:var(--mat-text-button-hover-state-layer-opacity, var(--mat-sys-hover-state-layer-opacity))}.mat-mdc-button.cdk-program-focused>.mat-mdc-button-persistent-ripple::before,.mat-mdc-button.cdk-keyboard-focused>.mat-mdc-button-persistent-ripple::before,.mat-mdc-button.mat-mdc-button-disabled-interactive:focus>.mat-mdc-button-persistent-ripple::before{opacity:var(--mat-text-button-focus-state-layer-opacity, var(--mat-sys-focus-state-layer-opacity))}.mat-mdc-button:active>.mat-mdc-button-persistent-ripple::before{opacity:var(--mat-text-button-pressed-state-layer-opacity, var(--mat-sys-pressed-state-layer-opacity))}.mat-mdc-button .mat-mdc-button-touch-target{position:absolute;top:50%;height:48px;left:0;right:0;transform:translateY(-50%);display:var(--mat-text-button-touch-target-display, block)}.mat-mdc-unelevated-button{transition:box-shadow 280ms cubic-bezier(0.4, 0, 0.2, 1);height:var(--mdc-filled-button-container-height, 40px);font-family:var(--mdc-filled-button-label-text-font, var(--mat-sys-label-large-font));font-size:var(--mdc-filled-button-label-text-size, var(--mat-sys-label-large-size));letter-spacing:var(--mdc-filled-button-label-text-tracking, var(--mat-sys-label-large-tracking));text-transform:var(--mdc-filled-button-label-text-transform);font-weight:var(--mdc-filled-button-label-text-weight, var(--mat-sys-label-large-weight));padding:0 var(--mat-filled-button-horizontal-padding, 24px)}.mat-mdc-unelevated-button>.mat-icon{margin-right:var(--mat-filled-button-icon-spacing, 8px);margin-left:var(--mat-filled-button-icon-offset, -8px)}[dir=rtl] .mat-mdc-unelevated-button>.mat-icon{margin-right:var(--mat-filled-button-icon-offset, -8px);margin-left:var(--mat-filled-button-icon-spacing, 8px)}.mat-mdc-unelevated-button .mdc-button__label+.mat-icon{margin-right:var(--mat-filled-button-icon-offset, -8px);margin-left:var(--mat-filled-button-icon-spacing, 8px)}[dir=rtl] .mat-mdc-unelevated-button .mdc-button__label+.mat-icon{margin-right:var(--mat-filled-button-icon-spacing, 8px);margin-left:var(--mat-filled-button-icon-offset, -8px)}.mat-mdc-unelevated-button .mat-ripple-element{background-color:var(--mat-filled-button-ripple-color, color-mix(in srgb, var(--mat-sys-on-primary) calc(var(--mat-sys-pressed-state-layer-opacity) * 100%), transparent))}.mat-mdc-unelevated-button .mat-mdc-button-persistent-ripple::before{background-color:var(--mat-filled-button-state-layer-color, var(--mat-sys-on-primary))}.mat-mdc-unelevated-button.mat-mdc-button-disabled .mat-mdc-button-persistent-ripple::before{background-color:var(--mat-filled-button-disabled-state-layer-color, var(--mat-sys-on-surface-variant))}.mat-mdc-unelevated-button:hover>.mat-mdc-button-persistent-ripple::before{opacity:var(--mat-filled-button-hover-state-layer-opacity, var(--mat-sys-hover-state-layer-opacity))}.mat-mdc-unelevated-button.cdk-program-focused>.mat-mdc-button-persistent-ripple::before,.mat-mdc-unelevated-button.cdk-keyboard-focused>.mat-mdc-button-persistent-ripple::before,.mat-mdc-unelevated-button.mat-mdc-button-disabled-interactive:focus>.mat-mdc-button-persistent-ripple::before{opacity:var(--mat-filled-button-focus-state-layer-opacity, var(--mat-sys-focus-state-layer-opacity))}.mat-mdc-unelevated-button:active>.mat-mdc-button-persistent-ripple::before{opacity:var(--mat-filled-button-pressed-state-layer-opacity, var(--mat-sys-pressed-state-layer-opacity))}.mat-mdc-unelevated-button .mat-mdc-button-touch-target{position:absolute;top:50%;height:48px;left:0;right:0;transform:translateY(-50%);display:var(--mat-filled-button-touch-target-display, block)}.mat-mdc-unelevated-button:not(:disabled){color:var(--mdc-filled-button-label-text-color, var(--mat-sys-on-primary));background-color:var(--mdc-filled-button-container-color, var(--mat-sys-primary))}.mat-mdc-unelevated-button,.mat-mdc-unelevated-button .mdc-button__ripple{border-radius:var(--mdc-filled-button-container-shape, var(--mat-sys-corner-full))}.mat-mdc-unelevated-button[disabled],.mat-mdc-unelevated-button.mat-mdc-button-disabled{cursor:default;pointer-events:none;color:var(--mdc-filled-button-disabled-label-text-color, color-mix(in srgb, var(--mat-sys-on-surface) 38%, transparent));background-color:var(--mdc-filled-button-disabled-container-color, color-mix(in srgb, var(--mat-sys-on-surface) 12%, transparent))}.mat-mdc-unelevated-button.mat-mdc-button-disabled-interactive{pointer-events:auto}.mat-mdc-raised-button{transition:box-shadow 280ms cubic-bezier(0.4, 0, 0.2, 1);box-shadow:var(--mdc-protected-button-container-elevation-shadow, var(--mat-sys-level1));height:var(--mdc-protected-button-container-height, 40px);font-family:var(--mdc-protected-button-label-text-font, var(--mat-sys-label-large-font));font-size:var(--mdc-protected-button-label-text-size, var(--mat-sys-label-large-size));letter-spacing:var(--mdc-protected-button-label-text-tracking, var(--mat-sys-label-large-tracking));text-transform:var(--mdc-protected-button-label-text-transform);font-weight:var(--mdc-protected-button-label-text-weight, var(--mat-sys-label-large-weight));padding:0 var(--mat-protected-button-horizontal-padding, 24px)}.mat-mdc-raised-button>.mat-icon{margin-right:var(--mat-protected-button-icon-spacing, 8px);margin-left:var(--mat-protected-button-icon-offset, -8px)}[dir=rtl] .mat-mdc-raised-button>.mat-icon{margin-right:var(--mat-protected-button-icon-offset, -8px);margin-left:var(--mat-protected-button-icon-spacing, 8px)}.mat-mdc-raised-button .mdc-button__label+.mat-icon{margin-right:var(--mat-protected-button-icon-offset, -8px);margin-left:var(--mat-protected-button-icon-spacing, 8px)}[dir=rtl] .mat-mdc-raised-button .mdc-button__label+.mat-icon{margin-right:var(--mat-protected-button-icon-spacing, 8px);margin-left:var(--mat-protected-button-icon-offset, -8px)}.mat-mdc-raised-button .mat-ripple-element{background-color:var(--mat-protected-button-ripple-color, color-mix(in srgb, var(--mat-sys-primary) calc(var(--mat-sys-pressed-state-layer-opacity) * 100%), transparent))}.mat-mdc-raised-button .mat-mdc-button-persistent-ripple::before{background-color:var(--mat-protected-button-state-layer-color, var(--mat-sys-primary))}.mat-mdc-raised-button.mat-mdc-button-disabled .mat-mdc-button-persistent-ripple::before{background-color:var(--mat-protected-button-disabled-state-layer-color, var(--mat-sys-on-surface-variant))}.mat-mdc-raised-button:hover>.mat-mdc-button-persistent-ripple::before{opacity:var(--mat-protected-button-hover-state-layer-opacity, var(--mat-sys-hover-state-layer-opacity))}.mat-mdc-raised-button.cdk-program-focused>.mat-mdc-button-persistent-ripple::before,.mat-mdc-raised-button.cdk-keyboard-focused>.mat-mdc-button-persistent-ripple::before,.mat-mdc-raised-button.mat-mdc-button-disabled-interactive:focus>.mat-mdc-button-persistent-ripple::before{opacity:var(--mat-protected-button-focus-state-layer-opacity, var(--mat-sys-focus-state-layer-opacity))}.mat-mdc-raised-button:active>.mat-mdc-button-persistent-ripple::before{opacity:var(--mat-protected-button-pressed-state-layer-opacity, var(--mat-sys-pressed-state-layer-opacity))}.mat-mdc-raised-button .mat-mdc-button-touch-target{position:absolute;top:50%;height:48px;left:0;right:0;transform:translateY(-50%);display:var(--mat-protected-button-touch-target-display, block)}.mat-mdc-raised-button:not(:disabled){color:var(--mdc-protected-button-label-text-color, var(--mat-sys-primary));background-color:var(--mdc-protected-button-container-color, var(--mat-sys-surface))}.mat-mdc-raised-button,.mat-mdc-raised-button .mdc-button__ripple{border-radius:var(--mdc-protected-button-container-shape, var(--mat-sys-corner-full))}.mat-mdc-raised-button:hover{box-shadow:var(--mdc-protected-button-hover-container-elevation-shadow, var(--mat-sys-level2))}.mat-mdc-raised-button:focus{box-shadow:var(--mdc-protected-button-focus-container-elevation-shadow, var(--mat-sys-level1))}.mat-mdc-raised-button:active,.mat-mdc-raised-button:focus:active{box-shadow:var(--mdc-protected-button-pressed-container-elevation-shadow, var(--mat-sys-level1))}.mat-mdc-raised-button[disabled],.mat-mdc-raised-button.mat-mdc-button-disabled{cursor:default;pointer-events:none;color:var(--mdc-protected-button-disabled-label-text-color, color-mix(in srgb, var(--mat-sys-on-surface) 38%, transparent));background-color:var(--mdc-protected-button-disabled-container-color, color-mix(in srgb, var(--mat-sys-on-surface) 12%, transparent))}.mat-mdc-raised-button[disabled].mat-mdc-button-disabled,.mat-mdc-raised-button.mat-mdc-button-disabled.mat-mdc-button-disabled{box-shadow:var(--mdc-protected-button-disabled-container-elevation-shadow, var(--mat-sys-level0))}.mat-mdc-raised-button.mat-mdc-button-disabled-interactive{pointer-events:auto}.mat-mdc-outlined-button{border-style:solid;transition:border 280ms cubic-bezier(0.4, 0, 0.2, 1);height:var(--mdc-outlined-button-container-height, 40px);font-family:var(--mdc-outlined-button-label-text-font, var(--mat-sys-label-large-font));font-size:var(--mdc-outlined-button-label-text-size, var(--mat-sys-label-large-size));letter-spacing:var(--mdc-outlined-button-label-text-tracking, var(--mat-sys-label-large-tracking));text-transform:var(--mdc-outlined-button-label-text-transform);font-weight:var(--mdc-outlined-button-label-text-weight, var(--mat-sys-label-large-weight));border-radius:var(--mdc-outlined-button-container-shape, var(--mat-sys-corner-full));border-width:var(--mdc-outlined-button-outline-width, 1px);padding:0 var(--mat-outlined-button-horizontal-padding, 24px)}.mat-mdc-outlined-button>.mat-icon{margin-right:var(--mat-outlined-button-icon-spacing, 8px);margin-left:var(--mat-outlined-button-icon-offset, -8px)}[dir=rtl] .mat-mdc-outlined-button>.mat-icon{margin-right:var(--mat-outlined-button-icon-offset, -8px);margin-left:var(--mat-outlined-button-icon-spacing, 8px)}.mat-mdc-outlined-button .mdc-button__label+.mat-icon{margin-right:var(--mat-outlined-button-icon-offset, -8px);margin-left:var(--mat-outlined-button-icon-spacing, 8px)}[dir=rtl] .mat-mdc-outlined-button .mdc-button__label+.mat-icon{margin-right:var(--mat-outlined-button-icon-spacing, 8px);margin-left:var(--mat-outlined-button-icon-offset, -8px)}.mat-mdc-outlined-button .mat-ripple-element{background-color:var(--mat-outlined-button-ripple-color, color-mix(in srgb, var(--mat-sys-primary) calc(var(--mat-sys-pressed-state-layer-opacity) * 100%), transparent))}.mat-mdc-outlined-button .mat-mdc-button-persistent-ripple::before{background-color:var(--mat-outlined-button-state-layer-color, var(--mat-sys-primary))}.mat-mdc-outlined-button.mat-mdc-button-disabled .mat-mdc-button-persistent-ripple::before{background-color:var(--mat-outlined-button-disabled-state-layer-color, var(--mat-sys-on-surface-variant))}.mat-mdc-outlined-button:hover>.mat-mdc-button-persistent-ripple::before{opacity:var(--mat-outlined-button-hover-state-layer-opacity, var(--mat-sys-hover-state-layer-opacity))}.mat-mdc-outlined-button.cdk-program-focused>.mat-mdc-button-persistent-ripple::before,.mat-mdc-outlined-button.cdk-keyboard-focused>.mat-mdc-button-persistent-ripple::before,.mat-mdc-outlined-button.mat-mdc-button-disabled-interactive:focus>.mat-mdc-button-persistent-ripple::before{opacity:var(--mat-outlined-button-focus-state-layer-opacity, var(--mat-sys-focus-state-layer-opacity))}.mat-mdc-outlined-button:active>.mat-mdc-button-persistent-ripple::before{opacity:var(--mat-outlined-button-pressed-state-layer-opacity, var(--mat-sys-pressed-state-layer-opacity))}.mat-mdc-outlined-button .mat-mdc-button-touch-target{position:absolute;top:50%;height:48px;left:0;right:0;transform:translateY(-50%);display:var(--mat-outlined-button-touch-target-display, block)}.mat-mdc-outlined-button:not(:disabled){color:var(--mdc-outlined-button-label-text-color, var(--mat-sys-primary));border-color:var(--mdc-outlined-button-outline-color, var(--mat-sys-outline))}.mat-mdc-outlined-button[disabled],.mat-mdc-outlined-button.mat-mdc-button-disabled{cursor:default;pointer-events:none;color:var(--mdc-outlined-button-disabled-label-text-color, color-mix(in srgb, var(--mat-sys-on-surface) 38%, transparent));border-color:var(--mdc-outlined-button-disabled-outline-color, color-mix(in srgb, var(--mat-sys-on-surface) 12%, transparent))}.mat-mdc-outlined-button.mat-mdc-button-disabled-interactive{pointer-events:auto}.mat-mdc-button,.mat-mdc-unelevated-button,.mat-mdc-raised-button,.mat-mdc-outlined-button{-webkit-tap-highlight-color:rgba(0,0,0,0)}.mat-mdc-button .mat-mdc-button-ripple,.mat-mdc-button .mat-mdc-button-persistent-ripple,.mat-mdc-button .mat-mdc-button-persistent-ripple::before,.mat-mdc-unelevated-button .mat-mdc-button-ripple,.mat-mdc-unelevated-button .mat-mdc-button-persistent-ripple,.mat-mdc-unelevated-button .mat-mdc-button-persistent-ripple::before,.mat-mdc-raised-button .mat-mdc-button-ripple,.mat-mdc-raised-button .mat-mdc-button-persistent-ripple,.mat-mdc-raised-button .mat-mdc-button-persistent-ripple::before,.mat-mdc-outlined-button .mat-mdc-button-ripple,.mat-mdc-outlined-button .mat-mdc-button-persistent-ripple,.mat-mdc-outlined-button .mat-mdc-button-persistent-ripple::before{top:0;left:0;right:0;bottom:0;position:absolute;pointer-events:none;border-radius:inherit}.mat-mdc-button .mat-mdc-button-ripple,.mat-mdc-unelevated-button .mat-mdc-button-ripple,.mat-mdc-raised-button .mat-mdc-button-ripple,.mat-mdc-outlined-button .mat-mdc-button-ripple{overflow:hidden}.mat-mdc-button .mat-mdc-button-persistent-ripple::before,.mat-mdc-unelevated-button .mat-mdc-button-persistent-ripple::before,.mat-mdc-raised-button .mat-mdc-button-persistent-ripple::before,.mat-mdc-outlined-button .mat-mdc-button-persistent-ripple::before{content:\\\"\\\";opacity:0}.mat-mdc-button .mdc-button__label,.mat-mdc-button .mat-icon,.mat-mdc-unelevated-button .mdc-button__label,.mat-mdc-unelevated-button .mat-icon,.mat-mdc-raised-button .mdc-button__label,.mat-mdc-raised-button .mat-icon,.mat-mdc-outlined-button .mdc-button__label,.mat-mdc-outlined-button .mat-icon{z-index:1;position:relative}.mat-mdc-button .mat-focus-indicator,.mat-mdc-unelevated-button .mat-focus-indicator,.mat-mdc-raised-button .mat-focus-indicator,.mat-mdc-outlined-button .mat-focus-indicator{top:0;left:0;right:0;bottom:0;position:absolute}.mat-mdc-button:focus>.mat-focus-indicator::before,.mat-mdc-unelevated-button:focus>.mat-focus-indicator::before,.mat-mdc-raised-button:focus>.mat-focus-indicator::before,.mat-mdc-outlined-button:focus>.mat-focus-indicator::before{content:\\\"\\\"}.mat-mdc-button._mat-animation-noopable,.mat-mdc-unelevated-button._mat-animation-noopable,.mat-mdc-raised-button._mat-animation-noopable,.mat-mdc-outlined-button._mat-animation-noopable{transition:none !important;animation:none !important}.mat-mdc-button>.mat-icon,.mat-mdc-unelevated-button>.mat-icon,.mat-mdc-raised-button>.mat-icon,.mat-mdc-outlined-button>.mat-icon{display:inline-block;position:relative;vertical-align:top;font-size:1.125rem;height:1.125rem;width:1.125rem}.mat-mdc-outlined-button .mat-mdc-button-ripple,.mat-mdc-outlined-button .mdc-button__ripple{top:-1px;left:-1px;bottom:-1px;right:-1px}.mat-mdc-unelevated-button .mat-focus-indicator::before,.mat-mdc-raised-button .mat-focus-indicator::before{margin:calc(calc(var(--mat-focus-indicator-border-width, 3px) + 2px)*-1)}.mat-mdc-outlined-button .mat-focus-indicator::before{margin:calc(calc(var(--mat-focus-indicator-border-width, 3px) + 3px)*-1)}\", \"@media(forced-colors: active){.mat-mdc-button:not(.mdc-button--outlined),.mat-mdc-unelevated-button:not(.mdc-button--outlined),.mat-mdc-raised-button:not(.mdc-button--outlined),.mat-mdc-outlined-button:not(.mdc-button--outlined),.mat-mdc-icon-button.mat-mdc-icon-button,.mat-mdc-outlined-button .mdc-button__ripple{outline:solid 1px}}\"],\n encapsulation: 2,\n changeDetection: 0\n });\n }\n return MatButton;\n})();\n/*#__PURE__*/(() => {\n (typeof ngDevMode === \"undefined\" || ngDevMode) && void 0;\n})();\n/**\n * Material Design button component for anchor elements. Anchor elements are used to provide\n * links for the user to navigate across different routes or pages.\n * See https://material.io/components/buttons\n *\n * The `MatAnchor` class applies to native anchor elements and captures the appearances for\n * \"text button\", \"outlined button\", and \"contained button\" per the Material Design\n * specification. `MatAnchor` additionally captures an additional \"flat\" appearance, which matches\n * \"contained\" but without elevation.\n */\nlet MatAnchor = /*#__PURE__*/(() => {\n class MatAnchor extends MatAnchorBase {\n static ɵfac = /* @__PURE__ */(() => {\n let ɵMatAnchor_BaseFactory;\n return function MatAnchor_Factory(__ngFactoryType__) {\n return (ɵMatAnchor_BaseFactory || (ɵMatAnchor_BaseFactory = i0.ɵɵgetInheritedFactory(MatAnchor)))(__ngFactoryType__ || MatAnchor);\n };\n })();\n static ɵcmp = /* @__PURE__ */i0.ɵɵdefineComponent({\n type: MatAnchor,\n selectors: [[\"a\", \"mat-button\", \"\"], [\"a\", \"mat-raised-button\", \"\"], [\"a\", \"mat-flat-button\", \"\"], [\"a\", \"mat-stroked-button\", \"\"]],\n hostVars: 15,\n hostBindings: function MatAnchor_HostBindings(rf, ctx) {\n if (rf & 2) {\n i0.ɵɵattribute(\"disabled\", ctx._getDisabledAttribute())(\"tabindex\", ctx.disabled && !ctx.disabledInteractive ? -1 : ctx.tabIndex)(\"aria-disabled\", ctx._getAriaDisabled());\n i0.ɵɵclassMap(ctx.color ? \"mat-\" + ctx.color : \"\");\n i0.ɵɵclassProp(\"mat-mdc-button-disabled\", ctx.disabled)(\"mat-mdc-button-disabled-interactive\", ctx.disabledInteractive)(\"_mat-animation-noopable\", ctx._animationMode === \"NoopAnimations\")(\"mat-unthemed\", !ctx.color)(\"mat-mdc-button-base\", true);\n }\n },\n exportAs: [\"matButton\", \"matAnchor\"],\n features: [i0.ɵɵInheritDefinitionFeature],\n attrs: _c0,\n ngContentSelectors: _c2,\n decls: 7,\n vars: 4,\n consts: [[1, \"mat-mdc-button-persistent-ripple\"], [1, \"mdc-button__label\"], [1, \"mat-focus-indicator\"], [1, \"mat-mdc-button-touch-target\"]],\n template: function MatAnchor_Template(rf, ctx) {\n if (rf & 1) {\n i0.ɵɵprojectionDef(_c1);\n i0.ɵɵelement(0, \"span\", 0);\n i0.ɵɵprojection(1);\n i0.ɵɵelementStart(2, \"span\", 1);\n i0.ɵɵprojection(3, 1);\n i0.ɵɵelementEnd();\n i0.ɵɵprojection(4, 2);\n i0.ɵɵelement(5, \"span\", 2)(6, \"span\", 3);\n }\n if (rf & 2) {\n i0.ɵɵclassProp(\"mdc-button__ripple\", !ctx._isFab)(\"mdc-fab__ripple\", ctx._isFab);\n }\n },\n styles: [_c3, _c4],\n encapsulation: 2,\n changeDetection: 0\n });\n }\n return MatAnchor;\n})();\n/*#__PURE__*/(() => {\n (typeof ngDevMode === \"undefined\" || ngDevMode) && void 0;\n})();\n\n/** Injection token to be used to override the default options for FAB. */\nconst MAT_FAB_DEFAULT_OPTIONS = /*#__PURE__*/new InjectionToken('mat-mdc-fab-default-options', {\n providedIn: 'root',\n factory: MAT_FAB_DEFAULT_OPTIONS_FACTORY\n});\n/** @docs-private */\nfunction MAT_FAB_DEFAULT_OPTIONS_FACTORY() {\n return {\n // The FAB by default has its color set to accent.\n color: 'accent'\n };\n}\n// Default FAB configuration.\nconst defaults = /*#__PURE__*/MAT_FAB_DEFAULT_OPTIONS_FACTORY();\n/**\n * Material Design floating action button (FAB) component. These buttons represent the primary\n * or most common action for users to interact with.\n * See https://material.io/components/buttons-floating-action-button/\n *\n * The `MatFabButton` class has two appearances: normal and extended.\n */\nlet MatFabButton = /*#__PURE__*/(() => {\n class MatFabButton extends MatButtonBase {\n _options = inject(MAT_FAB_DEFAULT_OPTIONS, {\n optional: true\n });\n _isFab = true;\n extended;\n constructor() {\n super();\n this._options = this._options || defaults;\n this.color = this._options.color || defaults.color;\n }\n static ɵfac = function MatFabButton_Factory(__ngFactoryType__) {\n return new (__ngFactoryType__ || MatFabButton)();\n };\n static ɵcmp = /* @__PURE__ */i0.ɵɵdefineComponent({\n type: MatFabButton,\n selectors: [[\"button\", \"mat-fab\", \"\"]],\n hostVars: 18,\n hostBindings: function MatFabButton_HostBindings(rf, ctx) {\n if (rf & 2) {\n i0.ɵɵattribute(\"disabled\", ctx._getDisabledAttribute())(\"aria-disabled\", ctx._getAriaDisabled());\n i0.ɵɵclassMap(ctx.color ? \"mat-\" + ctx.color : \"\");\n i0.ɵɵclassProp(\"mat-mdc-button-disabled\", ctx.disabled)(\"mat-mdc-button-disabled-interactive\", ctx.disabledInteractive)(\"_mat-animation-noopable\", ctx._animationMode === \"NoopAnimations\")(\"mat-unthemed\", !ctx.color)(\"mat-mdc-button-base\", true)(\"mdc-fab--extended\", ctx.extended)(\"mat-mdc-extended-fab\", ctx.extended);\n }\n },\n inputs: {\n extended: [2, \"extended\", \"extended\", booleanAttribute]\n },\n exportAs: [\"matButton\"],\n features: [i0.ɵɵInheritDefinitionFeature],\n attrs: _c5,\n ngContentSelectors: _c2,\n decls: 7,\n vars: 4,\n consts: [[1, \"mat-mdc-button-persistent-ripple\"], [1, \"mdc-button__label\"], [1, \"mat-focus-indicator\"], [1, \"mat-mdc-button-touch-target\"]],\n template: function MatFabButton_Template(rf, ctx) {\n if (rf & 1) {\n i0.ɵɵprojectionDef(_c1);\n i0.ɵɵelement(0, \"span\", 0);\n i0.ɵɵprojection(1);\n i0.ɵɵelementStart(2, \"span\", 1);\n i0.ɵɵprojection(3, 1);\n i0.ɵɵelementEnd();\n i0.ɵɵprojection(4, 2);\n i0.ɵɵelement(5, \"span\", 2)(6, \"span\", 3);\n }\n if (rf & 2) {\n i0.ɵɵclassProp(\"mdc-button__ripple\", !ctx._isFab)(\"mdc-fab__ripple\", ctx._isFab);\n }\n },\n styles: [\".mat-mdc-fab-base{-webkit-user-select:none;user-select:none;position:relative;display:inline-flex;align-items:center;justify-content:center;box-sizing:border-box;width:56px;height:56px;padding:0;border:none;fill:currentColor;text-decoration:none;cursor:pointer;-moz-appearance:none;-webkit-appearance:none;overflow:visible;transition:box-shadow 280ms cubic-bezier(0.4, 0, 0.2, 1),opacity 15ms linear 30ms,transform 270ms 0ms cubic-bezier(0, 0, 0.2, 1);flex-shrink:0;-webkit-tap-highlight-color:rgba(0,0,0,0)}.mat-mdc-fab-base .mat-mdc-button-ripple,.mat-mdc-fab-base .mat-mdc-button-persistent-ripple,.mat-mdc-fab-base .mat-mdc-button-persistent-ripple::before{top:0;left:0;right:0;bottom:0;position:absolute;pointer-events:none;border-radius:inherit}.mat-mdc-fab-base .mat-mdc-button-ripple{overflow:hidden}.mat-mdc-fab-base .mat-mdc-button-persistent-ripple::before{content:\\\"\\\";opacity:0}.mat-mdc-fab-base .mdc-button__label,.mat-mdc-fab-base .mat-icon{z-index:1;position:relative}.mat-mdc-fab-base .mat-focus-indicator{top:0;left:0;right:0;bottom:0;position:absolute}.mat-mdc-fab-base:focus>.mat-focus-indicator::before{content:\\\"\\\"}.mat-mdc-fab-base._mat-animation-noopable{transition:none !important;animation:none !important}.mat-mdc-fab-base::before{position:absolute;box-sizing:border-box;width:100%;height:100%;top:0;left:0;border:1px solid rgba(0,0,0,0);border-radius:inherit;content:\\\"\\\";pointer-events:none}.mat-mdc-fab-base[hidden]{display:none}.mat-mdc-fab-base::-moz-focus-inner{padding:0;border:0}.mat-mdc-fab-base:active,.mat-mdc-fab-base:focus{outline:none}.mat-mdc-fab-base:hover{cursor:pointer}.mat-mdc-fab-base>svg{width:100%}.mat-mdc-fab-base .mat-icon,.mat-mdc-fab-base .material-icons{transition:transform 180ms 90ms cubic-bezier(0, 0, 0.2, 1);fill:currentColor;will-change:transform}.mat-mdc-fab-base .mat-focus-indicator::before{margin:calc(calc(var(--mat-focus-indicator-border-width, 3px) + 2px)*-1)}.mat-mdc-fab-base[disabled],.mat-mdc-fab-base.mat-mdc-button-disabled{cursor:default;pointer-events:none}.mat-mdc-fab-base[disabled],.mat-mdc-fab-base[disabled]:focus,.mat-mdc-fab-base.mat-mdc-button-disabled,.mat-mdc-fab-base.mat-mdc-button-disabled:focus{box-shadow:none}.mat-mdc-fab-base.mat-mdc-button-disabled-interactive{pointer-events:auto}.mat-mdc-fab{background-color:var(--mdc-fab-container-color, var(--mat-sys-primary-container));border-radius:var(--mdc-fab-container-shape, var(--mat-sys-corner-large));color:var(--mat-fab-foreground-color, var(--mat-sys-on-primary-container, inherit));box-shadow:var(--mdc-fab-container-elevation-shadow, var(--mat-sys-level3))}.mat-mdc-fab:hover{box-shadow:var(--mdc-fab-hover-container-elevation-shadow, var(--mat-sys-level4))}.mat-mdc-fab:focus{box-shadow:var(--mdc-fab-focus-container-elevation-shadow, var(--mat-sys-level3))}.mat-mdc-fab:active,.mat-mdc-fab:focus:active{box-shadow:var(--mdc-fab-pressed-container-elevation-shadow, var(--mat-sys-level3))}.mat-mdc-fab[disabled],.mat-mdc-fab.mat-mdc-button-disabled{cursor:default;pointer-events:none;color:var(--mat-fab-disabled-state-foreground-color, color-mix(in srgb, var(--mat-sys-on-surface) 38%, transparent));background-color:var(--mat-fab-disabled-state-container-color, color-mix(in srgb, var(--mat-sys-on-surface) 12%, transparent))}.mat-mdc-fab.mat-mdc-button-disabled-interactive{pointer-events:auto}.mat-mdc-fab .mat-mdc-button-touch-target{position:absolute;top:50%;height:48px;left:50%;width:48px;transform:translate(-50%, -50%);display:var(--mat-fab-touch-target-display, block)}.mat-mdc-fab .mat-ripple-element{background-color:var(--mat-fab-ripple-color, color-mix(in srgb, var(--mat-sys-on-primary-container) calc(var(--mat-sys-pressed-state-layer-opacity) * 100%), transparent))}.mat-mdc-fab .mat-mdc-button-persistent-ripple::before{background-color:var(--mat-fab-state-layer-color, var(--mat-sys-on-primary-container))}.mat-mdc-fab.mat-mdc-button-disabled .mat-mdc-button-persistent-ripple::before{background-color:var(--mat-fab-disabled-state-layer-color)}.mat-mdc-fab:hover>.mat-mdc-button-persistent-ripple::before{opacity:var(--mat-fab-hover-state-layer-opacity, var(--mat-sys-hover-state-layer-opacity))}.mat-mdc-fab.cdk-program-focused>.mat-mdc-button-persistent-ripple::before,.mat-mdc-fab.cdk-keyboard-focused>.mat-mdc-button-persistent-ripple::before,.mat-mdc-fab.mat-mdc-button-disabled-interactive:focus>.mat-mdc-button-persistent-ripple::before{opacity:var(--mat-fab-focus-state-layer-opacity, var(--mat-sys-focus-state-layer-opacity))}.mat-mdc-fab:active>.mat-mdc-button-persistent-ripple::before{opacity:var(--mat-fab-pressed-state-layer-opacity, var(--mat-sys-pressed-state-layer-opacity))}.mat-mdc-mini-fab{width:40px;height:40px;background-color:var(--mdc-fab-small-container-color, var(--mat-sys-primary-container));border-radius:var(--mdc-fab-small-container-shape, var(--mat-sys-corner-medium));color:var(--mat-fab-small-foreground-color, var(--mat-sys-on-primary-container, inherit));box-shadow:var(--mdc-fab-small-container-elevation-shadow, var(--mat-sys-level3))}.mat-mdc-mini-fab:hover{box-shadow:var(--mdc-fab-small-hover-container-elevation-shadow, var(--mat-sys-level4))}.mat-mdc-mini-fab:focus{box-shadow:var(--mdc-fab-small-focus-container-elevation-shadow, var(--mat-sys-level3))}.mat-mdc-mini-fab:active,.mat-mdc-mini-fab:focus:active{box-shadow:var(--mdc-fab-small-pressed-container-elevation-shadow, var(--mat-sys-level3))}.mat-mdc-mini-fab[disabled],.mat-mdc-mini-fab.mat-mdc-button-disabled{cursor:default;pointer-events:none;color:var(--mat-fab-small-disabled-state-foreground-color, color-mix(in srgb, var(--mat-sys-on-surface) 38%, transparent));background-color:var(--mat-fab-small-disabled-state-container-color, color-mix(in srgb, var(--mat-sys-on-surface) 12%, transparent))}.mat-mdc-mini-fab.mat-mdc-button-disabled-interactive{pointer-events:auto}.mat-mdc-mini-fab .mat-mdc-button-touch-target{position:absolute;top:50%;height:48px;left:50%;width:48px;transform:translate(-50%, -50%);display:var(--mat-fab-small-touch-target-display)}.mat-mdc-mini-fab .mat-ripple-element{background-color:var(--mat-fab-small-ripple-color, color-mix(in srgb, var(--mat-sys-on-primary-container) calc(var(--mat-sys-pressed-state-layer-opacity) * 100%), transparent))}.mat-mdc-mini-fab .mat-mdc-button-persistent-ripple::before{background-color:var(--mat-fab-small-state-layer-color, var(--mat-sys-on-primary-container))}.mat-mdc-mini-fab.mat-mdc-button-disabled .mat-mdc-button-persistent-ripple::before{background-color:var(--mat-fab-small-disabled-state-layer-color)}.mat-mdc-mini-fab:hover>.mat-mdc-button-persistent-ripple::before{opacity:var(--mat-fab-small-hover-state-layer-opacity, var(--mat-sys-hover-state-layer-opacity))}.mat-mdc-mini-fab.cdk-program-focused>.mat-mdc-button-persistent-ripple::before,.mat-mdc-mini-fab.cdk-keyboard-focused>.mat-mdc-button-persistent-ripple::before,.mat-mdc-mini-fab.mat-mdc-button-disabled-interactive:focus>.mat-mdc-button-persistent-ripple::before{opacity:var(--mat-fab-small-focus-state-layer-opacity, var(--mat-sys-focus-state-layer-opacity))}.mat-mdc-mini-fab:active>.mat-mdc-button-persistent-ripple::before{opacity:var(--mat-fab-small-pressed-state-layer-opacity, var(--mat-sys-pressed-state-layer-opacity))}.mat-mdc-extended-fab{-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;border-radius:24px;padding-left:20px;padding-right:20px;width:auto;max-width:100%;line-height:normal;height:var(--mdc-extended-fab-container-height, 56px);border-radius:var(--mdc-extended-fab-container-shape, var(--mat-sys-corner-large));font-family:var(--mdc-extended-fab-label-text-font, var(--mat-sys-label-large-font));font-size:var(--mdc-extended-fab-label-text-size, var(--mat-sys-label-large-size));font-weight:var(--mdc-extended-fab-label-text-weight, var(--mat-sys-label-large-weight));letter-spacing:var(--mdc-extended-fab-label-text-tracking, var(--mat-sys-label-large-tracking));box-shadow:var(--mdc-extended-fab-container-elevation-shadow, var(--mat-sys-level3))}.mat-mdc-extended-fab:hover{box-shadow:var(--mdc-extended-fab-hover-container-elevation-shadow, var(--mat-sys-level4))}.mat-mdc-extended-fab:focus{box-shadow:var(--mdc-extended-fab-focus-container-elevation-shadow, var(--mat-sys-level3))}.mat-mdc-extended-fab:active,.mat-mdc-extended-fab:focus:active{box-shadow:var(--mdc-extended-fab-pressed-container-elevation-shadow, var(--mat-sys-level3))}.mat-mdc-extended-fab[disabled],.mat-mdc-extended-fab.mat-mdc-button-disabled{cursor:default;pointer-events:none}.mat-mdc-extended-fab[disabled],.mat-mdc-extended-fab[disabled]:focus,.mat-mdc-extended-fab.mat-mdc-button-disabled,.mat-mdc-extended-fab.mat-mdc-button-disabled:focus{box-shadow:none}.mat-mdc-extended-fab.mat-mdc-button-disabled-interactive{pointer-events:auto}[dir=rtl] .mat-mdc-extended-fab .mdc-button__label+.mat-icon,[dir=rtl] .mat-mdc-extended-fab .mdc-button__label+.material-icons,.mat-mdc-extended-fab>.mat-icon,.mat-mdc-extended-fab>.material-icons{margin-left:-8px;margin-right:12px}.mat-mdc-extended-fab .mdc-button__label+.mat-icon,.mat-mdc-extended-fab .mdc-button__label+.material-icons,[dir=rtl] .mat-mdc-extended-fab>.mat-icon,[dir=rtl] .mat-mdc-extended-fab>.material-icons{margin-left:12px;margin-right:-8px}.mat-mdc-extended-fab .mat-mdc-button-touch-target{width:100%}\"],\n encapsulation: 2,\n changeDetection: 0\n });\n }\n return MatFabButton;\n})();\n/*#__PURE__*/(() => {\n (typeof ngDevMode === \"undefined\" || ngDevMode) && void 0;\n})();\n/**\n * Material Design mini floating action button (FAB) component. These buttons represent the primary\n * or most common action for users to interact with.\n * See https://material.io/components/buttons-floating-action-button/\n */\nlet MatMiniFabButton = /*#__PURE__*/(() => {\n class MatMiniFabButton extends MatButtonBase {\n _options = inject(MAT_FAB_DEFAULT_OPTIONS, {\n optional: true\n });\n _isFab = true;\n constructor() {\n super();\n this._options = this._options || defaults;\n this.color = this._options.color || defaults.color;\n }\n static ɵfac = function MatMiniFabButton_Factory(__ngFactoryType__) {\n return new (__ngFactoryType__ || MatMiniFabButton)();\n };\n static ɵcmp = /* @__PURE__ */i0.ɵɵdefineComponent({\n type: MatMiniFabButton,\n selectors: [[\"button\", \"mat-mini-fab\", \"\"]],\n hostVars: 14,\n hostBindings: function MatMiniFabButton_HostBindings(rf, ctx) {\n if (rf & 2) {\n i0.ɵɵattribute(\"disabled\", ctx._getDisabledAttribute())(\"aria-disabled\", ctx._getAriaDisabled());\n i0.ɵɵclassMap(ctx.color ? \"mat-\" + ctx.color : \"\");\n i0.ɵɵclassProp(\"mat-mdc-button-disabled\", ctx.disabled)(\"mat-mdc-button-disabled-interactive\", ctx.disabledInteractive)(\"_mat-animation-noopable\", ctx._animationMode === \"NoopAnimations\")(\"mat-unthemed\", !ctx.color)(\"mat-mdc-button-base\", true);\n }\n },\n exportAs: [\"matButton\"],\n features: [i0.ɵɵInheritDefinitionFeature],\n attrs: _c6,\n ngContentSelectors: _c2,\n decls: 7,\n vars: 4,\n consts: [[1, \"mat-mdc-button-persistent-ripple\"], [1, \"mdc-button__label\"], [1, \"mat-focus-indicator\"], [1, \"mat-mdc-button-touch-target\"]],\n template: function MatMiniFabButton_Template(rf, ctx) {\n if (rf & 1) {\n i0.ɵɵprojectionDef(_c1);\n i0.ɵɵelement(0, \"span\", 0);\n i0.ɵɵprojection(1);\n i0.ɵɵelementStart(2, \"span\", 1);\n i0.ɵɵprojection(3, 1);\n i0.ɵɵelementEnd();\n i0.ɵɵprojection(4, 2);\n i0.ɵɵelement(5, \"span\", 2)(6, \"span\", 3);\n }\n if (rf & 2) {\n i0.ɵɵclassProp(\"mdc-button__ripple\", !ctx._isFab)(\"mdc-fab__ripple\", ctx._isFab);\n }\n },\n styles: [_c7],\n encapsulation: 2,\n changeDetection: 0\n });\n }\n return MatMiniFabButton;\n})();\n/*#__PURE__*/(() => {\n (typeof ngDevMode === \"undefined\" || ngDevMode) && void 0;\n})();\n/**\n * Material Design floating action button (FAB) component for anchor elements. Anchor elements\n * are used to provide links for the user to navigate across different routes or pages.\n * See https://material.io/components/buttons-floating-action-button/\n *\n * The `MatFabAnchor` class has two appearances: normal and extended.\n */\nlet MatFabAnchor = /*#__PURE__*/(() => {\n class MatFabAnchor extends MatAnchor {\n _options = inject(MAT_FAB_DEFAULT_OPTIONS, {\n optional: true\n });\n _isFab = true;\n extended;\n constructor() {\n super();\n this._options = this._options || defaults;\n this.color = this._options.color || defaults.color;\n }\n static ɵfac = function MatFabAnchor_Factory(__ngFactoryType__) {\n return new (__ngFactoryType__ || MatFabAnchor)();\n };\n static ɵcmp = /* @__PURE__ */i0.ɵɵdefineComponent({\n type: MatFabAnchor,\n selectors: [[\"a\", \"mat-fab\", \"\"]],\n hostVars: 19,\n hostBindings: function MatFabAnchor_HostBindings(rf, ctx) {\n if (rf & 2) {\n i0.ɵɵattribute(\"disabled\", ctx._getDisabledAttribute())(\"tabindex\", ctx.disabled && !ctx.disabledInteractive ? -1 : ctx.tabIndex)(\"aria-disabled\", ctx._getAriaDisabled());\n i0.ɵɵclassMap(ctx.color ? \"mat-\" + ctx.color : \"\");\n i0.ɵɵclassProp(\"mat-mdc-button-disabled\", ctx.disabled)(\"mat-mdc-button-disabled-interactive\", ctx.disabledInteractive)(\"_mat-animation-noopable\", ctx._animationMode === \"NoopAnimations\")(\"mat-unthemed\", !ctx.color)(\"mat-mdc-button-base\", true)(\"mdc-fab--extended\", ctx.extended)(\"mat-mdc-extended-fab\", ctx.extended);\n }\n },\n inputs: {\n extended: [2, \"extended\", \"extended\", booleanAttribute]\n },\n exportAs: [\"matButton\", \"matAnchor\"],\n features: [i0.ɵɵInheritDefinitionFeature],\n attrs: _c5,\n ngContentSelectors: _c2,\n decls: 7,\n vars: 4,\n consts: [[1, \"mat-mdc-button-persistent-ripple\"], [1, \"mdc-button__label\"], [1, \"mat-focus-indicator\"], [1, \"mat-mdc-button-touch-target\"]],\n template: function MatFabAnchor_Template(rf, ctx) {\n if (rf & 1) {\n i0.ɵɵprojectionDef(_c1);\n i0.ɵɵelement(0, \"span\", 0);\n i0.ɵɵprojection(1);\n i0.ɵɵelementStart(2, \"span\", 1);\n i0.ɵɵprojection(3, 1);\n i0.ɵɵelementEnd();\n i0.ɵɵprojection(4, 2);\n i0.ɵɵelement(5, \"span\", 2)(6, \"span\", 3);\n }\n if (rf & 2) {\n i0.ɵɵclassProp(\"mdc-button__ripple\", !ctx._isFab)(\"mdc-fab__ripple\", ctx._isFab);\n }\n },\n styles: [_c7],\n encapsulation: 2,\n changeDetection: 0\n });\n }\n return MatFabAnchor;\n})();\n/*#__PURE__*/(() => {\n (typeof ngDevMode === \"undefined\" || ngDevMode) && void 0;\n})();\n/**\n * Material Design mini floating action button (FAB) component for anchor elements. Anchor elements\n * are used to provide links for the user to navigate across different routes or pages.\n * See https://material.io/components/buttons-floating-action-button/\n */\nlet MatMiniFabAnchor = /*#__PURE__*/(() => {\n class MatMiniFabAnchor extends MatAnchor {\n _options = inject(MAT_FAB_DEFAULT_OPTIONS, {\n optional: true\n });\n _isFab = true;\n constructor() {\n super();\n this._options = this._options || defaults;\n this.color = this._options.color || defaults.color;\n }\n static ɵfac = function MatMiniFabAnchor_Factory(__ngFactoryType__) {\n return new (__ngFactoryType__ || MatMiniFabAnchor)();\n };\n static ɵcmp = /* @__PURE__ */i0.ɵɵdefineComponent({\n type: MatMiniFabAnchor,\n selectors: [[\"a\", \"mat-mini-fab\", \"\"]],\n hostVars: 15,\n hostBindings: function MatMiniFabAnchor_HostBindings(rf, ctx) {\n if (rf & 2) {\n i0.ɵɵattribute(\"disabled\", ctx._getDisabledAttribute())(\"tabindex\", ctx.disabled && !ctx.disabledInteractive ? -1 : ctx.tabIndex)(\"aria-disabled\", ctx._getAriaDisabled());\n i0.ɵɵclassMap(ctx.color ? \"mat-\" + ctx.color : \"\");\n i0.ɵɵclassProp(\"mat-mdc-button-disabled\", ctx.disabled)(\"mat-mdc-button-disabled-interactive\", ctx.disabledInteractive)(\"_mat-animation-noopable\", ctx._animationMode === \"NoopAnimations\")(\"mat-unthemed\", !ctx.color)(\"mat-mdc-button-base\", true);\n }\n },\n exportAs: [\"matButton\", \"matAnchor\"],\n features: [i0.ɵɵInheritDefinitionFeature],\n attrs: _c6,\n ngContentSelectors: _c2,\n decls: 7,\n vars: 4,\n consts: [[1, \"mat-mdc-button-persistent-ripple\"], [1, \"mdc-button__label\"], [1, \"mat-focus-indicator\"], [1, \"mat-mdc-button-touch-target\"]],\n template: function MatMiniFabAnchor_Template(rf, ctx) {\n if (rf & 1) {\n i0.ɵɵprojectionDef(_c1);\n i0.ɵɵelement(0, \"span\", 0);\n i0.ɵɵprojection(1);\n i0.ɵɵelementStart(2, \"span\", 1);\n i0.ɵɵprojection(3, 1);\n i0.ɵɵelementEnd();\n i0.ɵɵprojection(4, 2);\n i0.ɵɵelement(5, \"span\", 2)(6, \"span\", 3);\n }\n if (rf & 2) {\n i0.ɵɵclassProp(\"mdc-button__ripple\", !ctx._isFab)(\"mdc-fab__ripple\", ctx._isFab);\n }\n },\n styles: [_c7],\n encapsulation: 2,\n changeDetection: 0\n });\n }\n return MatMiniFabAnchor;\n})();\n/*#__PURE__*/(() => {\n (typeof ngDevMode === \"undefined\" || ngDevMode) && void 0;\n})();\n\n/**\n * Material Design icon button component. This type of button displays a single interactive icon for\n * users to perform an action.\n * See https://material.io/develop/web/components/buttons/icon-buttons/\n */\nlet MatIconButton = /*#__PURE__*/(() => {\n class MatIconButton extends MatButtonBase {\n constructor() {\n super();\n this._rippleLoader.configureRipple(this._elementRef.nativeElement, {\n centered: true\n });\n }\n static ɵfac = function MatIconButton_Factory(__ngFactoryType__) {\n return new (__ngFactoryType__ || MatIconButton)();\n };\n static ɵcmp = /* @__PURE__ */i0.ɵɵdefineComponent({\n type: MatIconButton,\n selectors: [[\"button\", \"mat-icon-button\", \"\"]],\n hostVars: 14,\n hostBindings: function MatIconButton_HostBindings(rf, ctx) {\n if (rf & 2) {\n i0.ɵɵattribute(\"disabled\", ctx._getDisabledAttribute())(\"aria-disabled\", ctx._getAriaDisabled());\n i0.ɵɵclassMap(ctx.color ? \"mat-\" + ctx.color : \"\");\n i0.ɵɵclassProp(\"mat-mdc-button-disabled\", ctx.disabled)(\"mat-mdc-button-disabled-interactive\", ctx.disabledInteractive)(\"_mat-animation-noopable\", ctx._animationMode === \"NoopAnimations\")(\"mat-unthemed\", !ctx.color)(\"mat-mdc-button-base\", true);\n }\n },\n exportAs: [\"matButton\"],\n features: [i0.ɵɵInheritDefinitionFeature],\n attrs: _c8,\n ngContentSelectors: _c9,\n decls: 4,\n vars: 0,\n consts: [[1, \"mat-mdc-button-persistent-ripple\", \"mdc-icon-button__ripple\"], [1, \"mat-focus-indicator\"], [1, \"mat-mdc-button-touch-target\"]],\n template: function MatIconButton_Template(rf, ctx) {\n if (rf & 1) {\n i0.ɵɵprojectionDef();\n i0.ɵɵelement(0, \"span\", 0);\n i0.ɵɵprojection(1);\n i0.ɵɵelement(2, \"span\", 1)(3, \"span\", 2);\n }\n },\n styles: [\".mat-mdc-icon-button{-webkit-user-select:none;user-select:none;display:inline-block;position:relative;box-sizing:border-box;border:none;outline:none;background-color:rgba(0,0,0,0);fill:currentColor;color:inherit;text-decoration:none;cursor:pointer;z-index:0;overflow:visible;border-radius:50%;flex-shrink:0;text-align:center;width:var(--mdc-icon-button-state-layer-size, 40px);height:var(--mdc-icon-button-state-layer-size, 40px);padding:calc(calc(var(--mdc-icon-button-state-layer-size, 40px) - var(--mdc-icon-button-icon-size, 24px)) / 2);font-size:var(--mdc-icon-button-icon-size, 24px);color:var(--mdc-icon-button-icon-color, var(--mat-sys-on-surface-variant));-webkit-tap-highlight-color:rgba(0,0,0,0)}.mat-mdc-icon-button .mat-mdc-button-ripple,.mat-mdc-icon-button .mat-mdc-button-persistent-ripple,.mat-mdc-icon-button .mat-mdc-button-persistent-ripple::before{top:0;left:0;right:0;bottom:0;position:absolute;pointer-events:none;border-radius:inherit}.mat-mdc-icon-button .mat-mdc-button-ripple{overflow:hidden}.mat-mdc-icon-button .mat-mdc-button-persistent-ripple::before{content:\\\"\\\";opacity:0}.mat-mdc-icon-button .mdc-button__label,.mat-mdc-icon-button .mat-icon{z-index:1;position:relative}.mat-mdc-icon-button .mat-focus-indicator{top:0;left:0;right:0;bottom:0;position:absolute}.mat-mdc-icon-button:focus>.mat-focus-indicator::before{content:\\\"\\\"}.mat-mdc-icon-button .mat-ripple-element{background-color:var(--mat-icon-button-ripple-color, color-mix(in srgb, var(--mat-sys-on-surface-variant) calc(var(--mat-sys-pressed-state-layer-opacity) * 100%), transparent))}.mat-mdc-icon-button .mat-mdc-button-persistent-ripple::before{background-color:var(--mat-icon-button-state-layer-color, var(--mat-sys-on-surface-variant))}.mat-mdc-icon-button.mat-mdc-button-disabled .mat-mdc-button-persistent-ripple::before{background-color:var(--mat-icon-button-disabled-state-layer-color, var(--mat-sys-on-surface-variant))}.mat-mdc-icon-button:hover>.mat-mdc-button-persistent-ripple::before{opacity:var(--mat-icon-button-hover-state-layer-opacity, var(--mat-sys-hover-state-layer-opacity))}.mat-mdc-icon-button.cdk-program-focused>.mat-mdc-button-persistent-ripple::before,.mat-mdc-icon-button.cdk-keyboard-focused>.mat-mdc-button-persistent-ripple::before,.mat-mdc-icon-button.mat-mdc-button-disabled-interactive:focus>.mat-mdc-button-persistent-ripple::before{opacity:var(--mat-icon-button-focus-state-layer-opacity, var(--mat-sys-focus-state-layer-opacity))}.mat-mdc-icon-button:active>.mat-mdc-button-persistent-ripple::before{opacity:var(--mat-icon-button-pressed-state-layer-opacity, var(--mat-sys-pressed-state-layer-opacity))}.mat-mdc-icon-button .mat-mdc-button-touch-target{position:absolute;top:50%;height:48px;left:50%;width:48px;transform:translate(-50%, -50%);display:var(--mat-icon-button-touch-target-display, block)}.mat-mdc-icon-button._mat-animation-noopable{transition:none !important;animation:none !important}.mat-mdc-icon-button[disabled],.mat-mdc-icon-button.mat-mdc-button-disabled{cursor:default;pointer-events:none;color:var(--mdc-icon-button-disabled-icon-color, color-mix(in srgb, var(--mat-sys-on-surface) 38%, transparent))}.mat-mdc-icon-button.mat-mdc-button-disabled-interactive{pointer-events:auto}.mat-mdc-icon-button img,.mat-mdc-icon-button svg{width:var(--mdc-icon-button-icon-size, 24px);height:var(--mdc-icon-button-icon-size, 24px);vertical-align:baseline}.mat-mdc-icon-button .mat-mdc-button-persistent-ripple{border-radius:50%}.mat-mdc-icon-button[hidden]{display:none}.mat-mdc-icon-button.mat-unthemed:not(.mdc-ripple-upgraded):focus::before,.mat-mdc-icon-button.mat-primary:not(.mdc-ripple-upgraded):focus::before,.mat-mdc-icon-button.mat-accent:not(.mdc-ripple-upgraded):focus::before,.mat-mdc-icon-button.mat-warn:not(.mdc-ripple-upgraded):focus::before{background:rgba(0,0,0,0);opacity:1}\", _c4],\n encapsulation: 2,\n changeDetection: 0\n });\n }\n return MatIconButton;\n})();\n/*#__PURE__*/(() => {\n (typeof ngDevMode === \"undefined\" || ngDevMode) && void 0;\n})();\n/**\n * Material Design icon button component for anchor elements. This button displays a single\n * interaction icon that allows users to navigate across different routes or pages.\n * See https://material.io/develop/web/components/buttons/icon-buttons/\n */\nlet MatIconAnchor = /*#__PURE__*/(() => {\n class MatIconAnchor extends MatAnchorBase {\n static ɵfac = /* @__PURE__ */(() => {\n let ɵMatIconAnchor_BaseFactory;\n return function MatIconAnchor_Factory(__ngFactoryType__) {\n return (ɵMatIconAnchor_BaseFactory || (ɵMatIconAnchor_BaseFactory = i0.ɵɵgetInheritedFactory(MatIconAnchor)))(__ngFactoryType__ || MatIconAnchor);\n };\n })();\n static ɵcmp = /* @__PURE__ */i0.ɵɵdefineComponent({\n type: MatIconAnchor,\n selectors: [[\"a\", \"mat-icon-button\", \"\"]],\n hostVars: 15,\n hostBindings: function MatIconAnchor_HostBindings(rf, ctx) {\n if (rf & 2) {\n i0.ɵɵattribute(\"disabled\", ctx._getDisabledAttribute())(\"tabindex\", ctx.disabled && !ctx.disabledInteractive ? -1 : ctx.tabIndex)(\"aria-disabled\", ctx._getAriaDisabled());\n i0.ɵɵclassMap(ctx.color ? \"mat-\" + ctx.color : \"\");\n i0.ɵɵclassProp(\"mat-mdc-button-disabled\", ctx.disabled)(\"mat-mdc-button-disabled-interactive\", ctx.disabledInteractive)(\"_mat-animation-noopable\", ctx._animationMode === \"NoopAnimations\")(\"mat-unthemed\", !ctx.color)(\"mat-mdc-button-base\", true);\n }\n },\n exportAs: [\"matButton\", \"matAnchor\"],\n features: [i0.ɵɵInheritDefinitionFeature],\n attrs: _c8,\n ngContentSelectors: _c9,\n decls: 4,\n vars: 0,\n consts: [[1, \"mat-mdc-button-persistent-ripple\", \"mdc-icon-button__ripple\"], [1, \"mat-focus-indicator\"], [1, \"mat-mdc-button-touch-target\"]],\n template: function MatIconAnchor_Template(rf, ctx) {\n if (rf & 1) {\n i0.ɵɵprojectionDef();\n i0.ɵɵelement(0, \"span\", 0);\n i0.ɵɵprojection(1);\n i0.ɵɵelement(2, \"span\", 1)(3, \"span\", 2);\n }\n },\n styles: [_c10, _c4],\n encapsulation: 2,\n changeDetection: 0\n });\n }\n return MatIconAnchor;\n})();\n/*#__PURE__*/(() => {\n (typeof ngDevMode === \"undefined\" || ngDevMode) && void 0;\n})();\nlet MatButtonModule = /*#__PURE__*/(() => {\n class MatButtonModule {\n static ɵfac = function MatButtonModule_Factory(__ngFactoryType__) {\n return new (__ngFactoryType__ || MatButtonModule)();\n };\n static ɵmod = /* @__PURE__ */i0.ɵɵdefineNgModule({\n type: MatButtonModule\n });\n static ɵinj = /* @__PURE__ */i0.ɵɵdefineInjector({\n imports: [MatCommonModule, MatRippleModule, MatCommonModule]\n });\n }\n return MatButtonModule;\n})();\n/*#__PURE__*/(() => {\n (typeof ngDevMode === \"undefined\" || ngDevMode) && void 0;\n})();\n\n/**\n * Generated bundle index. Do not edit.\n */\n\nexport { MAT_BUTTON_CONFIG, MAT_FAB_DEFAULT_OPTIONS, MAT_FAB_DEFAULT_OPTIONS_FACTORY, MatAnchor, MatButton, MatButtonModule, MatFabAnchor, MatFabButton, MatIconAnchor, MatIconButton, MatMiniFabAnchor, MatMiniFabButton };\n","import * as i0 from '@angular/core';\nimport { InjectionToken, Directive, inject, Component, ViewEncapsulation, ChangeDetectionStrategy, NgZone, ElementRef, ChangeDetectorRef, ANIMATION_MODULE_TYPE, afterRender, ViewChild, Injector, TemplateRef, Injectable, NgModule } from '@angular/core';\nimport { MatButton, MatButtonModule } from '@angular/material/button';\nimport { Subject, of } from 'rxjs';\nimport { DOCUMENT } from '@angular/common';\nimport { BasePortalOutlet, CdkPortalOutlet, ComponentPortal, TemplatePortal, PortalModule } from '@angular/cdk/portal';\nimport { _IdGenerator, LiveAnnouncer } from '@angular/cdk/a11y';\nimport { Platform } from '@angular/cdk/platform';\nimport { take, takeUntil } from 'rxjs/operators';\nimport { BreakpointObserver, Breakpoints } from '@angular/cdk/layout';\nimport { Overlay, OverlayConfig, OverlayModule } from '@angular/cdk/overlay';\nimport { MatCommonModule } from '@angular/material/core';\n\n/** Maximum amount of milliseconds that can be passed into setTimeout. */\nfunction SimpleSnackBar_Conditional_2_Template(rf, ctx) {\n if (rf & 1) {\n const _r1 = i0.ɵɵgetCurrentView();\n i0.ɵɵelementStart(0, \"div\", 1)(1, \"button\", 2);\n i0.ɵɵlistener(\"click\", function SimpleSnackBar_Conditional_2_Template_button_click_1_listener() {\n i0.ɵɵrestoreView(_r1);\n const ctx_r1 = i0.ɵɵnextContext();\n return i0.ɵɵresetView(ctx_r1.action());\n });\n i0.ɵɵtext(2);\n i0.ɵɵelementEnd()();\n }\n if (rf & 2) {\n const ctx_r1 = i0.ɵɵnextContext();\n i0.ɵɵadvance(2);\n i0.ɵɵtextInterpolate1(\" \", ctx_r1.data.action, \" \");\n }\n}\nconst _c0 = [\"label\"];\nfunction MatSnackBarContainer_ng_template_4_Template(rf, ctx) {}\nconst MAX_TIMEOUT = /*#__PURE__*/Math.pow(2, 31) - 1;\n/**\n * Reference to a snack bar dispatched from the snack bar service.\n */\nclass MatSnackBarRef {\n _overlayRef;\n /** The instance of the component making up the content of the snack bar. */\n instance;\n /**\n * The instance of the component making up the content of the snack bar.\n * @docs-private\n */\n containerInstance;\n /** Subject for notifying the user that the snack bar has been dismissed. */\n _afterDismissed = /*#__PURE__*/new Subject();\n /** Subject for notifying the user that the snack bar has opened and appeared. */\n _afterOpened = /*#__PURE__*/new Subject();\n /** Subject for notifying the user that the snack bar action was called. */\n _onAction = /*#__PURE__*/new Subject();\n /**\n * Timeout ID for the duration setTimeout call. Used to clear the timeout if the snackbar is\n * dismissed before the duration passes.\n */\n _durationTimeoutId;\n /** Whether the snack bar was dismissed using the action button. */\n _dismissedByAction = false;\n constructor(containerInstance, _overlayRef) {\n this._overlayRef = _overlayRef;\n this.containerInstance = containerInstance;\n containerInstance._onExit.subscribe(() => this._finishDismiss());\n }\n /** Dismisses the snack bar. */\n dismiss() {\n if (!this._afterDismissed.closed) {\n this.containerInstance.exit();\n }\n clearTimeout(this._durationTimeoutId);\n }\n /** Marks the snackbar action clicked. */\n dismissWithAction() {\n if (!this._onAction.closed) {\n this._dismissedByAction = true;\n this._onAction.next();\n this._onAction.complete();\n this.dismiss();\n }\n clearTimeout(this._durationTimeoutId);\n }\n /**\n * Marks the snackbar action clicked.\n * @deprecated Use `dismissWithAction` instead.\n * @breaking-change 8.0.0\n */\n closeWithAction() {\n this.dismissWithAction();\n }\n /** Dismisses the snack bar after some duration */\n _dismissAfter(duration) {\n // Note that we need to cap the duration to the maximum value for setTimeout, because\n // it'll revert to 1 if somebody passes in something greater (e.g. `Infinity`). See #17234.\n this._durationTimeoutId = setTimeout(() => this.dismiss(), Math.min(duration, MAX_TIMEOUT));\n }\n /** Marks the snackbar as opened */\n _open() {\n if (!this._afterOpened.closed) {\n this._afterOpened.next();\n this._afterOpened.complete();\n }\n }\n /** Cleans up the DOM after closing. */\n _finishDismiss() {\n this._overlayRef.dispose();\n if (!this._onAction.closed) {\n this._onAction.complete();\n }\n this._afterDismissed.next({\n dismissedByAction: this._dismissedByAction\n });\n this._afterDismissed.complete();\n this._dismissedByAction = false;\n }\n /** Gets an observable that is notified when the snack bar is finished closing. */\n afterDismissed() {\n return this._afterDismissed;\n }\n /** Gets an observable that is notified when the snack bar has opened and appeared. */\n afterOpened() {\n return this.containerInstance._onEnter;\n }\n /** Gets an observable that is notified when the snack bar action is called. */\n onAction() {\n return this._onAction;\n }\n}\n\n/** Injection token that can be used to access the data that was passed in to a snack bar. */\nconst MAT_SNACK_BAR_DATA = /*#__PURE__*/new InjectionToken('MatSnackBarData');\n/**\n * Configuration used when opening a snack-bar.\n */\nclass MatSnackBarConfig {\n /** The politeness level for the MatAriaLiveAnnouncer announcement. */\n politeness = 'assertive';\n /**\n * Message to be announced by the LiveAnnouncer. When opening a snackbar without a custom\n * component or template, the announcement message will default to the specified message.\n */\n announcementMessage = '';\n /**\n * The view container that serves as the parent for the snackbar for the purposes of dependency\n * injection. Note: this does not affect where the snackbar is inserted in the DOM.\n */\n viewContainerRef;\n /** The length of time in milliseconds to wait before automatically dismissing the snack bar. */\n duration = 0;\n /** Extra CSS classes to be added to the snack bar container. */\n panelClass;\n /** Text layout direction for the snack bar. */\n direction;\n /** Data being injected into the child component. */\n data = null;\n /** The horizontal position to place the snack bar. */\n horizontalPosition = 'center';\n /** The vertical position to place the snack bar. */\n verticalPosition = 'bottom';\n}\n\n/** Directive that should be applied to the text element to be rendered in the snack bar. */\nlet MatSnackBarLabel = /*#__PURE__*/(() => {\n class MatSnackBarLabel {\n static ɵfac = function MatSnackBarLabel_Factory(__ngFactoryType__) {\n return new (__ngFactoryType__ || MatSnackBarLabel)();\n };\n static ɵdir = /* @__PURE__ */i0.ɵɵdefineDirective({\n type: MatSnackBarLabel,\n selectors: [[\"\", \"matSnackBarLabel\", \"\"]],\n hostAttrs: [1, \"mat-mdc-snack-bar-label\", \"mdc-snackbar__label\"]\n });\n }\n return MatSnackBarLabel;\n})();\n/*#__PURE__*/(() => {\n (typeof ngDevMode === \"undefined\" || ngDevMode) && void 0;\n})();\n/** Directive that should be applied to the element containing the snack bar's action buttons. */\nlet MatSnackBarActions = /*#__PURE__*/(() => {\n class MatSnackBarActions {\n static ɵfac = function MatSnackBarActions_Factory(__ngFactoryType__) {\n return new (__ngFactoryType__ || MatSnackBarActions)();\n };\n static ɵdir = /* @__PURE__ */i0.ɵɵdefineDirective({\n type: MatSnackBarActions,\n selectors: [[\"\", \"matSnackBarActions\", \"\"]],\n hostAttrs: [1, \"mat-mdc-snack-bar-actions\", \"mdc-snackbar__actions\"]\n });\n }\n return MatSnackBarActions;\n})();\n/*#__PURE__*/(() => {\n (typeof ngDevMode === \"undefined\" || ngDevMode) && void 0;\n})();\n/** Directive that should be applied to each of the snack bar's action buttons. */\nlet MatSnackBarAction = /*#__PURE__*/(() => {\n class MatSnackBarAction {\n static ɵfac = function MatSnackBarAction_Factory(__ngFactoryType__) {\n return new (__ngFactoryType__ || MatSnackBarAction)();\n };\n static ɵdir = /* @__PURE__ */i0.ɵɵdefineDirective({\n type: MatSnackBarAction,\n selectors: [[\"\", \"matSnackBarAction\", \"\"]],\n hostAttrs: [1, \"mat-mdc-snack-bar-action\", \"mdc-snackbar__action\"]\n });\n }\n return MatSnackBarAction;\n})();\n/*#__PURE__*/(() => {\n (typeof ngDevMode === \"undefined\" || ngDevMode) && void 0;\n})();\nlet SimpleSnackBar = /*#__PURE__*/(() => {\n class SimpleSnackBar {\n snackBarRef = inject(MatSnackBarRef);\n data = inject(MAT_SNACK_BAR_DATA);\n constructor() {}\n /** Performs the action on the snack bar. */\n action() {\n this.snackBarRef.dismissWithAction();\n }\n /** If the action button should be shown. */\n get hasAction() {\n return !!this.data.action;\n }\n static ɵfac = function SimpleSnackBar_Factory(__ngFactoryType__) {\n return new (__ngFactoryType__ || SimpleSnackBar)();\n };\n static ɵcmp = /* @__PURE__ */i0.ɵɵdefineComponent({\n type: SimpleSnackBar,\n selectors: [[\"simple-snack-bar\"]],\n hostAttrs: [1, \"mat-mdc-simple-snack-bar\"],\n exportAs: [\"matSnackBar\"],\n decls: 3,\n vars: 2,\n consts: [[\"matSnackBarLabel\", \"\"], [\"matSnackBarActions\", \"\"], [\"mat-button\", \"\", \"matSnackBarAction\", \"\", 3, \"click\"]],\n template: function SimpleSnackBar_Template(rf, ctx) {\n if (rf & 1) {\n i0.ɵɵelementStart(0, \"div\", 0);\n i0.ɵɵtext(1);\n i0.ɵɵelementEnd();\n i0.ɵɵtemplate(2, SimpleSnackBar_Conditional_2_Template, 3, 1, \"div\", 1);\n }\n if (rf & 2) {\n i0.ɵɵadvance();\n i0.ɵɵtextInterpolate1(\" \", ctx.data.message, \"\\n\");\n i0.ɵɵadvance();\n i0.ɵɵconditional(ctx.hasAction ? 2 : -1);\n }\n },\n dependencies: [MatButton, MatSnackBarLabel, MatSnackBarActions, MatSnackBarAction],\n styles: [\".mat-mdc-simple-snack-bar{display:flex}\"],\n encapsulation: 2,\n changeDetection: 0\n });\n }\n return SimpleSnackBar;\n})();\n/*#__PURE__*/(() => {\n (typeof ngDevMode === \"undefined\" || ngDevMode) && void 0;\n})();\nconst ENTER_ANIMATION = '_mat-snack-bar-enter';\nconst EXIT_ANIMATION = '_mat-snack-bar-exit';\n/**\n * Internal component that wraps user-provided snack bar content.\n * @docs-private\n */\nlet MatSnackBarContainer = /*#__PURE__*/(() => {\n class MatSnackBarContainer extends BasePortalOutlet {\n _ngZone = inject(NgZone);\n _elementRef = inject(ElementRef);\n _changeDetectorRef = inject(ChangeDetectorRef);\n _platform = inject(Platform);\n _rendersRef;\n _animationsDisabled = inject(ANIMATION_MODULE_TYPE, {\n optional: true\n }) === 'NoopAnimations';\n snackBarConfig = inject(MatSnackBarConfig);\n _document = inject(DOCUMENT);\n _trackedModals = new Set();\n _enterFallback;\n _exitFallback;\n _renders = new Subject();\n /** The number of milliseconds to wait before announcing the snack bar's content. */\n _announceDelay = 150;\n /** The timeout for announcing the snack bar's content. */\n _announceTimeoutId;\n /** Whether the component has been destroyed. */\n _destroyed = false;\n /** The portal outlet inside of this container into which the snack bar content will be loaded. */\n _portalOutlet;\n /** Subject for notifying that the snack bar has announced to screen readers. */\n _onAnnounce = new Subject();\n /** Subject for notifying that the snack bar has exited from view. */\n _onExit = new Subject();\n /** Subject for notifying that the snack bar has finished entering the view. */\n _onEnter = new Subject();\n /** The state of the snack bar animations. */\n _animationState = 'void';\n /** aria-live value for the live region. */\n _live;\n /**\n * Element that will have the `mdc-snackbar__label` class applied if the attached component\n * or template does not have it. This ensures that the appropriate structure, typography, and\n * color is applied to the attached view.\n */\n _label;\n /**\n * Role of the live region. This is only for Firefox as there is a known issue where Firefox +\n * JAWS does not read out aria-live message.\n */\n _role;\n /** Unique ID of the aria-live element. */\n _liveElementId = inject(_IdGenerator).getId('mat-snack-bar-container-live-');\n constructor() {\n super();\n const config = this.snackBarConfig;\n // Use aria-live rather than a live role like 'alert' or 'status'\n // because NVDA and JAWS have show inconsistent behavior with live roles.\n if (config.politeness === 'assertive' && !config.announcementMessage) {\n this._live = 'assertive';\n } else if (config.politeness === 'off') {\n this._live = 'off';\n } else {\n this._live = 'polite';\n }\n // Only set role for Firefox. Set role based on aria-live because setting role=\"alert\" implies\n // aria-live=\"assertive\" which may cause issues if aria-live is set to \"polite\" above.\n if (this._platform.FIREFOX) {\n if (this._live === 'polite') {\n this._role = 'status';\n }\n if (this._live === 'assertive') {\n this._role = 'alert';\n }\n }\n // Note: ideally we'd just do an `afterNextRender` in the places where we need to delay\n // something, however in some cases (TestBed teardown) the injector can be destroyed at an\n // unexpected time, causing the `afterRender` to fail.\n this._rendersRef = afterRender(() => this._renders.next(), {\n manualCleanup: true\n });\n }\n /** Attach a component portal as content to this snack bar container. */\n attachComponentPortal(portal) {\n this._assertNotAttached();\n const result = this._portalOutlet.attachComponentPortal(portal);\n this._afterPortalAttached();\n return result;\n }\n /** Attach a template portal as content to this snack bar container. */\n attachTemplatePortal(portal) {\n this._assertNotAttached();\n const result = this._portalOutlet.attachTemplatePortal(portal);\n this._afterPortalAttached();\n return result;\n }\n /**\n * Attaches a DOM portal to the snack bar container.\n * @deprecated To be turned into a method.\n * @breaking-change 10.0.0\n */\n attachDomPortal = portal => {\n this._assertNotAttached();\n const result = this._portalOutlet.attachDomPortal(portal);\n this._afterPortalAttached();\n return result;\n };\n /** Handle end of animations, updating the state of the snackbar. */\n onAnimationEnd(animationName) {\n if (animationName === EXIT_ANIMATION) {\n this._completeExit();\n } else if (animationName === ENTER_ANIMATION) {\n clearTimeout(this._enterFallback);\n this._ngZone.run(() => {\n this._onEnter.next();\n this._onEnter.complete();\n });\n }\n }\n /** Begin animation of snack bar entrance into view. */\n enter() {\n if (!this._destroyed) {\n this._animationState = 'visible';\n // _animationState lives in host bindings and `detectChanges` does not refresh host bindings\n // so we have to call `markForCheck` to ensure the host view is refreshed eventually.\n this._changeDetectorRef.markForCheck();\n this._changeDetectorRef.detectChanges();\n this._screenReaderAnnounce();\n if (this._animationsDisabled) {\n this._renders.pipe(take(1)).subscribe(() => {\n this._ngZone.run(() => queueMicrotask(() => this.onAnimationEnd(ENTER_ANIMATION)));\n });\n } else {\n clearTimeout(this._enterFallback);\n this._enterFallback = setTimeout(() => {\n // The snack bar will stay invisible if it fails to animate. Add a fallback class so it\n // becomes visible. This can happen in some apps that do `* {animation: none !important}`.\n this._elementRef.nativeElement.classList.add('mat-snack-bar-fallback-visible');\n this.onAnimationEnd(ENTER_ANIMATION);\n }, 200);\n }\n }\n }\n /** Begin animation of the snack bar exiting from view. */\n exit() {\n if (this._destroyed) {\n return of(undefined);\n }\n // It's common for snack bars to be opened by random outside calls like HTTP requests or\n // errors. Run inside the NgZone to ensure that it functions correctly.\n this._ngZone.run(() => {\n // Note: this one transitions to `hidden`, rather than `void`, in order to handle the case\n // where multiple snack bars are opened in quick succession (e.g. two consecutive calls to\n // `MatSnackBar.open`).\n this._animationState = 'hidden';\n this._changeDetectorRef.markForCheck();\n // Mark this element with an 'exit' attribute to indicate that the snackbar has\n // been dismissed and will soon be removed from the DOM. This is used by the snackbar\n // test harness.\n this._elementRef.nativeElement.setAttribute('mat-exit', '');\n // If the snack bar hasn't been announced by the time it exits it wouldn't have been open\n // long enough to visually read it either, so clear the timeout for announcing.\n clearTimeout(this._announceTimeoutId);\n if (this._animationsDisabled) {\n this._renders.pipe(take(1)).subscribe(() => {\n this._ngZone.run(() => queueMicrotask(() => this.onAnimationEnd(EXIT_ANIMATION)));\n });\n } else {\n clearTimeout(this._exitFallback);\n this._exitFallback = setTimeout(() => this.onAnimationEnd(EXIT_ANIMATION), 200);\n }\n });\n return this._onExit;\n }\n /** Makes sure the exit callbacks have been invoked when the element is destroyed. */\n ngOnDestroy() {\n this._destroyed = true;\n this._clearFromModals();\n this._completeExit();\n this._renders.complete();\n this._rendersRef.destroy();\n }\n _completeExit() {\n clearTimeout(this._exitFallback);\n queueMicrotask(() => {\n this._onExit.next();\n this._onExit.complete();\n });\n }\n /**\n * Called after the portal contents have been attached. Can be\n * used to modify the DOM once it's guaranteed to be in place.\n */\n _afterPortalAttached() {\n const element = this._elementRef.nativeElement;\n const panelClasses = this.snackBarConfig.panelClass;\n if (panelClasses) {\n if (Array.isArray(panelClasses)) {\n // Note that we can't use a spread here, because IE doesn't support multiple arguments.\n panelClasses.forEach(cssClass => element.classList.add(cssClass));\n } else {\n element.classList.add(panelClasses);\n }\n }\n this._exposeToModals();\n // Check to see if the attached component or template uses the MDC template structure,\n // specifically the MDC label. If not, the container should apply the MDC label class to this\n // component's label container, which will apply MDC's label styles to the attached view.\n const label = this._label.nativeElement;\n const labelClass = 'mdc-snackbar__label';\n label.classList.toggle(labelClass, !label.querySelector(`.${labelClass}`));\n }\n /**\n * Some browsers won't expose the accessibility node of the live element if there is an\n * `aria-modal` and the live element is outside of it. This method works around the issue by\n * pointing the `aria-owns` of all modals to the live element.\n */\n _exposeToModals() {\n // TODO(http://github.com/angular/components/issues/26853): consider de-duplicating this with the\n // `LiveAnnouncer` and any other usages.\n //\n // Note that the selector here is limited to CDK overlays at the moment in order to reduce the\n // section of the DOM we need to look through. This should cover all the cases we support, but\n // the selector can be expanded if it turns out to be too narrow.\n const id = this._liveElementId;\n const modals = this._document.querySelectorAll('body > .cdk-overlay-container [aria-modal=\"true\"]');\n for (let i = 0; i < modals.length; i++) {\n const modal = modals[i];\n const ariaOwns = modal.getAttribute('aria-owns');\n this._trackedModals.add(modal);\n if (!ariaOwns) {\n modal.setAttribute('aria-owns', id);\n } else if (ariaOwns.indexOf(id) === -1) {\n modal.setAttribute('aria-owns', ariaOwns + ' ' + id);\n }\n }\n }\n /** Clears the references to the live element from any modals it was added to. */\n _clearFromModals() {\n this._trackedModals.forEach(modal => {\n const ariaOwns = modal.getAttribute('aria-owns');\n if (ariaOwns) {\n const newValue = ariaOwns.replace(this._liveElementId, '').trim();\n if (newValue.length > 0) {\n modal.setAttribute('aria-owns', newValue);\n } else {\n modal.removeAttribute('aria-owns');\n }\n }\n });\n this._trackedModals.clear();\n }\n /** Asserts that no content is already attached to the container. */\n _assertNotAttached() {\n if (this._portalOutlet.hasAttached() && (typeof ngDevMode === 'undefined' || ngDevMode)) {\n throw Error('Attempting to attach snack bar content after content is already attached');\n }\n }\n /**\n * Starts a timeout to move the snack bar content to the live region so screen readers will\n * announce it.\n */\n _screenReaderAnnounce() {\n if (this._announceTimeoutId) {\n return;\n }\n this._ngZone.runOutsideAngular(() => {\n this._announceTimeoutId = setTimeout(() => {\n if (this._destroyed) {\n return;\n }\n const element = this._elementRef.nativeElement;\n const inertElement = element.querySelector('[aria-hidden]');\n const liveElement = element.querySelector('[aria-live]');\n if (inertElement && liveElement) {\n // If an element in the snack bar content is focused before being moved\n // track it and restore focus after moving to the live region.\n let focusedElement = null;\n if (this._platform.isBrowser && document.activeElement instanceof HTMLElement && inertElement.contains(document.activeElement)) {\n focusedElement = document.activeElement;\n }\n inertElement.removeAttribute('aria-hidden');\n liveElement.appendChild(inertElement);\n focusedElement?.focus();\n this._onAnnounce.next();\n this._onAnnounce.complete();\n }\n }, this._announceDelay);\n });\n }\n static ɵfac = function MatSnackBarContainer_Factory(__ngFactoryType__) {\n return new (__ngFactoryType__ || MatSnackBarContainer)();\n };\n static ɵcmp = /* @__PURE__ */i0.ɵɵdefineComponent({\n type: MatSnackBarContainer,\n selectors: [[\"mat-snack-bar-container\"]],\n viewQuery: function MatSnackBarContainer_Query(rf, ctx) {\n if (rf & 1) {\n i0.ɵɵviewQuery(CdkPortalOutlet, 7);\n i0.ɵɵviewQuery(_c0, 7);\n }\n if (rf & 2) {\n let _t;\n i0.ɵɵqueryRefresh(_t = i0.ɵɵloadQuery()) && (ctx._portalOutlet = _t.first);\n i0.ɵɵqueryRefresh(_t = i0.ɵɵloadQuery()) && (ctx._label = _t.first);\n }\n },\n hostAttrs: [1, \"mdc-snackbar\", \"mat-mdc-snack-bar-container\"],\n hostVars: 6,\n hostBindings: function MatSnackBarContainer_HostBindings(rf, ctx) {\n if (rf & 1) {\n i0.ɵɵlistener(\"animationend\", function MatSnackBarContainer_animationend_HostBindingHandler($event) {\n return ctx.onAnimationEnd($event.animationName);\n })(\"animationcancel\", function MatSnackBarContainer_animationcancel_HostBindingHandler($event) {\n return ctx.onAnimationEnd($event.animationName);\n });\n }\n if (rf & 2) {\n i0.ɵɵclassProp(\"mat-snack-bar-container-enter\", ctx._animationState === \"visible\")(\"mat-snack-bar-container-exit\", ctx._animationState === \"hidden\")(\"mat-snack-bar-container-animations-enabled\", !ctx._animationsDisabled);\n }\n },\n features: [i0.ɵɵInheritDefinitionFeature],\n decls: 6,\n vars: 3,\n consts: [[\"label\", \"\"], [1, \"mdc-snackbar__surface\", \"mat-mdc-snackbar-surface\"], [1, \"mat-mdc-snack-bar-label\"], [\"aria-hidden\", \"true\"], [\"cdkPortalOutlet\", \"\"]],\n template: function MatSnackBarContainer_Template(rf, ctx) {\n if (rf & 1) {\n i0.ɵɵelementStart(0, \"div\", 1)(1, \"div\", 2, 0)(3, \"div\", 3);\n i0.ɵɵtemplate(4, MatSnackBarContainer_ng_template_4_Template, 0, 0, \"ng-template\", 4);\n i0.ɵɵelementEnd();\n i0.ɵɵelement(5, \"div\");\n i0.ɵɵelementEnd()();\n }\n if (rf & 2) {\n i0.ɵɵadvance(5);\n i0.ɵɵattribute(\"aria-live\", ctx._live)(\"role\", ctx._role)(\"id\", ctx._liveElementId);\n }\n },\n dependencies: [CdkPortalOutlet],\n styles: [\"@keyframes _mat-snack-bar-enter{from{transform:scale(0.8);opacity:0}to{transform:scale(1);opacity:1}}@keyframes _mat-snack-bar-exit{from{opacity:1}to{opacity:0}}.mat-mdc-snack-bar-container{display:flex;align-items:center;justify-content:center;box-sizing:border-box;-webkit-tap-highlight-color:rgba(0,0,0,0);margin:8px}.mat-mdc-snack-bar-handset .mat-mdc-snack-bar-container{width:100vw}.mat-snack-bar-container-animations-enabled{opacity:0}.mat-snack-bar-container-animations-enabled.mat-snack-bar-fallback-visible{opacity:1}.mat-snack-bar-container-animations-enabled.mat-snack-bar-container-enter{animation:_mat-snack-bar-enter 150ms cubic-bezier(0, 0, 0.2, 1) forwards}.mat-snack-bar-container-animations-enabled.mat-snack-bar-container-exit{animation:_mat-snack-bar-exit 75ms cubic-bezier(0.4, 0, 1, 1) forwards}.mat-mdc-snackbar-surface{box-shadow:0px 3px 5px -1px rgba(0, 0, 0, 0.2), 0px 6px 10px 0px rgba(0, 0, 0, 0.14), 0px 1px 18px 0px rgba(0, 0, 0, 0.12);display:flex;align-items:center;justify-content:flex-start;box-sizing:border-box;padding-left:0;padding-right:8px}[dir=rtl] .mat-mdc-snackbar-surface{padding-right:0;padding-left:8px}.mat-mdc-snack-bar-container .mat-mdc-snackbar-surface{min-width:344px;max-width:672px}.mat-mdc-snack-bar-handset .mat-mdc-snackbar-surface{width:100%;min-width:0}@media(forced-colors: active){.mat-mdc-snackbar-surface{outline:solid 1px}}.mat-mdc-snack-bar-container .mat-mdc-snackbar-surface{color:var(--mdc-snackbar-supporting-text-color, var(--mat-sys-inverse-on-surface));border-radius:var(--mdc-snackbar-container-shape, var(--mat-sys-corner-extra-small));background-color:var(--mdc-snackbar-container-color, var(--mat-sys-inverse-surface))}.mdc-snackbar__label{width:100%;flex-grow:1;box-sizing:border-box;margin:0;padding:14px 8px 14px 16px}[dir=rtl] .mdc-snackbar__label{padding-left:8px;padding-right:16px}.mat-mdc-snack-bar-container .mdc-snackbar__label{font-family:var(--mdc-snackbar-supporting-text-font, var(--mat-sys-body-medium-font));font-size:var(--mdc-snackbar-supporting-text-size, var(--mat-sys-body-medium-size));font-weight:var(--mdc-snackbar-supporting-text-weight, var(--mat-sys-body-medium-weight));line-height:var(--mdc-snackbar-supporting-text-line-height, var(--mat-sys-body-medium-line-height))}.mat-mdc-snack-bar-actions{display:flex;flex-shrink:0;align-items:center;box-sizing:border-box}.mat-mdc-snack-bar-handset,.mat-mdc-snack-bar-container,.mat-mdc-snack-bar-label{flex:1 1 auto}.mat-mdc-snack-bar-container .mat-mdc-button.mat-mdc-snack-bar-action:not(:disabled).mat-unthemed{color:var(--mat-snack-bar-button-color, var(--mat-sys-inverse-primary))}.mat-mdc-snack-bar-container .mat-mdc-button.mat-mdc-snack-bar-action:not(:disabled){--mat-text-button-state-layer-color:currentColor;--mat-text-button-ripple-color:currentColor}.mat-mdc-snack-bar-container .mat-mdc-button.mat-mdc-snack-bar-action:not(:disabled) .mat-ripple-element{opacity:.1}\"],\n encapsulation: 2\n });\n }\n return MatSnackBarContainer;\n})();\n/*#__PURE__*/(() => {\n (typeof ngDevMode === \"undefined\" || ngDevMode) && void 0;\n})();\n\n/** @docs-private */\nfunction MAT_SNACK_BAR_DEFAULT_OPTIONS_FACTORY() {\n return new MatSnackBarConfig();\n}\n/** Injection token that can be used to specify default snack bar. */\nconst MAT_SNACK_BAR_DEFAULT_OPTIONS = /*#__PURE__*/new InjectionToken('mat-snack-bar-default-options', {\n providedIn: 'root',\n factory: MAT_SNACK_BAR_DEFAULT_OPTIONS_FACTORY\n});\n/**\n * Service to dispatch Material Design snack bar messages.\n */\nlet MatSnackBar = /*#__PURE__*/(() => {\n class MatSnackBar {\n _overlay = inject(Overlay);\n _live = inject(LiveAnnouncer);\n _injector = inject(Injector);\n _breakpointObserver = inject(BreakpointObserver);\n _parentSnackBar = inject(MatSnackBar, {\n optional: true,\n skipSelf: true\n });\n _defaultConfig = inject(MAT_SNACK_BAR_DEFAULT_OPTIONS);\n /**\n * Reference to the current snack bar in the view *at this level* (in the Angular injector tree).\n * If there is a parent snack-bar service, all operations should delegate to that parent\n * via `_openedSnackBarRef`.\n */\n _snackBarRefAtThisLevel = null;\n /** The component that should be rendered as the snack bar's simple component. */\n simpleSnackBarComponent = SimpleSnackBar;\n /** The container component that attaches the provided template or component. */\n snackBarContainerComponent = MatSnackBarContainer;\n /** The CSS class to apply for handset mode. */\n handsetCssClass = 'mat-mdc-snack-bar-handset';\n /** Reference to the currently opened snackbar at *any* level. */\n get _openedSnackBarRef() {\n const parent = this._parentSnackBar;\n return parent ? parent._openedSnackBarRef : this._snackBarRefAtThisLevel;\n }\n set _openedSnackBarRef(value) {\n if (this._parentSnackBar) {\n this._parentSnackBar._openedSnackBarRef = value;\n } else {\n this._snackBarRefAtThisLevel = value;\n }\n }\n constructor() {}\n /**\n * Creates and dispatches a snack bar with a custom component for the content, removing any\n * currently opened snack bars.\n *\n * @param component Component to be instantiated.\n * @param config Extra configuration for the snack bar.\n */\n openFromComponent(component, config) {\n return this._attach(component, config);\n }\n /**\n * Creates and dispatches a snack bar with a custom template for the content, removing any\n * currently opened snack bars.\n *\n * @param template Template to be instantiated.\n * @param config Extra configuration for the snack bar.\n */\n openFromTemplate(template, config) {\n return this._attach(template, config);\n }\n /**\n * Opens a snackbar with a message and an optional action.\n * @param message The message to show in the snackbar.\n * @param action The label for the snackbar action.\n * @param config Additional configuration options for the snackbar.\n */\n open(message, action = '', config) {\n const _config = {\n ...this._defaultConfig,\n ...config\n };\n // Since the user doesn't have access to the component, we can\n // override the data to pass in our own message and action.\n _config.data = {\n message,\n action\n };\n // Since the snack bar has `role=\"alert\"`, we don't\n // want to announce the same message twice.\n if (_config.announcementMessage === message) {\n _config.announcementMessage = undefined;\n }\n return this.openFromComponent(this.simpleSnackBarComponent, _config);\n }\n /**\n * Dismisses the currently-visible snack bar.\n */\n dismiss() {\n if (this._openedSnackBarRef) {\n this._openedSnackBarRef.dismiss();\n }\n }\n ngOnDestroy() {\n // Only dismiss the snack bar at the current level on destroy.\n if (this._snackBarRefAtThisLevel) {\n this._snackBarRefAtThisLevel.dismiss();\n }\n }\n /**\n * Attaches the snack bar container component to the overlay.\n */\n _attachSnackBarContainer(overlayRef, config) {\n const userInjector = config && config.viewContainerRef && config.viewContainerRef.injector;\n const injector = Injector.create({\n parent: userInjector || this._injector,\n providers: [{\n provide: MatSnackBarConfig,\n useValue: config\n }]\n });\n const containerPortal = new ComponentPortal(this.snackBarContainerComponent, config.viewContainerRef, injector);\n const containerRef = overlayRef.attach(containerPortal);\n containerRef.instance.snackBarConfig = config;\n return containerRef.instance;\n }\n /**\n * Places a new component or a template as the content of the snack bar container.\n */\n _attach(content, userConfig) {\n const config = {\n ...new MatSnackBarConfig(),\n ...this._defaultConfig,\n ...userConfig\n };\n const overlayRef = this._createOverlay(config);\n const container = this._attachSnackBarContainer(overlayRef, config);\n const snackBarRef = new MatSnackBarRef(container, overlayRef);\n if (content instanceof TemplateRef) {\n const portal = new TemplatePortal(content, null, {\n $implicit: config.data,\n snackBarRef\n });\n snackBarRef.instance = container.attachTemplatePortal(portal);\n } else {\n const injector = this._createInjector(config, snackBarRef);\n const portal = new ComponentPortal(content, undefined, injector);\n const contentRef = container.attachComponentPortal(portal);\n // We can't pass this via the injector, because the injector is created earlier.\n snackBarRef.instance = contentRef.instance;\n }\n // Subscribe to the breakpoint observer and attach the mat-snack-bar-handset class as\n // appropriate. This class is applied to the overlay element because the overlay must expand to\n // fill the width of the screen for full width snackbars.\n this._breakpointObserver.observe(Breakpoints.HandsetPortrait).pipe(takeUntil(overlayRef.detachments())).subscribe(state => {\n overlayRef.overlayElement.classList.toggle(this.handsetCssClass, state.matches);\n });\n if (config.announcementMessage) {\n // Wait until the snack bar contents have been announced then deliver this message.\n container._onAnnounce.subscribe(() => {\n this._live.announce(config.announcementMessage, config.politeness);\n });\n }\n this._animateSnackBar(snackBarRef, config);\n this._openedSnackBarRef = snackBarRef;\n return this._openedSnackBarRef;\n }\n /** Animates the old snack bar out and the new one in. */\n _animateSnackBar(snackBarRef, config) {\n // When the snackbar is dismissed, clear the reference to it.\n snackBarRef.afterDismissed().subscribe(() => {\n // Clear the snackbar ref if it hasn't already been replaced by a newer snackbar.\n if (this._openedSnackBarRef == snackBarRef) {\n this._openedSnackBarRef = null;\n }\n if (config.announcementMessage) {\n this._live.clear();\n }\n });\n // If a dismiss timeout is provided, set up dismiss based on after the snackbar is opened.\n if (config.duration && config.duration > 0) {\n snackBarRef.afterOpened().subscribe(() => snackBarRef._dismissAfter(config.duration));\n }\n if (this._openedSnackBarRef) {\n // If a snack bar is already in view, dismiss it and enter the\n // new snack bar after exit animation is complete.\n this._openedSnackBarRef.afterDismissed().subscribe(() => {\n snackBarRef.containerInstance.enter();\n });\n this._openedSnackBarRef.dismiss();\n } else {\n // If no snack bar is in view, enter the new snack bar.\n snackBarRef.containerInstance.enter();\n }\n }\n /**\n * Creates a new overlay and places it in the correct location.\n * @param config The user-specified snack bar config.\n */\n _createOverlay(config) {\n const overlayConfig = new OverlayConfig();\n overlayConfig.direction = config.direction;\n let positionStrategy = this._overlay.position().global();\n // Set horizontal position.\n const isRtl = config.direction === 'rtl';\n const isLeft = config.horizontalPosition === 'left' || config.horizontalPosition === 'start' && !isRtl || config.horizontalPosition === 'end' && isRtl;\n const isRight = !isLeft && config.horizontalPosition !== 'center';\n if (isLeft) {\n positionStrategy.left('0');\n } else if (isRight) {\n positionStrategy.right('0');\n } else {\n positionStrategy.centerHorizontally();\n }\n // Set horizontal position.\n if (config.verticalPosition === 'top') {\n positionStrategy.top('0');\n } else {\n positionStrategy.bottom('0');\n }\n overlayConfig.positionStrategy = positionStrategy;\n return this._overlay.create(overlayConfig);\n }\n /**\n * Creates an injector to be used inside of a snack bar component.\n * @param config Config that was used to create the snack bar.\n * @param snackBarRef Reference to the snack bar.\n */\n _createInjector(config, snackBarRef) {\n const userInjector = config && config.viewContainerRef && config.viewContainerRef.injector;\n return Injector.create({\n parent: userInjector || this._injector,\n providers: [{\n provide: MatSnackBarRef,\n useValue: snackBarRef\n }, {\n provide: MAT_SNACK_BAR_DATA,\n useValue: config.data\n }]\n });\n }\n static ɵfac = function MatSnackBar_Factory(__ngFactoryType__) {\n return new (__ngFactoryType__ || MatSnackBar)();\n };\n static ɵprov = /* @__PURE__ */i0.ɵɵdefineInjectable({\n token: MatSnackBar,\n factory: MatSnackBar.ɵfac,\n providedIn: 'root'\n });\n }\n return MatSnackBar;\n})();\n/*#__PURE__*/(() => {\n (typeof ngDevMode === \"undefined\" || ngDevMode) && void 0;\n})();\nconst DIRECTIVES = [MatSnackBarContainer, MatSnackBarLabel, MatSnackBarActions, MatSnackBarAction];\nlet MatSnackBarModule = /*#__PURE__*/(() => {\n class MatSnackBarModule {\n static ɵfac = function MatSnackBarModule_Factory(__ngFactoryType__) {\n return new (__ngFactoryType__ || MatSnackBarModule)();\n };\n static ɵmod = /* @__PURE__ */i0.ɵɵdefineNgModule({\n type: MatSnackBarModule\n });\n static ɵinj = /* @__PURE__ */i0.ɵɵdefineInjector({\n providers: [MatSnackBar],\n imports: [OverlayModule, PortalModule, MatButtonModule, MatCommonModule, SimpleSnackBar, MatCommonModule]\n });\n }\n return MatSnackBarModule;\n})();\n/*#__PURE__*/(() => {\n (typeof ngDevMode === \"undefined\" || ngDevMode) && void 0;\n})();\n\n/**\n * Animations used by the Material snack bar.\n * @docs-private\n * @deprecated No longer used, will be removed.\n * @breaking-change 21.0.0\n */\nconst matSnackBarAnimations = {\n // Represents\n // trigger('state', [\n // state(\n // 'void, hidden',\n // style({\n // transform: 'scale(0.8)',\n // opacity: 0,\n // }),\n // ),\n // state(\n // 'visible',\n // style({\n // transform: 'scale(1)',\n // opacity: 1,\n // }),\n // ),\n // transition('* => visible', animate('150ms cubic-bezier(0, 0, 0.2, 1)')),\n // transition(\n // '* => void, * => hidden',\n // animate(\n // '75ms cubic-bezier(0.4, 0.0, 1, 1)',\n // style({\n // opacity: 0,\n // }),\n // ),\n // ),\n // ])\n /** Animation that shows and hides a snack bar. */\n snackBarState: {\n type: 7,\n name: 'state',\n 'definitions': [{\n type: 0,\n name: 'void, hidden',\n styles: {\n type: 6,\n styles: {\n transform: 'scale(0.8)',\n opacity: 0\n },\n offset: null\n }\n }, {\n type: 0,\n name: 'visible',\n styles: {\n type: 6,\n styles: {\n transform: 'scale(1)',\n opacity: 1\n },\n offset: null\n }\n }, {\n type: 1,\n expr: '* => visible',\n animation: {\n type: 4,\n styles: null,\n timings: '150ms cubic-bezier(0, 0, 0.2, 1)'\n },\n options: null\n }, {\n type: 1,\n expr: '* => void, * => hidden',\n animation: {\n type: 4,\n styles: {\n type: 6,\n styles: {\n opacity: 0\n },\n offset: null\n },\n timings: '75ms cubic-bezier(0.4, 0.0, 1, 1)'\n },\n options: null\n }],\n options: {}\n }\n};\n\n/**\n * Generated bundle index. Do not edit.\n */\n\nexport { MAT_SNACK_BAR_DATA, MAT_SNACK_BAR_DEFAULT_OPTIONS, MAT_SNACK_BAR_DEFAULT_OPTIONS_FACTORY, MatSnackBar, MatSnackBarAction, MatSnackBarActions, MatSnackBarConfig, MatSnackBarContainer, MatSnackBarLabel, MatSnackBarModule, MatSnackBarRef, SimpleSnackBar, matSnackBarAnimations };\n","import { NgClass, NgIf } from '@angular/common';\nimport { ChangeDetectionStrategy, Component, HostBinding, Inject } from '@angular/core';\nimport { MAT_SNACK_BAR_DATA } from '@angular/material/snack-bar';\n\nimport { ToasterModel } from './toaster.service';\n\n@Component({\n selector: 'ms-toaster',\n template: `\n \n \n \n `,\n imports: [NgIf, NgClass],\n changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class ToasterComponent {\n @HostBinding('class') className = 'toaster';\n\n constructor(@Inject(MAT_SNACK_BAR_DATA) public toast: ToasterModel) {}\n}\n","import { Injectable } from '@angular/core';\nimport { MatSnackBar, MatSnackBarDismiss } from '@angular/material/snack-bar';\n\nimport { EpcotConfig } from '@frontend/sports/common/client-config-data-access';\nimport { Observable } from 'rxjs';\n\nimport { ToasterComponent } from './toaster.component';\n\nexport interface ToasterConfig {\n icon?: string;\n duration?: number;\n panelClass?: string;\n}\n\nexport interface ToasterModel {\n icon?: string;\n message: string;\n dismiss: () => void;\n}\n\nexport interface Toaster {\n dismissed: Observable;\n opened: Observable;\n dismiss(): void;\n}\n\n@Injectable({ providedIn: 'root' })\nexport class ToasterService {\n private isEpcotEnabled: boolean;\n constructor(\n private snackBar: MatSnackBar,\n private epcotConfigService: EpcotConfig,\n ) {\n this.isEpcotEnabled = this.epcotConfigService.isEnabled;\n }\n\n open(message: string, { icon, duration, panelClass }: ToasterConfig = {}): Toaster {\n const toasterPanelClass = this.isEpcotEnabled ? 'toaster-panel' : 'row-toaster-panel';\n const panelClasses = [toasterPanelClass];\n if (panelClass) {\n panelClasses.push(...panelClass.split(' '));\n }\n const toaster = this.snackBar.openFromComponent(ToasterComponent, {\n data: {\n icon,\n message,\n dismiss: () => {\n toaster.dismiss();\n },\n },\n horizontalPosition: 'left',\n verticalPosition: 'bottom',\n panelClass: panelClasses,\n duration,\n });\n\n return {\n dismiss: () => {\n toaster.dismiss();\n },\n dismissed: toaster.afterDismissed(),\n opened: toaster.afterOpened(),\n };\n }\n\n dismiss(): void {\n this.snackBar.dismiss();\n }\n}\n","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, map, of } from 'rxjs';\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, catchError, finalize, map, of, shareReplay, switchMap, throwError } from 'rxjs';\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 favDraft.forEach((f) => {\n const tags = result[f.id];\n\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 { 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, catchError, filter, map, of } from 'rxjs';\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})\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})\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 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","@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.component.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\">\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.component.html',\n changeDetection: ChangeDetectionStrategy.OnPush,\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, concatMap, map } from 'rxjs';\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.component.html',\n changeDetection: ChangeDetectionStrategy.OnPush,\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 {\n badgeText: this.sitecore.crmTokens.messages.Rewards,\n cssClass: 'reward-token-eligible-tag--rewards',\n };\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 && sport) {\n \n }\n\n
\n

{{ title }}

\n @if (sport && league) {\n \n }\n
\n \n
\n}\n\n\n@if (tabs.length) {\n \n} @else if (pills.length) {\n \n}\n\n @if (sport && league && leagueIds) {\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 { IntervalFilter } from '../../calendar/count-interval.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 { CompetitionRoute } from '../../navigation/navigation.models';\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.component.html',\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 tabs: TabItemUiViewModel[] = [];\n pills: WidgetTabBarItem[] = [];\n\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 leagueIds?: number[];\n regionType: ItemType.Region | ItemType.Tournament;\n variant: ThemeNotificationVariant = {\n 'mgm-4': 'utility',\n };\n\n private 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.leagueIds = undefined;\n this.title = undefined;\n\n const route = this.route.params();\n const interval = this.calendar.getInterval(route.context);\n\n if (this.isInvalidContext(route, interval) || !changes.sport?.currentValue || !this.sport) {\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 if (!route.league) {\n this.handleNoLeagueRoute();\n } else {\n this.handleLeagueRoute(route);\n }\n }\n this.updateNavigationTabs();\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 private isInvalidContext(route: CompetitionRoute, interval: IntervalFilter | undefined): boolean {\n return (\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 );\n }\n\n private handleNoLeagueRoute(): void {\n if (!this.region) {\n return;\n }\n this.title = `${this.sitecore.globalMessages.All} ${this.region.name}`;\n }\n\n private handleLeagueRoute(route: CompetitionRoute): void {\n if (!this.sport) {\n return;\n }\n\n const league = children(this.sport, ItemType.Competition).pop() as LeagueItem;\n\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 this.leagueIds = [this.league.id];\n\n if (this.league.parentLeagueId) {\n this.leagueIds.push(this.league.parentLeagueId);\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 private updateNavigationTabs(): void {\n if (this.showPrimaryNavAsPill) {\n this.pills =\n 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 } else {\n this.tabs =\n 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 }\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, filter, map, merge, of, switchMap } from 'rxjs';\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 { 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, map, of, switchMap } from 'rxjs';\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([\n of(sport),\n this.bettingCache.getConference(conferenceId, competitionId, {\n from: start,\n to: end,\n }),\n ]),\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, first, map, mergeMap, of } from 'rxjs';\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, catchError, forkJoin, map, of } from 'rxjs';\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 { 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","/* 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 { 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 { Pipe, PipeTransform, inject } from '@angular/core';\n\nimport { CurrencyPipeInput } from './model';\nimport { MoneyService } from './money.service';\n\n@Pipe({\n name: 'msCurrency',\n standalone: true,\n})\nexport class CurrencyPipe implements PipeTransform {\n private readonly moneyService = inject(MoneyService);\n\n transform(\n input: CurrencyPipeInput,\n symbol: boolean = false,\n fractionSize: number = 2,\n hideEmptyFraction: boolean = false,\n locale?: string,\n currencyCode?: string,\n formatCurrency: boolean = true,\n ): string {\n return this.moneyService.transform(input, symbol, fractionSize, hideEmptyFraction, locale, currencyCode, formatCurrency);\n }\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 { 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';\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, map, of } from 'rxjs';\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, tap } from 'rxjs';\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\n create(events: GridSourceEvent[], options?: EventOption): Observable;\n}\n\n@Injectable({ providedIn: 'root' })\nexport class EventFactory {\n private factories: BaseFactory[];\n\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 { 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;\n virtualCompetitionId?: number;\n virtualCompetitionGroupId?: number;\n imageProfile?: ImageProfile;\n}\n\nexport interface StandingsData {\n groups: LeagueGroup[];\n virtualCompetitionId?: number;\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","import { EventDetailsColumnType } from '@frontend/sports/betting-offer/feature/model';\nimport { EventFactory, EventOption, GridSourceEvent } from '@frontend/sports/grid/core/feature/factories';\nimport { 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 { Store } from '@ngrx/store';\nimport { isArray, isEqual, keys } from 'lodash-es';\nimport { Observable, ReplaySubject, Subscription, combineLatest, distinctUntilChanged, filter, map, startWith, switchMap, tap } from 'rxjs';\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\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 combineLatest([\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 ),\n //usually it's not advised to combine selectors in this way, made an exception here to avoid coupling the two states together\n store.select(mediaStateSelector),\n ]).pipe(\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 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 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 { 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 const { groups, sixPackGroups } = config || {\n groups: new Array(),\n sixPackGroups: new Array(),\n };\n\n if (layout === GridLayout.SixPack) {\n source = this.applyGroupOrder(sixPackGroups, options);\n } else {\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","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 {\n ColumnActiveState,\n GridStoreService,\n GridStoreState,\n IGridGroup,\n addEvents,\n setActiveGroupState,\n setGroupCollapseThreshold,\n setStateCollapse,\n} from '@frontend/sports/grid/core/feature/store';\nimport { Store } from '@ngrx/store';\nimport { isNumber, mapValues } from 'lodash-es';\nimport { Observable, forkJoin, map, of } from 'rxjs';\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\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 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 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, NgZone, inject } from '@angular/core';\nimport { ActivationStart, Router } from '@angular/router';\n\nimport { PerformanceProfilingConfig } from '@frontend/sports/common/client-config-data-access';\nimport { filterSports } from '@frontend/sports/host-app/sports-product/feature/utils';\nimport { OnAppInit } from '@frontend/vanilla/core';\nimport { filter } from 'rxjs';\n\n@Injectable({\n providedIn: 'root',\n})\nexport class PerformanceProfilingService implements OnAppInit {\n private readonly markNameTTI = 'vn_tti';\n\n private lastTrackedUrl = '';\n private zone = inject(NgZone);\n\n constructor(\n private router: Router,\n private performanceConfig: PerformanceProfilingConfig,\n ) {}\n\n onAppInit() {\n this.zone.runOutsideAngular(() =>\n setTimeout(() => {\n this.register();\n }),\n );\n }\n\n register(): void {\n try {\n const profilingEnabled = this.performanceConfig.isPerformanceProfilingEnabled;\n const browserSupport = performance && performance.measure;\n\n if (profilingEnabled && !!browserSupport) {\n this.router.events\n .pipe(\n filter((event) => event instanceof ActivationStart),\n filterSports(),\n )\n .subscribe(() => this.navigationStartCallback());\n }\n // eslint-disable-next-line no-empty\n } catch {}\n }\n\n markTTI(): void {\n if (!this.performanceConfig.isPerformanceProfilingEnabled) {\n return;\n }\n\n this.markEnd(this.markNameTTI);\n }\n\n private markEnd(markName: string): void {\n try {\n // markNameTTI is tracked from the grid. if there are more grids on the page we want to only track once'\n if (markName === this.markNameTTI && this.lastTrackedUrl !== this.router.url) {\n performance.mark(`${markName}-end`);\n const navigationStartMark = performance.getEntriesByName(`${markName}-start`)[0];\n if (navigationStartMark) {\n performance.measure(markName, `${markName}-start`, `${markName}-end`);\n }\n if (markName === this.markNameTTI) {\n this.lastTrackedUrl = this.router.url;\n }\n }\n // eslint-disable-next-line no-empty\n } catch {}\n }\n\n private navigationStartCallback = () => {\n this.markStart();\n };\n\n private markStart(): void {\n try {\n performance.clearMarks(`${this.markNameTTI}-start`);\n performance.clearMarks(`${this.markNameTTI}-end`);\n performance.clearMeasures(this.markNameTTI);\n\n performance.mark(`${this.markNameTTI}-start`);\n // eslint-disable-next-line no-empty\n } catch {}\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, catchError, first, isObservable } from 'rxjs';\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;\n\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\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 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 { 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 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, concatMap, distinctUntilChanged, first, from, map, skip, startWith, switchMap, tap } from 'rxjs';\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 private 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, filter, merge, throttleTime } from 'rxjs';\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 { 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\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 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 return source.lift(new BufferWithSizeOperator(bufferSize, closingNotifier));\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 { 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, debounceTime, distinct, filter, map, noop, throttleTime } from 'rxjs';\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, filter } from 'rxjs';\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/ban-ts-comment */\nimport { StringDictionary } from '@frontend/sports/common/core/utils/extended-types';\n\nexport const isShallowCopy = (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\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 { 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, map, of } from 'rxjs';\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\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\n .get('/competitions/participants/v2', {\n competitionId: id,\n sportId,\n })\n .pipe(\n map((response) => {\n if (!response) {\n return [];\n }\n\n return response\n .filter(\n (item) =>\n item &&\n item.participantId &&\n (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, map, of, switchMap } from 'rxjs';\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 { 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, first, map, of, switchMap } from 'rxjs';\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\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 ? {\n jersey: this.getImage(paarticipantId, this.v2Prefix, this.homeTeamSuffix),\n isParticipantProfile: false,\n }\n : {\n jersey: this.getImage(paarticipantId, this.v1Prefix, this.homeTeamSuffix),\n isParticipantProfile: false,\n };\n }\n } else {\n return {\n jersey: imageProfile?.jersey,\n logo: imageProfile?.logo,\n isParticipantProfile: imageProfile?.isParticipantProfile ?? true,\n };\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: {\n sport: number;\n sportName: string;\n competition: string;\n competitionName: string;\n team: number;\n teamName: string;\n data?: Data;\n },\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, 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\n return {\n teamPagesNavigationViewModel,\n teamPagesDetailsModel,\n };\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 routeParams: {\n sport: number;\n sportName: string;\n competition: string;\n competitionName: string;\n team: number;\n teamName: string;\n data?: Data;\n },\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 { 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, debounceTime, filter, iif, map, of, take, timeout } from 'rxjs';\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 standalone: false,\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","\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, map, of, switchMap, tap } from 'rxjs';\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.component.html',\n changeDetection: ChangeDetectionStrategy.OnPush,\n standalone: false,\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","\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, concatMap, of, switchMap, tap, withLatestFrom } from 'rxjs';\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 { 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 { TeamPagesCompetitionViewBinding, TeamPagesComponentLoaderService } from '../../teampages/team-pages-component-loader.service';\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.component.html',\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\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, map } from 'rxjs';\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\n .getSportList({\n from: start,\n to: end,\n })\n .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 { Injectable } from '@angular/core';\n\nimport { ApiService } from '@frontend/sports/common/api-utils';\nimport { MyBetsConfig } from '@frontend/sports/common/client-config-data-access';\nimport { LoggerFactory, SportsRemoteLogger } from '@frontend/sports/common/core/feature/logging';\nimport { ExpiringCache } from '@frontend/sports/common/core/utils/expiring-cache';\nimport { OpenBetsSummary } from '@frontend/sports/types/models/my-bets';\nimport { catchError, firstValueFrom, of } from 'rxjs';\n\n@Injectable({ providedIn: 'root' })\nexport class SummaryApiService {\n private readonly logger: SportsRemoteLogger;\n private mybetsStatsCache: ExpiringCache;\n\n constructor(\n private apiService: ApiService,\n loggerFactory: LoggerFactory,\n private myBetsConfig: MyBetsConfig,\n ) {\n this.logger = loggerFactory.getLogger('MyBetsApiService');\n\n const timeout = (this.myBetsConfig.cacheTimeout || 100) / 1000;\n this.mybetsStatsCache = new ExpiringCache(timeout);\n }\n\n async getOpenBetsCount(): Promise {\n const source = this.apiService.get('mybets/stats').pipe(\n catchError((error) => {\n this.logger.error(error, 'Get OpenBetsCount response failed.');\n\n return of({\n openEvents: {},\n openBetsCount: 0,\n liveBetsCount: 0,\n betNumbers: [],\n isMigrationSummary: false,\n hasError: false,\n });\n }),\n );\n\n return firstValueFrom(this.mybetsStatsCache.getOrCreate(`mybets-stats`, source));\n }\n}\n","import { Injectable } from '@angular/core';\n\nimport { EventModel } from '@frontend/sports/betting-offer/feature/model';\nimport { MyBetsConfig, MyBetsVisualizationTrackingConfig } from '@frontend/sports/common/client-config-data-access';\nimport { DispatcherService } from '@frontend/sports/common/core/utils/dispatcher';\nimport { isDefined } from '@frontend/sports/common/core/utils/extended-types';\nimport { filterSportsEmitLast, fromSportsEnter, fromSportsLeave } from '@frontend/sports/host-app/sports-product/feature/utils';\nimport { OpenBetsSummary } from '@frontend/sports/types/models/my-bets';\nimport { UserService } from '@frontend/sports/user/feature';\nimport { sum } from 'lodash-es';\nimport { BehaviorSubject, Subject, filter, firstValueFrom, takeUntil, timer } from 'rxjs';\n\nimport { SummaryApiService } from './summary-api.service';\n\n@Injectable({\n providedIn: 'root',\n})\nexport class MyBetsSummaryService {\n private readonly defaultSummary: OpenBetsSummary = {\n openEvents: {},\n liveBetsCount: 0,\n openBetsCount: 0,\n betNumbers: [],\n isMigrationSummary: false,\n hasError: false,\n };\n\n private readonly EARLY_PAYOUT_SUCCESS = 'EARLY_PAYOUT_SUCCESS';\n private readonly BET_PLACE_SUCCESS = 'BET_PLACE_SUCCESS';\n private readonly CASH_OUT_ALREADY_EXECUTED = 'CASH_OUT_ALREADY_EXECUTED';\n\n private _summaryUpdate = new BehaviorSubject | undefined>(undefined);\n readonly summaryUpdate = this._summaryUpdate.pipe(filter(isDefined));\n\n private stopped$: Subject;\n\n constructor(\n private loader: SummaryApiService,\n private dispatcherService: DispatcherService,\n private userService: UserService,\n private mybetsConfig: MyBetsConfig,\n private mybetsVisualizationTrackingConfig: MyBetsVisualizationTrackingConfig,\n ) {\n this.stopped$ = new Subject();\n\n if (this.userService.isAuthenticated) {\n this.start();\n }\n\n this.userService.onUserLogInWithSettings$.pipe(filterSportsEmitLast()).subscribe(() => this.start());\n this.userService.onUserLogout$.pipe(filterSportsEmitLast()).subscribe(() => this.stop());\n fromSportsEnter().subscribe(() => this.userService.isAuthenticated && this.start());\n fromSportsLeave().subscribe(() => () => this.stop());\n }\n\n get summary(): Readonly {\n return this._summaryUpdate.value || this.defaultSummary;\n }\n\n update(summary: OpenBetsSummary): void {\n this._summaryUpdate.next(summary);\n }\n\n getOpenBetsCount(evt: EventModel): number {\n const openEvents = this.summary.openEvents;\n if (Object.keys(openEvents).length !== 0 && this.mybetsVisualizationTrackingConfig?.isEnabled) {\n return openEvents[evt.id]?.openBetsCount;\n }\n const eventIds = [evt.id, ...(evt.splitFixtureIds || []), ...(evt.hybridFixtureData?.fixtureId ? [evt.hybridFixtureData.fixtureId] : [])];\n const eventCounts = eventIds.filter((eid) => openEvents[eid]).map((eid) => openEvents[eid].openBetsCount);\n\n return sum(eventCounts);\n }\n\n private start(): void {\n this.stop();\n this.subscribeBetPlacementActions();\n firstValueFrom(this.mybetsVisualizationTrackingConfig.whenReady);\n if (!this.mybetsConfig.isOpenSummaryLoadingRestricted) {\n timer(0, this.mybetsConfig.myBetsRefreshInMilliseconds || 120000)\n .pipe(takeUntil(this.stopped$))\n .subscribe(() => this.refresh());\n }\n }\n\n async refresh(aggresive: boolean = false): Promise {\n if (aggresive || !this._summaryUpdate.value || this.summary.openBetsCount > 0) {\n let summary: OpenBetsSummary;\n try {\n summary = await this.loader.getOpenBetsCount();\n } catch {\n summary = { ...this.defaultSummary };\n }\n\n this.update(summary);\n }\n }\n\n private stop(): void {\n this.stopped$.next();\n }\n\n private subscribeBetPlacementActions(): void {\n this.dispatcherService.on(this.EARLY_PAYOUT_SUCCESS).pipe(takeUntil(this.stopped$)).subscribe(this.onBetPlacementAction);\n\n this.dispatcherService.on(this.BET_PLACE_SUCCESS).pipe(takeUntil(this.stopped$)).subscribe(this.onBetPlacementAction);\n\n this.dispatcherService.on(this.CASH_OUT_ALREADY_EXECUTED).pipe(takeUntil(this.stopped$)).subscribe(this.onBetPlacementAction);\n }\n\n private onBetPlacementAction = () => {\n this.refresh(true);\n };\n}\n","import { Location } from '@angular/common';\nimport { Injectable } from '@angular/core';\n\nimport { AccaBoostConfig, LayoutNavigationConfig, MyBetsConfig } from '@frontend/sports/common/client-config-data-access';\nimport { filterSportsEmitLast } from '@frontend/sports/host-app/sports-product/feature/utils';\nimport { DialogAnimation, ModalDialogOptions } from '@frontend/sports/modal/feature';\nimport { TrackingService, trackingConstants } from '@frontend/sports/tracking/feature';\nimport { OpenBetsSummary } from '@frontend/sports/types/models/my-bets';\nimport { UserService } from '@frontend/sports/user/feature';\nimport { BottomNavService, MenuAction, MenuActionsService, NavigationService } from '@frontend/vanilla/core';\nimport { BetslipIntegrationService } from 'packages/sports/common/betslip/integration/betslip-integration.service';\nimport { combineLatest, switchMap } from 'rxjs';\n\nimport { EpcotConfigService } from '../common/epcot-config.service';\nimport { MyBetsSummaryService } from '../my-bets-base/my-bets-summary.service';\nimport { RedirectHelperService } from '../navigation-core/redirect-helper.service';\nimport { ModelPopupTypes, UrlHelperService } from '../navigation-core/url-helper.service';\nimport { PopupManager } from '../popup/popup-manager.service';\n\n@Injectable({\n providedIn: 'root',\n})\nexport class BottomNavActionsService {\n private static readonly betslipBadgeClass: string = 'badge-primary';\n private static readonly extraIconBadgeClass: string = 'badge-extra-bubble-icon';\n private static readonly myBetsBadgeClass: string = 'badge-primary theme-spot-filled';\n\n private openBetsCount: number;\n private hasLiveOpenBets = false;\n\n constructor(\n private vanillaNavigation: NavigationService,\n private popupManager: PopupManager,\n private trackingService: TrackingService,\n private redirectHelper: RedirectHelperService,\n private location: Location,\n private myBetsSummary: MyBetsSummaryService,\n private userService: UserService,\n private urlHelper: UrlHelperService,\n private betslipIntegrationService: BetslipIntegrationService,\n private bottomNavService: BottomNavService,\n private menuActionsService: MenuActionsService,\n private accaBoostConfig: AccaBoostConfig,\n private layoutNavigationConfig: LayoutNavigationConfig,\n private epcotConfigService: EpcotConfigService,\n private mybetsConfig: MyBetsConfig,\n ) {}\n\n betslipToggled(): void {\n if (this.popupManager.isOpen()) {\n this.trackingService.track(trackingConstants.EVENT_PAGE_VIEW, { [trackingConstants.PAGE_NAME]: 'M2_slip_action_ClosePopup' });\n }\n this.popupManager.open('betslip', undefined);\n }\n\n myBetsToggled(): void {\n const myBetsUrl = this.urlHelper.getMyBetsUrl();\n if (this.location.path() === myBetsUrl) {\n this.redirectHelper.goBack();\n this.myBetsTracking(false);\n } else {\n if (this.userService.isAuthenticated) {\n if (this.epcotConfigService.isShowMyBetsPopup) {\n this.popupManager.updateDialogAnimation(ModelPopupTypes.MyBets, DialogAnimation.SlideInFromBottom);\n const options: ModalDialogOptions = {\n settings: {\n closeAnimation: DialogAnimation.SlideOutFromTop,\n },\n };\n this.popupManager.open(ModelPopupTypes.MyBets, undefined, options);\n } else {\n this.vanillaNavigation.goTo(myBetsUrl);\n }\n } else {\n const returnUrl = this.epcotConfigService.isShowMyBetsPopup ? this.urlHelper.getMyBetPopupUrl() : myBetsUrl;\n this.menuActionsService.invoke(MenuAction.GOTO_LOGIN, '', [undefined, undefined, { returnUrl }]);\n }\n this.myBetsTracking(true);\n }\n }\n\n private myBetsTracking(isOpen: boolean): void {\n if (!isOpen) {\n return;\n }\n\n let pageName: string;\n if (this.hasLiveOpenBets) {\n pageName = 'HIconON_Live';\n } else if (this.openBetsCount > 0) {\n pageName = 'HIcon_Open';\n } else {\n pageName = 'HIcon_Settled';\n }\n this.trackingService.update({ [trackingConstants.PAGE_REFERRING_ACTION]: pageName });\n }\n\n init(): void {\n if (!this.mybetsConfig.isOpenSummaryLoadingRestricted) {\n this.myBetsSummary.summaryUpdate.pipe(filterSportsEmitLast()).subscribe((stats: OpenBetsSummary) => {\n this.hasLiveOpenBets = stats.liveBetsCount > 0;\n this.openBetsCount = stats.openBetsCount;\n\n let betsCount: number | string = 0;\n let badgeClass = BottomNavActionsService.myBetsBadgeClass;\n if (this.mybetsConfig.isShowOpenBetsCount) {\n if (stats.openBetsCount > 99) {\n betsCount = '99+';\n badgeClass = badgeClass.replace('theme-spot-filled', 'open-bets bets-99-plus');\n } else {\n betsCount = stats.openBetsCount;\n badgeClass = badgeClass.replace('theme-spot-filled', 'open-bets');\n }\n } else betsCount = stats.liveBetsCount;\n const myBetsItem = this.layoutNavigationConfig?.bottomNavItemsMapping['mybets'] || 'mybets';\n\n this.bottomNavService.setItemCounter(myBetsItem, betsCount || null, badgeClass);\n });\n }\n\n this.betslipIntegrationService\n .betslipInitialized$()\n .pipe(\n filterSportsEmitLast(),\n switchMap(() =>\n combineLatest([this.betslipIntegrationService.betslipPicksCount$(), this.betslipIntegrationService.isAccaBoostToken$()]),\n ),\n )\n .subscribe(([count, isAccaBoost]) => this.setBetslipCounter(count, isAccaBoost));\n }\n\n private setBetslipCounter(count: number, isAccaBoost: boolean): void {\n this.bottomNavService.setItemCounter(\n 'betslip',\n count || null,\n isAccaBoost ? this.accaBoostBadgeClass : BottomNavActionsService.betslipBadgeClass,\n );\n }\n\n menuToggled(): void {\n this.vanillaNavigation.goToOverlayOutlet('menu');\n }\n\n private get accaBoostBadgeClass(): string {\n return `${BottomNavActionsService.betslipBadgeClass} ${BottomNavActionsService.extraIconBadgeClass} ${this.accaBoostConfig.icon}`;\n }\n}\n","import { Injector, ɵisInjectable } from '@angular/core';\nimport { ActivatedRouteSnapshot, PRIMARY_OUTLET, Router, UrlSegment } from '@angular/router';\n\nimport { NavigationInfo } from '@frontend/vanilla/core';\n\nexport function getToken(token: any, snapshot: ActivatedRouteSnapshot, moduleInjector: Injector): T {\n return getTokenWithInjector(token, snapshot, moduleInjector).token;\n}\n\n/**\n * From '@angular/router/fesm2022/router.mjs\n * @param as\n * @param bs\n * @returns\n */\nfunction equalPath(as: UrlSegment[], bs: UrlSegment[]) {\n if (as.length !== bs.length) return false;\n return as.every((a, i) => a.path === bs[i].path);\n}\n\n/**\n * From '@angular/router/fesm2022/router.mjs\n */\nfunction shallowEqual(a: Record, b: Record) {\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: string | symbol;\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}\nfunction getDataKeys(obj: object) {\n return [...Object.keys(obj), ...Object.getOwnPropertySymbols(obj)];\n}\nfunction equalArraysOrString(a: unknown, b: unknown) {\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\nfunction getClosestRouteInjector(snapshot?: any) {\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\nfunction getTokenOrFunctionIdentity(tokenOrFunction: any, injector: 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}\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 || shouldSkipGuardsAndResolvers(to)) {\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\nfunction shouldSkipGuardsAndResolvers(to: ActivatedRouteSnapshot): boolean {\n const router: Router | undefined = getClosestRouteInjector(to)?.get(Router);\n const info = router?.getCurrentNavigation()?.extras.info;\n\n return hasSkipInfo(info) && info.skipPrimaryOutletGuardsAndResolvers && to.outlet === PRIMARY_OUTLET;\n}\n\nfunction hasSkipInfo(info: unknown): info is Required> {\n return Boolean(info && (info as NavigationInfo).skipPrimaryOutletGuardsAndResolvers !== undefined);\n}\n","export enum TopNavigationVersion {\n V2 = 'V2',\n V3 = 'V3',\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, 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, first, forkJoin, takeUntil } from 'rxjs';\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, distinctUntilKeyChanged, switchMap } from 'rxjs';\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 hasItemRemoved = true;\n\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, catchError, map, of, switchMap, tap } from 'rxjs';\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 numpadAction?: NumpadAction;\n}\n\nexport const defaultQuickBetStakeState: IQuickBetStakeState = {\n stake: null,\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","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\nexport enum BetslipTrackingConstants {\n CategoryEvent = 'betslip',\n LabelEvent = 'full betslip',\n SourceTabbed = 'tabbed betslip',\n SourceLinear = 'linear betslip',\n Close = 'close cta',\n Click = 'click',\n}\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 { PickBetTrackingTypes } from '../tracking/models';\nimport { betslipCurrentTypeSelector } from '../types/base/selectors';\nimport { betslipTypeStateSelector } from '../types/selectors';\nimport { GroupWithClosedLegsError } from '../validation/errors/general/group-pick-error';\nimport { selectAllBetBuilderErrors, selectAllCurrentBetslipErrors, selectPickErrorsState } from '../validation/selectors';\nimport {\n isGroupPickOfferChange,\n isPickStatusClosed,\n isPickStatusLocked,\n isPickStatusOddsChangedPreCheck,\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 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, shareReplay } from 'rxjs';\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 { 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 { entries, isEqual } from 'lodash-es';\nimport { Observable, distinctUntilChanged, map } from 'rxjs';\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 entries(this.mediaObserver.breakpoints)\n .filter((point) => point[0] 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/* eslint-disable no-prototype-builtins */\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 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\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 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 this.route.data.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(({ model }) => this.onRouteResolved(model));\n }\n\n abstract onRouteResolved(model: TModel): void;\n}\n","import { Injectable } from '@angular/core';\n\nimport { ClientConfigProductName, LazyClientConfig, LazyClientConfigBase, LazyClientConfigService } from '@frontend/vanilla/core';\nimport { LDOptions } from 'launchdarkly-js-client-sdk';\n\n/**\n * @stable\n */\n@LazyClientConfig({ key: 'vnLaunchDarkly', product: ClientConfigProductName.SF })\n@Injectable({\n providedIn: 'root',\n useFactory: launchDarklyConfigFactory,\n deps: [LazyClientConfigService],\n})\nexport class LaunchDarklyConfig extends LazyClientConfigBase {\n clientId: string;\n options: LDOptions;\n sessionCookieName: string;\n}\n\nexport function launchDarklyConfigFactory(service: LazyClientConfigService) {\n return service.get(LaunchDarklyConfig);\n}\n","function e(e) {\n function t(e, t) {\n Error.captureStackTrace && Error.captureStackTrace(this, this.constructor), this.message = e, this.code = t;\n }\n return t.prototype = new Error(), t.prototype.name = e, t.prototype.constructor = t, t;\n}\nconst t = e(\"LaunchDarklyUnexpectedResponseError\"),\n n = e(\"LaunchDarklyInvalidEnvironmentIdError\"),\n r = e(\"LaunchDarklyInvalidUserError\"),\n o = e(\"LaunchDarklyInvalidEventKeyError\"),\n i = e(\"LaunchDarklyInvalidArgumentError\"),\n a = e(\"LaunchDarklyFlagFetchError\");\nfor (var s = {\n LDUnexpectedResponseError: t,\n LDInvalidEnvironmentIdError: n,\n LDInvalidUserError: r,\n LDInvalidEventKeyError: o,\n LDInvalidArgumentError: i,\n LDInvalidDataError: e(\"LaunchDarklyInvalidDataError\"),\n LDFlagFetchError: a,\n LDTimeoutError: e(\"LaunchDarklyTimeoutError\"),\n isHttpErrorRecoverable: function (e) {\n return !(e >= 400 && e < 500) || 400 === e || 408 === e || 429 === e;\n }\n }, c = function (e) {\n var t = m(e),\n n = t[0],\n r = t[1];\n return 3 * (n + r) / 4 - r;\n }, u = function (e) {\n var t,\n n,\n r = m(e),\n o = r[0],\n i = r[1],\n a = new g(function (e, t, n) {\n return 3 * (t + n) / 4 - n;\n }(0, o, i)),\n s = 0,\n c = i > 0 ? o - 4 : o;\n for (n = 0; n < c; n += 4) t = f[e.charCodeAt(n)] << 18 | f[e.charCodeAt(n + 1)] << 12 | f[e.charCodeAt(n + 2)] << 6 | f[e.charCodeAt(n + 3)], a[s++] = t >> 16 & 255, a[s++] = t >> 8 & 255, a[s++] = 255 & t;\n 2 === i && (t = f[e.charCodeAt(n)] << 2 | f[e.charCodeAt(n + 1)] >> 4, a[s++] = 255 & t);\n 1 === i && (t = f[e.charCodeAt(n)] << 10 | f[e.charCodeAt(n + 1)] << 4 | f[e.charCodeAt(n + 2)] >> 2, a[s++] = t >> 8 & 255, a[s++] = 255 & t);\n return a;\n }, l = function (e) {\n for (var t, n = e.length, r = n % 3, o = [], i = 16383, a = 0, s = n - r; a < s; a += i) o.push(h(e, a, a + i > s ? s : a + i));\n 1 === r ? (t = e[n - 1], o.push(d[t >> 2] + d[t << 4 & 63] + \"==\")) : 2 === r && (t = (e[n - 2] << 8) + e[n - 1], o.push(d[t >> 10] + d[t >> 4 & 63] + d[t << 2 & 63] + \"=\"));\n return o.join(\"\");\n }, d = [], f = [], g = \"undefined\" != typeof Uint8Array ? Uint8Array : Array, v = \"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/\", p = 0; p < 64; ++p) d[p] = v[p], f[v.charCodeAt(p)] = p;\nfunction m(e) {\n var t = e.length;\n if (t % 4 > 0) throw new Error(\"Invalid string. Length must be a multiple of 4\");\n var n = e.indexOf(\"=\");\n return -1 === n && (n = t), [n, n === t ? 0 : 4 - n % 4];\n}\nfunction h(e, t, n) {\n for (var r, o, i = [], a = t; a < n; a += 3) r = (e[a] << 16 & 16711680) + (e[a + 1] << 8 & 65280) + (255 & e[a + 2]), i.push(d[(o = r) >> 18 & 63] + d[o >> 12 & 63] + d[o >> 6 & 63] + d[63 & o]);\n return i.join(\"\");\n}\nf[\"-\".charCodeAt(0)] = 62, f[\"_\".charCodeAt(0)] = 63;\nvar y = {\n byteLength: c,\n toByteArray: u,\n fromByteArray: l\n },\n w = Array.isArray,\n b = Object.keys,\n k = Object.prototype.hasOwnProperty,\n E = function e(t, n) {\n if (t === n) return !0;\n if (t && n && \"object\" == typeof t && \"object\" == typeof n) {\n var r,\n o,\n i,\n a = w(t),\n s = w(n);\n if (a && s) {\n if ((o = t.length) != n.length) return !1;\n for (r = o; 0 != r--;) if (!e(t[r], n[r])) return !1;\n return !0;\n }\n if (a != s) return !1;\n var c = t instanceof Date,\n u = n instanceof Date;\n if (c != u) return !1;\n if (c && u) return t.getTime() == n.getTime();\n var l = t instanceof RegExp,\n d = n instanceof RegExp;\n if (l != d) return !1;\n if (l && d) return t.toString() == n.toString();\n var f = b(t);\n if ((o = f.length) !== b(n).length) return !1;\n for (r = o; 0 != r--;) if (!k.call(n, f[r])) return !1;\n for (r = o; 0 != r--;) if (!e(t[i = f[r]], n[i])) return !1;\n return !0;\n }\n return t != t && n != n;\n };\nconst D = [\"key\", \"ip\", \"country\", \"email\", \"firstName\", \"lastName\", \"avatar\", \"name\"];\nfunction x(e) {\n const t = unescape(encodeURIComponent(e));\n return y.fromByteArray(function (e) {\n const t = [];\n for (let n = 0; n < e.length; n++) t.push(e.charCodeAt(n));\n return t;\n }(t));\n}\nfunction C(e, t) {\n return Object.prototype.hasOwnProperty.call(e, t);\n}\nvar P,\n S = {\n appendUrlPath: function (e, t) {\n return (e.endsWith(\"/\") ? e.substring(0, e.length - 1) : e) + (t.startsWith(\"/\") ? \"\" : \"/\") + t;\n },\n base64URLEncode: function (e) {\n return x(e).replace(/=/g, \"\").replace(/\\+/g, \"-\").replace(/\\//g, \"_\");\n },\n btoa: x,\n clone: function (e) {\n return JSON.parse(JSON.stringify(e));\n },\n deepEquals: function (e, t) {\n return E(e, t);\n },\n extend: function (...e) {\n return e.reduce((e, t) => ({\n ...e,\n ...t\n }), {});\n },\n getLDUserAgentString: function (e) {\n const t = e.version || \"?\";\n return e.userAgent + \"/\" + t;\n },\n objectHasOwnProperty: C,\n onNextTick: function (e) {\n setTimeout(e, 0);\n },\n sanitizeContext: function (e) {\n if (!e) return e;\n let t;\n return null !== e.kind && void 0 !== e.kind || D.forEach(n => {\n const r = e[n];\n void 0 !== r && \"string\" != typeof r && (t = t || {\n ...e\n }, t[n] = String(r));\n }), t || e;\n },\n transformValuesToVersionedValues: function (e) {\n const t = {};\n for (const n in e) C(e, n) && (t[n] = {\n value: e[n],\n version: 0\n });\n return t;\n },\n transformVersionedValuesToValues: function (e) {\n const t = {};\n for (const n in e) C(e, n) && (t[n] = e[n].value);\n return t;\n },\n wrapPromiseCallback: function (e, t) {\n const n = e.then(e => (t && setTimeout(() => {\n t(null, e);\n }, 0), e), e => {\n if (!t) return Promise.reject(e);\n setTimeout(() => {\n t(e, null);\n }, 0);\n });\n return t ? void 0 : n;\n }\n },\n I = new Uint8Array(16);\nfunction O() {\n if (!P && !(P = \"undefined\" != typeof crypto && crypto.getRandomValues && crypto.getRandomValues.bind(crypto) || \"undefined\" != typeof msCrypto && \"function\" == typeof msCrypto.getRandomValues && msCrypto.getRandomValues.bind(msCrypto))) throw new Error(\"crypto.getRandomValues() not supported. See https://github.com/uuidjs/uuid#getrandomvalues-not-supported\");\n return P(I);\n}\nvar T = /^(?:[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}|00000000-0000-0000-0000-000000000000)$/i;\nfunction L(e) {\n return \"string\" == typeof e && T.test(e);\n}\nfor (var U, R, A = [], j = 0; j < 256; ++j) A.push((j + 256).toString(16).substr(1));\nfunction F(e) {\n var t = arguments.length > 1 && void 0 !== arguments[1] ? arguments[1] : 0,\n n = (A[e[t + 0]] + A[e[t + 1]] + A[e[t + 2]] + A[e[t + 3]] + \"-\" + A[e[t + 4]] + A[e[t + 5]] + \"-\" + A[e[t + 6]] + A[e[t + 7]] + \"-\" + A[e[t + 8]] + A[e[t + 9]] + \"-\" + A[e[t + 10]] + A[e[t + 11]] + A[e[t + 12]] + A[e[t + 13]] + A[e[t + 14]] + A[e[t + 15]]).toLowerCase();\n if (!L(n)) throw TypeError(\"Stringified UUID is invalid\");\n return n;\n}\nvar N = 0,\n $ = 0;\nfunction V(e) {\n if (!L(e)) throw TypeError(\"Invalid UUID\");\n var t,\n n = new Uint8Array(16);\n return n[0] = (t = parseInt(e.slice(0, 8), 16)) >>> 24, n[1] = t >>> 16 & 255, n[2] = t >>> 8 & 255, n[3] = 255 & t, n[4] = (t = parseInt(e.slice(9, 13), 16)) >>> 8, n[5] = 255 & t, n[6] = (t = parseInt(e.slice(14, 18), 16)) >>> 8, n[7] = 255 & t, n[8] = (t = parseInt(e.slice(19, 23), 16)) >>> 8, n[9] = 255 & t, n[10] = (t = parseInt(e.slice(24, 36), 16)) / 1099511627776 & 255, n[11] = t / 4294967296 & 255, n[12] = t >>> 24 & 255, n[13] = t >>> 16 & 255, n[14] = t >>> 8 & 255, n[15] = 255 & t, n;\n}\nfunction M(e, t, n) {\n function r(e, r, o, i) {\n if (\"string\" == typeof e && (e = function (e) {\n e = unescape(encodeURIComponent(e));\n for (var t = [], n = 0; n < e.length; ++n) t.push(e.charCodeAt(n));\n return t;\n }(e)), \"string\" == typeof r && (r = V(r)), 16 !== r.length) throw TypeError(\"Namespace must be array-like (16 iterable integer values, 0-255)\");\n var a = new Uint8Array(16 + e.length);\n if (a.set(r), a.set(e, r.length), (a = n(a))[6] = 15 & a[6] | t, a[8] = 63 & a[8] | 128, o) {\n i = i || 0;\n for (var s = 0; s < 16; ++s) o[i + s] = a[s];\n return o;\n }\n return F(a);\n }\n try {\n r.name = e;\n } catch (e) {}\n return r.DNS = \"6ba7b810-9dad-11d1-80b4-00c04fd430c8\", r.URL = \"6ba7b811-9dad-11d1-80b4-00c04fd430c8\", r;\n}\nfunction q(e) {\n return 14 + (e + 64 >>> 9 << 4) + 1;\n}\nfunction H(e, t) {\n var n = (65535 & e) + (65535 & t);\n return (e >> 16) + (t >> 16) + (n >> 16) << 16 | 65535 & n;\n}\nfunction z(e, t, n, r, o, i) {\n return H((a = H(H(t, e), H(r, i))) << (s = o) | a >>> 32 - s, n);\n var a, s;\n}\nfunction K(e, t, n, r, o, i, a) {\n return z(t & n | ~t & r, e, t, o, i, a);\n}\nfunction _(e, t, n, r, o, i, a) {\n return z(t & r | n & ~r, e, t, o, i, a);\n}\nfunction J(e, t, n, r, o, i, a) {\n return z(t ^ n ^ r, e, t, o, i, a);\n}\nfunction B(e, t, n, r, o, i, a) {\n return z(n ^ (t | ~r), e, t, o, i, a);\n}\nvar G = M(\"v3\", 48, function (e) {\n if (\"string\" == typeof e) {\n var t = unescape(encodeURIComponent(e));\n e = new Uint8Array(t.length);\n for (var n = 0; n < t.length; ++n) e[n] = t.charCodeAt(n);\n }\n return function (e) {\n for (var t = [], n = 32 * e.length, r = \"0123456789abcdef\", o = 0; o < n; o += 8) {\n var i = e[o >> 5] >>> o % 32 & 255,\n a = parseInt(r.charAt(i >>> 4 & 15) + r.charAt(15 & i), 16);\n t.push(a);\n }\n return t;\n }(function (e, t) {\n e[t >> 5] |= 128 << t % 32, e[q(t) - 1] = t;\n for (var n = 1732584193, r = -271733879, o = -1732584194, i = 271733878, a = 0; a < e.length; a += 16) {\n var s = n,\n c = r,\n u = o,\n l = i;\n n = K(n, r, o, i, e[a], 7, -680876936), i = K(i, n, r, o, e[a + 1], 12, -389564586), o = K(o, i, n, r, e[a + 2], 17, 606105819), r = K(r, o, i, n, e[a + 3], 22, -1044525330), n = K(n, r, o, i, e[a + 4], 7, -176418897), i = K(i, n, r, o, e[a + 5], 12, 1200080426), o = K(o, i, n, r, e[a + 6], 17, -1473231341), r = K(r, o, i, n, e[a + 7], 22, -45705983), n = K(n, r, o, i, e[a + 8], 7, 1770035416), i = K(i, n, r, o, e[a + 9], 12, -1958414417), o = K(o, i, n, r, e[a + 10], 17, -42063), r = K(r, o, i, n, e[a + 11], 22, -1990404162), n = K(n, r, o, i, e[a + 12], 7, 1804603682), i = K(i, n, r, o, e[a + 13], 12, -40341101), o = K(o, i, n, r, e[a + 14], 17, -1502002290), n = _(n, r = K(r, o, i, n, e[a + 15], 22, 1236535329), o, i, e[a + 1], 5, -165796510), i = _(i, n, r, o, e[a + 6], 9, -1069501632), o = _(o, i, n, r, e[a + 11], 14, 643717713), r = _(r, o, i, n, e[a], 20, -373897302), n = _(n, r, o, i, e[a + 5], 5, -701558691), i = _(i, n, r, o, e[a + 10], 9, 38016083), o = _(o, i, n, r, e[a + 15], 14, -660478335), r = _(r, o, i, n, e[a + 4], 20, -405537848), n = _(n, r, o, i, e[a + 9], 5, 568446438), i = _(i, n, r, o, e[a + 14], 9, -1019803690), o = _(o, i, n, r, e[a + 3], 14, -187363961), r = _(r, o, i, n, e[a + 8], 20, 1163531501), n = _(n, r, o, i, e[a + 13], 5, -1444681467), i = _(i, n, r, o, e[a + 2], 9, -51403784), o = _(o, i, n, r, e[a + 7], 14, 1735328473), n = J(n, r = _(r, o, i, n, e[a + 12], 20, -1926607734), o, i, e[a + 5], 4, -378558), i = J(i, n, r, o, e[a + 8], 11, -2022574463), o = J(o, i, n, r, e[a + 11], 16, 1839030562), r = J(r, o, i, n, e[a + 14], 23, -35309556), n = J(n, r, o, i, e[a + 1], 4, -1530992060), i = J(i, n, r, o, e[a + 4], 11, 1272893353), o = J(o, i, n, r, e[a + 7], 16, -155497632), r = J(r, o, i, n, e[a + 10], 23, -1094730640), n = J(n, r, o, i, e[a + 13], 4, 681279174), i = J(i, n, r, o, e[a], 11, -358537222), o = J(o, i, n, r, e[a + 3], 16, -722521979), r = J(r, o, i, n, e[a + 6], 23, 76029189), n = J(n, r, o, i, e[a + 9], 4, -640364487), i = J(i, n, r, o, e[a + 12], 11, -421815835), o = J(o, i, n, r, e[a + 15], 16, 530742520), n = B(n, r = J(r, o, i, n, e[a + 2], 23, -995338651), o, i, e[a], 6, -198630844), i = B(i, n, r, o, e[a + 7], 10, 1126891415), o = B(o, i, n, r, e[a + 14], 15, -1416354905), r = B(r, o, i, n, e[a + 5], 21, -57434055), n = B(n, r, o, i, e[a + 12], 6, 1700485571), i = B(i, n, r, o, e[a + 3], 10, -1894986606), o = B(o, i, n, r, e[a + 10], 15, -1051523), r = B(r, o, i, n, e[a + 1], 21, -2054922799), n = B(n, r, o, i, e[a + 8], 6, 1873313359), i = B(i, n, r, o, e[a + 15], 10, -30611744), o = B(o, i, n, r, e[a + 6], 15, -1560198380), r = B(r, o, i, n, e[a + 13], 21, 1309151649), n = B(n, r, o, i, e[a + 4], 6, -145523070), i = B(i, n, r, o, e[a + 11], 10, -1120210379), o = B(o, i, n, r, e[a + 2], 15, 718787259), r = B(r, o, i, n, e[a + 9], 21, -343485551), n = H(n, s), r = H(r, c), o = H(o, u), i = H(i, l);\n }\n return [n, r, o, i];\n }(function (e) {\n if (0 === e.length) return [];\n for (var t = 8 * e.length, n = new Uint32Array(q(t)), r = 0; r < t; r += 8) n[r >> 5] |= (255 & e[r / 8]) << r % 32;\n return n;\n }(e), 8 * e.length));\n }),\n W = G;\nfunction X(e, t, n, r) {\n switch (e) {\n case 0:\n return t & n ^ ~t & r;\n case 1:\n case 3:\n return t ^ n ^ r;\n case 2:\n return t & n ^ t & r ^ n & r;\n }\n}\nfunction Q(e, t) {\n return e << t | e >>> 32 - t;\n}\nvar Y = M(\"v5\", 80, function (e) {\n var t = [1518500249, 1859775393, 2400959708, 3395469782],\n n = [1732584193, 4023233417, 2562383102, 271733878, 3285377520];\n if (\"string\" == typeof e) {\n var r = unescape(encodeURIComponent(e));\n e = [];\n for (var o = 0; o < r.length; ++o) e.push(r.charCodeAt(o));\n } else Array.isArray(e) || (e = Array.prototype.slice.call(e));\n e.push(128);\n for (var i = e.length / 4 + 2, a = Math.ceil(i / 16), s = new Array(a), c = 0; c < a; ++c) {\n for (var u = new Uint32Array(16), l = 0; l < 16; ++l) u[l] = e[64 * c + 4 * l] << 24 | e[64 * c + 4 * l + 1] << 16 | e[64 * c + 4 * l + 2] << 8 | e[64 * c + 4 * l + 3];\n s[c] = u;\n }\n s[a - 1][14] = 8 * (e.length - 1) / Math.pow(2, 32), s[a - 1][14] = Math.floor(s[a - 1][14]), s[a - 1][15] = 8 * (e.length - 1) & 4294967295;\n for (var d = 0; d < a; ++d) {\n for (var f = new Uint32Array(80), g = 0; g < 16; ++g) f[g] = s[d][g];\n for (var v = 16; v < 80; ++v) f[v] = Q(f[v - 3] ^ f[v - 8] ^ f[v - 14] ^ f[v - 16], 1);\n for (var p = n[0], m = n[1], h = n[2], y = n[3], w = n[4], b = 0; b < 80; ++b) {\n var k = Math.floor(b / 20),\n E = Q(p, 5) + X(k, m, h, y) + w + t[k] + f[b] >>> 0;\n w = y, y = h, h = Q(m, 30) >>> 0, m = p, p = E;\n }\n n[0] = n[0] + p >>> 0, n[1] = n[1] + m >>> 0, n[2] = n[2] + h >>> 0, n[3] = n[3] + y >>> 0, n[4] = n[4] + w >>> 0;\n }\n return [n[0] >> 24 & 255, n[0] >> 16 & 255, n[0] >> 8 & 255, 255 & n[0], n[1] >> 24 & 255, n[1] >> 16 & 255, n[1] >> 8 & 255, 255 & n[1], n[2] >> 24 & 255, n[2] >> 16 & 255, n[2] >> 8 & 255, 255 & n[2], n[3] >> 24 & 255, n[3] >> 16 & 255, n[3] >> 8 & 255, 255 & n[3], n[4] >> 24 & 255, n[4] >> 16 & 255, n[4] >> 8 & 255, 255 & n[4]];\n }),\n Z = Y;\nvar ee = Object.freeze({\n __proto__: null,\n v1: function (e, t, n) {\n var r = t && n || 0,\n o = t || new Array(16),\n i = (e = e || {}).node || U,\n a = void 0 !== e.clockseq ? e.clockseq : R;\n if (null == i || null == a) {\n var s = e.random || (e.rng || O)();\n null == i && (i = U = [1 | s[0], s[1], s[2], s[3], s[4], s[5]]), null == a && (a = R = 16383 & (s[6] << 8 | s[7]));\n }\n var c = void 0 !== e.msecs ? e.msecs : Date.now(),\n u = void 0 !== e.nsecs ? e.nsecs : $ + 1,\n l = c - N + (u - $) / 1e4;\n if (l < 0 && void 0 === e.clockseq && (a = a + 1 & 16383), (l < 0 || c > N) && void 0 === e.nsecs && (u = 0), u >= 1e4) throw new Error(\"uuid.v1(): Can't create more than 10M uuids/sec\");\n N = c, $ = u, R = a;\n var d = (1e4 * (268435455 & (c += 122192928e5)) + u) % 4294967296;\n o[r++] = d >>> 24 & 255, o[r++] = d >>> 16 & 255, o[r++] = d >>> 8 & 255, o[r++] = 255 & d;\n var f = c / 4294967296 * 1e4 & 268435455;\n o[r++] = f >>> 8 & 255, o[r++] = 255 & f, o[r++] = f >>> 24 & 15 | 16, o[r++] = f >>> 16 & 255, o[r++] = a >>> 8 | 128, o[r++] = 255 & a;\n for (var g = 0; g < 6; ++g) o[r + g] = i[g];\n return t || F(o);\n },\n v3: W,\n v4: function (e, t, n) {\n var r = (e = e || {}).random || (e.rng || O)();\n if (r[6] = 15 & r[6] | 64, r[8] = 63 & r[8] | 128, t) {\n n = n || 0;\n for (var o = 0; o < 16; ++o) t[n + o] = r[o];\n return t;\n }\n return F(r);\n },\n v5: Z,\n NIL: \"00000000-0000-0000-0000-000000000000\",\n version: function (e) {\n if (!L(e)) throw TypeError(\"Invalid UUID\");\n return parseInt(e.substr(14, 1), 16);\n },\n validate: L,\n stringify: F,\n parse: V\n});\nconst te = [\"debug\", \"info\", \"warn\", \"error\", \"none\"];\nvar ne = {\n commonBasicLogger: function (e, t) {\n if (e && e.destination && \"function\" != typeof e.destination) throw new Error(\"destination for basicLogger was set to a non-function\");\n function n(e) {\n return function (t) {\n console && console[e] && console[e].call(console, t);\n };\n }\n const r = e && e.destination ? [e.destination, e.destination, e.destination, e.destination] : [n(\"log\"), n(\"info\"), n(\"warn\"), n(\"error\")],\n o = !(!e || !e.destination),\n i = e && void 0 !== e.prefix && null !== e.prefix ? e.prefix : \"[LaunchDarkly] \";\n let a = 1;\n if (e && e.level) for (let t = 0; t < te.length; t++) te[t] === e.level && (a = t);\n function s(e, n, a) {\n if (a.length < 1) return;\n let s;\n const c = o ? n + \": \" + i : i;\n if (1 !== a.length && t) {\n const e = [...a];\n e[0] = c + e[0], s = t(...e);\n } else s = c + a[0];\n try {\n r[e](s);\n } catch (e) {\n console && console.log && console.log(\"[LaunchDarkly] Configured logger's \" + n + \" method threw an exception: \" + e);\n }\n }\n const c = {};\n for (let e = 0; e < te.length; e++) {\n const t = te[e];\n if (\"none\" !== t) if (e < a) c[t] = () => {};else {\n const n = e;\n c[t] = function () {\n s(n, t, arguments);\n };\n }\n }\n return c;\n },\n validateLogger: function (e) {\n te.forEach(t => {\n if (\"none\" !== t && (!e[t] || \"function\" != typeof e[t])) throw new Error(\"Provided logger instance must support logger.\" + t + \"(...) method\");\n });\n }\n};\nfunction re(e) {\n return e && e.message ? e.message : \"string\" == typeof e || e instanceof String ? e : JSON.stringify(e);\n}\nconst oe = \" Please see https://docs.launchdarkly.com/sdk/client-side/javascript#initialize-the-client for instructions on SDK initialization.\";\nvar ie = {\n bootstrapInvalid: function () {\n return \"LaunchDarkly bootstrap data is not available because the back end could not read the flags.\";\n },\n bootstrapOldFormat: function () {\n return \"LaunchDarkly client was initialized with bootstrap data that did not include flag metadata. Events may not be sent correctly.\" + oe;\n },\n clientInitialized: function () {\n return \"LaunchDarkly client initialized\";\n },\n clientNotReady: function () {\n return \"LaunchDarkly client is not ready\";\n },\n debugEnqueueingEvent: function (e) {\n return 'enqueueing \"' + e + '\" event';\n },\n debugPostingDiagnosticEvent: function (e) {\n return \"sending diagnostic event (\" + e.kind + \")\";\n },\n debugPostingEvents: function (e) {\n return \"sending \" + e + \" events\";\n },\n debugStreamDelete: function (e) {\n return 'received streaming deletion for flag \"' + e + '\"';\n },\n debugStreamDeleteIgnored: function (e) {\n return 'received streaming deletion for flag \"' + e + '\" but ignored due to version check';\n },\n debugStreamPatch: function (e) {\n return 'received streaming update for flag \"' + e + '\"';\n },\n debugStreamPatchIgnored: function (e) {\n return 'received streaming update for flag \"' + e + '\" but ignored due to version check';\n },\n debugStreamPing: function () {\n return \"received ping message from stream\";\n },\n debugPolling: function (e) {\n return \"polling for feature flags at \" + e;\n },\n debugStreamPut: function () {\n return \"received streaming update for all flags\";\n },\n deprecated: function (e, t) {\n return t ? '\"' + e + '\" is deprecated, please use \"' + t + '\"' : '\"' + e + '\" is deprecated';\n },\n environmentNotFound: function () {\n return \"Environment not found. Double check that you specified a valid environment/client-side ID.\" + oe;\n },\n environmentNotSpecified: function () {\n return \"No environment/client-side ID was specified.\" + oe;\n },\n errorFetchingFlags: function (e) {\n return \"Error fetching flag settings: \" + re(e);\n },\n eventCapacityExceeded: function () {\n return \"Exceeded event queue capacity. Increase capacity to avoid dropping events.\";\n },\n eventWithoutContext: function () {\n return \"Be sure to call `identify` in the LaunchDarkly client: https://docs.launchdarkly.com/sdk/features/identify#javascript\";\n },\n httpErrorMessage: function (e, t, n) {\n return \"Received error \" + e + (401 === e ? \" (invalid SDK key)\" : \"\") + \" for \" + t + \" - \" + (s.isHttpErrorRecoverable(e) ? n : \"giving up permanently\");\n },\n httpUnavailable: function () {\n return \"Cannot make HTTP requests in this environment.\" + oe;\n },\n identifyDisabled: function () {\n return \"identify() has no effect here; it must be called on the main client instance\";\n },\n inspectorMethodError: (e, t) => `an inspector: \"${t}\" of type: \"${e}\" generated an exception`,\n invalidContentType: function (e) {\n return 'Expected application/json content type but got \"' + e + '\"';\n },\n invalidData: function () {\n return \"Invalid data received from LaunchDarkly; connection may have been interrupted\";\n },\n invalidInspector: (e, t) => `an inspector: \"${t}\" of an invalid type (${e}) was configured`,\n invalidKey: function () {\n return \"Event key must be a string\";\n },\n invalidMetricValue: e => `The track function was called with a non-numeric \"metricValue\" (${e}), only numeric metric values are supported.`,\n invalidContext: function () {\n return \"Invalid context specified.\" + oe;\n },\n invalidTagValue: e => `Config option \"${e}\" must only contain letters, numbers, ., _ or -.`,\n localStorageUnavailable: function (e) {\n return \"local storage is unavailable: \" + re(e);\n },\n networkError: e => \"network error\" + (e ? \" (\" + e + \")\" : \"\"),\n optionBelowMinimum: (e, t, n) => 'Config option \"' + e + '\" was set to ' + t + \", changing to minimum value of \" + n,\n streamClosing: function () {\n return \"Closing stream connection\";\n },\n streamConnecting: function (e) {\n return \"Opening stream connection to \" + e;\n },\n streamError: function (e, t) {\n return \"Error on stream connection: \" + re(e) + \", will continue retrying after \" + t + \" milliseconds.\";\n },\n tagValueTooLong: e => `Value of \"${e}\" was longer than 64 characters and was discarded.`,\n unknownCustomEventKey: function (e) {\n return 'Custom event \"' + e + '\" does not exist';\n },\n unknownOption: e => 'Ignoring unknown config option \"' + e + '\"',\n contextNotSpecified: function () {\n return \"No context specified.\" + oe;\n },\n unrecoverableStreamError: e => `Error on stream connection ${re(e)}, giving up permanently`,\n wrongOptionType: (e, t, n) => 'Config option \"' + e + '\" should be of type ' + t + \", got \" + n + \", using default value\",\n wrongOptionTypeBoolean: (e, t) => 'Config option \"' + e + '\" should be a boolean, got ' + t + \", converting to boolean\"\n};\nconst {\n validateLogger: ae\n } = ne,\n se = {\n baseUrl: {\n default: \"https://app.launchdarkly.com\"\n },\n streamUrl: {\n default: \"https://clientstream.launchdarkly.com\"\n },\n eventsUrl: {\n default: \"https://events.launchdarkly.com\"\n },\n sendEvents: {\n default: !0\n },\n streaming: {\n type: \"boolean\"\n },\n sendLDHeaders: {\n default: !0\n },\n requestHeaderTransform: {\n type: \"function\"\n },\n sendEventsOnlyForVariation: {\n default: !1\n },\n useReport: {\n default: !1\n },\n evaluationReasons: {\n default: !1\n },\n eventCapacity: {\n default: 100,\n minimum: 1\n },\n flushInterval: {\n default: 2e3,\n minimum: 2e3\n },\n samplingInterval: {\n default: 0,\n minimum: 0\n },\n streamReconnectDelay: {\n default: 1e3,\n minimum: 0\n },\n allAttributesPrivate: {\n default: !1\n },\n privateAttributes: {\n default: []\n },\n bootstrap: {\n type: \"string|object\"\n },\n diagnosticRecordingInterval: {\n default: 9e5,\n minimum: 2e3\n },\n diagnosticOptOut: {\n default: !1\n },\n wrapperName: {\n type: \"string\"\n },\n wrapperVersion: {\n type: \"string\"\n },\n stateProvider: {\n type: \"object\"\n },\n application: {\n validator: function (e, t, n) {\n const r = {};\n t.id && (r.id = le(`${e}.id`, t.id, n));\n t.version && (r.version = le(`${e}.version`, t.version, n));\n return r;\n }\n },\n inspectors: {\n default: []\n }\n },\n ce = /^(\\w|\\.|-)+$/;\nfunction ue(e) {\n return e && e.replace(/\\/+$/, \"\");\n}\nfunction le(e, t, n) {\n if (\"string\" == typeof t && t.match(ce)) {\n if (!(t.length > 64)) return t;\n n.warn(ie.tagValueTooLong(e));\n } else n.warn(ie.invalidTagValue(e));\n}\nvar de = {\n baseOptionDefs: se,\n validate: function (e, t, n, r) {\n const o = S.extend({\n logger: {\n default: r\n }\n }, se, n),\n i = {};\n function a(e) {\n S.onNextTick(() => {\n t && t.maybeReportError(new s.LDInvalidArgumentError(e));\n });\n }\n let c = S.extend({}, e || {});\n return function (e) {\n const t = e;\n Object.keys(i).forEach(e => {\n if (void 0 !== t[e]) {\n const n = i[e];\n r && r.warn(ie.deprecated(e, n)), n && (void 0 === t[n] && (t[n] = t[e]), delete t[e]);\n }\n });\n }(c), c = function (e) {\n const t = S.extend({}, e);\n return Object.keys(o).forEach(e => {\n void 0 !== t[e] && null !== t[e] || (t[e] = o[e] && o[e].default);\n }), t;\n }(c), c = function (e) {\n const t = S.extend({}, e),\n n = e => {\n if (null === e) return \"any\";\n if (void 0 === e) return;\n if (Array.isArray(e)) return \"array\";\n const t = typeof e;\n return \"boolean\" === t || \"string\" === t || \"number\" === t || \"function\" === t ? t : \"object\";\n };\n return Object.keys(e).forEach(i => {\n const s = e[i];\n if (null != s) {\n const c = o[i];\n if (void 0 === c) a(ie.unknownOption(i));else {\n const o = c.type || n(c.default),\n u = c.validator;\n if (u) {\n const n = u(i, e[i], r);\n void 0 !== n ? t[i] = n : delete t[i];\n } else if (\"any\" !== o) {\n const e = o.split(\"|\"),\n r = n(s);\n e.indexOf(r) < 0 ? \"boolean\" === o ? (t[i] = !!s, a(ie.wrongOptionTypeBoolean(i, r))) : (a(ie.wrongOptionType(i, o, r)), t[i] = c.default) : \"number\" === r && void 0 !== c.minimum && s < c.minimum && (a(ie.optionBelowMinimum(i, s, c.minimum)), t[i] = c.minimum);\n }\n }\n }\n }), t.baseUrl = ue(t.baseUrl), t.streamUrl = ue(t.streamUrl), t.eventsUrl = ue(t.eventsUrl), t;\n }(c), ae(c.logger), c;\n },\n getTags: function (e) {\n const t = {};\n return e && (e.application && void 0 !== e.application.id && null !== e.application.id && (t[\"application-id\"] = [e.application.id]), e.application && void 0 !== e.application.version && null !== e.application.id && (t[\"application-version\"] = [e.application.version])), t;\n }\n};\nconst {\n getLDUserAgentString: fe\n} = S;\nvar ge = {\n getLDHeaders: function (e, t) {\n if (t && !t.sendLDHeaders) return {};\n const n = {};\n n[e.userAgentHeaderName || \"User-Agent\"] = fe(e), t && t.wrapperName && (n[\"X-LaunchDarkly-Wrapper\"] = t.wrapperVersion ? t.wrapperName + \"/\" + t.wrapperVersion : t.wrapperName);\n const r = de.getTags(t),\n o = Object.keys(r);\n return o.length && (n[\"x-launchdarkly-tags\"] = o.sort().map(e => Array.isArray(r[e]) ? r[e].sort().map(t => `${e}/${t}`) : [`${e}/${r[e]}`]).reduce((e, t) => e.concat(t), []).join(\" \")), n;\n },\n transformHeaders: function (e, t) {\n return t && t.requestHeaderTransform ? t.requestHeaderTransform({\n ...e\n }) : e;\n }\n};\nconst {\n v1: ve\n } = ee,\n {\n getLDHeaders: pe,\n transformHeaders: me\n } = ge;\nvar he = function (e, t, n) {\n const r = S.extend({\n \"Content-Type\": \"application/json\"\n }, pe(e, n)),\n o = {};\n return o.sendEvents = (t, o, i) => {\n if (!e.httpRequest) return Promise.resolve();\n const a = JSON.stringify(t),\n c = i ? null : ve();\n return function t(u) {\n const l = i ? r : S.extend({}, r, {\n \"X-LaunchDarkly-Event-Schema\": \"4\",\n \"X-LaunchDarkly-Payload-ID\": c\n });\n return e.httpRequest(\"POST\", o, me(l, n), a).promise.then(e => {\n if (e) return e.status >= 400 && s.isHttpErrorRecoverable(e.status) && u ? t(!1) : function (e) {\n const t = {\n status: e.status\n },\n n = e.header(\"date\");\n if (n) {\n const e = Date.parse(n);\n e && (t.serverTime = e);\n }\n return t;\n }(e);\n }).catch(() => u ? t(!1) : Promise.reject());\n }(!0).catch(() => {});\n }, o;\n};\nconst {\n commonBasicLogger: ye\n} = ne;\nfunction we(e) {\n return \"string\" == typeof e && \"kind\" !== e && e.match(/^(\\w|\\.|-)+$/);\n}\nfunction be(e) {\n return e.includes(\"%\") || e.includes(\":\") ? e.replace(/%/g, \"%25\").replace(/:/g, \"%3A\") : e;\n}\nvar ke = {\n checkContext: function (e, t) {\n if (e) {\n if (t && (void 0 === e.kind || null === e.kind)) return void 0 !== e.key && null !== e.key;\n const n = e.key,\n r = void 0 === e.kind ? \"user\" : e.kind,\n o = we(r),\n i = \"multi\" === r || null != n && \"\" !== n;\n if (\"multi\" === r) {\n const t = Object.keys(e).filter(e => \"kind\" !== e);\n return i && t.every(e => we(e)) && t.every(t => {\n const n = e[t].key;\n return null != n && \"\" !== n;\n });\n }\n return i && o;\n }\n return !1;\n },\n getContextKeys: function (e, t = ye()) {\n if (!e) return;\n const n = {},\n {\n kind: r,\n key: o\n } = e;\n switch (r) {\n case void 0:\n n.user = `${o}`;\n break;\n case \"multi\":\n Object.entries(e).filter(([e]) => \"kind\" !== e).forEach(([e, t]) => {\n t && t.key && (n[e] = t.key);\n });\n break;\n case null:\n t.warn(`null is not a valid context kind: ${e}`);\n break;\n case \"\":\n t.warn(`'' is not a valid context kind: ${e}`);\n break;\n default:\n n[r] = `${o}`;\n }\n return n;\n },\n getContextKinds: function (e) {\n return e ? null === e.kind || void 0 === e.kind ? [\"user\"] : \"multi\" !== e.kind ? [e.kind] : Object.keys(e).filter(e => \"kind\" !== e) : [];\n },\n getCanonicalKey: function (e) {\n if (e) {\n if ((void 0 === e.kind || null === e.kind || \"user\" === e.kind) && e.key) return e.key;\n if (\"multi\" !== e.kind && e.key) return `${e.kind}:${be(e.key)}`;\n if (\"multi\" === e.kind) return Object.keys(e).sort().filter(e => \"kind\" !== e).map(t => `${t}:${be(e[t].key)}`).join(\":\");\n }\n }\n};\nconst {\n getContextKinds: Ee\n} = ke;\nvar De = function () {\n const e = {};\n let t = 0,\n n = 0,\n r = {},\n o = {};\n return e.summarizeEvent = e => {\n if (\"feature\" === e.kind) {\n const i = e.key + \":\" + (null !== e.variation && void 0 !== e.variation ? e.variation : \"\") + \":\" + (null !== e.version && void 0 !== e.version ? e.version : \"\"),\n a = r[i];\n let s = o[e.key];\n s || (s = new Set(), o[e.key] = s), function (e) {\n return e.context ? Ee(e.context) : e.contextKeys ? Object.keys(e.contextKeys) : [];\n }(e).forEach(e => s.add(e)), a ? a.count = a.count + 1 : r[i] = {\n count: 1,\n key: e.key,\n version: e.version,\n variation: e.variation,\n value: e.value,\n default: e.default\n }, (0 === t || e.creationDate < t) && (t = e.creationDate), e.creationDate > n && (n = e.creationDate);\n }\n }, e.getSummary = () => {\n const e = {};\n let i = !0;\n for (const t of Object.values(r)) {\n let n = e[t.key];\n n || (n = {\n default: t.default,\n counters: [],\n contextKinds: [...o[t.key]]\n }, e[t.key] = n);\n const r = {\n value: t.value,\n count: t.count\n };\n void 0 !== t.variation && null !== t.variation && (r.variation = t.variation), void 0 !== t.version && null !== t.version ? r.version = t.version : r.unknown = !0, n.counters.push(r), i = !1;\n }\n return i ? null : {\n startDate: t,\n endDate: n,\n features: e\n };\n }, e.clearSummary = () => {\n t = 0, n = 0, r = {}, o = {};\n }, e;\n};\nfunction xe(e) {\n return e.replace(/~/g, \"~0\").replace(/\\//g, \"~1\");\n}\nfunction Ce(e) {\n return (e.startsWith(\"/\") ? e.substring(1) : e).split(\"/\").map(e => e.indexOf(\"~\") >= 0 ? e.replace(/~1/g, \"/\").replace(/~0/g, \"~\") : e);\n}\nfunction Pe(e) {\n return !e.startsWith(\"/\");\n}\nfunction Se(e, t) {\n const n = Pe(e),\n r = Pe(t);\n if (n && r) return e === t;\n if (n) {\n const n = Ce(t);\n return 1 === n.length && e === n[0];\n }\n if (r) {\n const n = Ce(e);\n return 1 === n.length && t === n[0];\n }\n return e === t;\n}\nfunction Ie(e) {\n return `/${xe(e)}`;\n}\nvar Oe = {\n cloneExcluding: function (e, t) {\n const n = [],\n r = {},\n o = [];\n for (n.push(...Object.keys(e).map(t => ({\n key: t,\n ptr: Ie(t),\n source: e,\n parent: r,\n visited: [e]\n }))); n.length;) {\n const e = n.pop();\n if (t.some(t => Se(t, e.ptr))) o.push(e.ptr);else {\n const t = e.source[e.key];\n if (null === t) e.parent[e.key] = t;else if (Array.isArray(t)) e.parent[e.key] = [...t];else if (\"object\" == typeof t) {\n if (e.visited.includes(t)) continue;\n e.parent[e.key] = {}, n.push(...Object.keys(t).map(n => {\n return {\n key: n,\n ptr: (r = e.ptr, o = xe(n), `${r}/${o}`),\n source: t,\n parent: e.parent[e.key],\n visited: [...e.visited, t]\n };\n var r, o;\n }));\n } else e.parent[e.key] = t;\n }\n }\n return {\n cloned: r,\n excluded: o.sort()\n };\n },\n compare: Se,\n literalToReference: Ie\n};\nvar Te = function (e) {\n const t = {},\n n = e.allAttributesPrivate,\n r = e.privateAttributes || [],\n o = [\"key\", \"kind\", \"_meta\", \"anonymous\"],\n i = [\"name\", \"ip\", \"firstName\", \"lastName\", \"email\", \"avatar\", \"country\"],\n a = (e, t) => {\n if (\"object\" != typeof e || null === e || Array.isArray(e)) return;\n const {\n cloned: i,\n excluded: a\n } = Oe.cloneExcluding(e, ((e, t) => (n || t && e.anonymous ? Object.keys(e) : [...r, ...(e._meta && e._meta.privateAttributes || [])]).filter(e => !o.some(t => Oe.compare(e, t))))(e, t));\n return i.key = String(i.key), a.length && (i._meta || (i._meta = {}), i._meta.redactedAttributes = a), i._meta && (delete i._meta.privateAttributes, 0 === Object.keys(i._meta).length && delete i._meta), void 0 !== i.anonymous && (i.anonymous = !!i.anonymous), i;\n };\n return t.filter = (e, t = !1) => void 0 === e.kind || null === e.kind ? a((e => {\n const t = {\n ...(e.custom || {}),\n kind: \"user\",\n key: e.key\n };\n void 0 !== e.anonymous && (t.anonymous = !!e.anonymous);\n for (const n of i) delete t[n], void 0 !== e[n] && null !== e[n] && (t[n] = String(e[n]));\n return void 0 !== e.privateAttributeNames && null !== e.privateAttributeNames && (t._meta = t._meta || {}, t._meta.privateAttributes = e.privateAttributeNames.map(e => e.startsWith(\"/\") ? Oe.literalToReference(e) : e)), t;\n })(e), t) : \"multi\" === e.kind ? ((e, t) => {\n const n = {\n kind: e.kind\n },\n r = Object.keys(e);\n for (const o of r) if (\"kind\" !== o) {\n const r = a(e[o], t);\n r && (n[o] = r);\n }\n return n;\n })(e, t) : a(e, t), t;\n};\nconst {\n getContextKeys: Le\n} = ke;\nvar Ue = function (e, t, n, r = null, o = null, i = null) {\n const a = {},\n c = i || he(e, n, t),\n u = S.appendUrlPath(t.eventsUrl, \"/events/bulk/\" + n),\n l = De(),\n d = Te(t),\n f = t.samplingInterval,\n g = t.eventCapacity,\n v = t.flushInterval,\n p = t.logger;\n let m,\n h = [],\n y = 0,\n w = !1,\n b = !1;\n function k() {\n return 0 === f || 0 === Math.floor(Math.random() * f);\n }\n function E(e) {\n const t = S.extend({}, e);\n return \"identify\" === e.kind ? t.context = d.filter(e.context) : \"feature\" === e.kind ? t.context = d.filter(e.context, !0) : (t.contextKeys = Le(e.context, p), delete t.context), \"feature\" === e.kind && (delete t.trackEvents, delete t.debugEventsUntilDate), t;\n }\n function D(e) {\n h.length < g ? (h.push(e), b = !1) : (b || (b = !0, p.warn(ie.eventCapacityExceeded())), r && r.incrementDroppedEvents());\n }\n return a.enqueue = function (e) {\n if (w) return;\n let t = !1,\n n = !1;\n var r;\n if (l.summarizeEvent(e), \"feature\" === e.kind ? k() && (t = !!e.trackEvents, n = !!(r = e).debugEventsUntilDate && r.debugEventsUntilDate > y && r.debugEventsUntilDate > new Date().getTime()) : t = k(), t && D(E(e)), n) {\n const t = S.extend({}, e, {\n kind: \"debug\"\n });\n t.context = d.filter(t.context), delete t.trackEvents, delete t.debugEventsUntilDate, D(t);\n }\n }, a.flush = function () {\n if (w) return Promise.resolve();\n const e = h,\n t = l.getSummary();\n return l.clearSummary(), t && (t.kind = \"summary\", e.push(t)), r && r.setEventsInLastBatch(e.length), 0 === e.length ? Promise.resolve() : (h = [], p.debug(ie.debugPostingEvents(e.length)), c.sendEvents(e, u).then(e => {\n e && (e.serverTime && (y = e.serverTime), s.isHttpErrorRecoverable(e.status) || (w = !0), e.status >= 400 && S.onNextTick(() => {\n o.maybeReportError(new s.LDUnexpectedResponseError(ie.httpErrorMessage(e.status, \"event posting\", \"some events were dropped\")));\n }));\n }));\n }, a.start = function () {\n const e = () => {\n a.flush(), m = setTimeout(e, v);\n };\n m = setTimeout(e, v);\n }, a.stop = function () {\n clearTimeout(m);\n }, a;\n};\nvar Re = function (e) {\n const t = {},\n n = {};\n return t.on = function (e, t, r) {\n n[e] = n[e] || [], n[e] = n[e].concat({\n handler: t,\n context: r\n });\n }, t.off = function (e, t, r) {\n if (n[e]) for (let o = 0; o < n[e].length; o++) n[e][o].handler === t && n[e][o].context === r && (n[e] = n[e].slice(0, o).concat(n[e].slice(o + 1)));\n }, t.emit = function (e) {\n if (!n[e]) return;\n const t = n[e].slice(0);\n for (let e = 0; e < t.length; e++) t[e].handler.apply(t[e].context, Array.prototype.slice.call(arguments, 1));\n }, t.getEvents = function () {\n return Object.keys(n);\n }, t.getEventListenerCount = function (e) {\n return n[e] ? n[e].length : 0;\n }, t.maybeReportError = function (t) {\n t && (n[\"error\"] ? this.emit(\"error\", t) : (e || console).error(t.message));\n }, t;\n};\nconst Ae = \"ready\",\n je = \"initialized\",\n Fe = \"failed\";\nvar Ne = function (e) {\n let t = !1,\n n = !1,\n r = null,\n o = null;\n const i = new Promise(t => {\n const n = () => {\n e.off(Ae, n), t();\n };\n e.on(Ae, n);\n }).catch(() => {});\n return {\n getInitializationPromise: () => o || (t ? Promise.resolve() : n ? Promise.reject(r) : (o = new Promise((t, n) => {\n const r = () => {\n e.off(je, r), t();\n },\n o = t => {\n e.off(Fe, o), n(t);\n };\n e.on(je, r), e.on(Fe, o);\n }), o)),\n getReadyPromise: () => i,\n signalSuccess: () => {\n t || n || (t = !0, e.emit(je), e.emit(Ae));\n },\n signalFailure: o => {\n t || n || (n = !0, r = o, e.emit(Fe, o), e.emit(Ae)), e.maybeReportError(o);\n }\n };\n};\nvar $e = function (e, t, n, r) {\n const o = {};\n function i() {\n let e = \"\";\n const o = r.getContext();\n return o && (e = n || S.btoa(JSON.stringify(o))), \"ld:\" + t + \":\" + e;\n }\n return o.loadFlags = () => e.get(i()).then(e => {\n if (null == e) return null;\n try {\n let t = JSON.parse(e);\n if (t) {\n const e = t.$schema;\n void 0 === e || e < 1 ? t = S.transformValuesToVersionedValues(t) : delete t.$schema;\n }\n return t;\n } catch (e) {\n return o.clearFlags().then(() => null);\n }\n }), o.saveFlags = t => {\n const n = S.extend({}, t, {\n $schema: 1\n });\n return e.set(i(), JSON.stringify(n));\n }, o.clearFlags = () => e.clear(i()), o;\n};\nvar Ve = function (e, t) {\n const n = {};\n let r = !1;\n const o = e => {\n r || (r = !0, t.warn(ie.localStorageUnavailable(e)));\n };\n return n.isEnabled = () => !!e, n.get = t => new Promise(n => {\n e ? e.get(t).then(n).catch(e => {\n o(e), n(void 0);\n }) : n(void 0);\n }), n.set = (t, n) => new Promise(r => {\n e ? e.set(t, n).then(() => r(!0)).catch(e => {\n o(e), r(!1);\n }) : r(!1);\n }), n.clear = t => new Promise(n => {\n e ? e.clear(t).then(() => n(!0)).catch(e => {\n o(e), n(!1);\n }) : n(!1);\n }), n;\n};\nconst {\n appendUrlPath: Me,\n base64URLEncode: qe,\n objectHasOwnProperty: He\n } = S,\n {\n getLDHeaders: ze,\n transformHeaders: Ke\n } = ge,\n {\n isHttpErrorRecoverable: _e\n } = s;\nvar Je = function (e, t, n, r) {\n const o = t.streamUrl,\n i = t.logger,\n a = {},\n s = Me(o, \"/eval/\" + n),\n c = t.useReport,\n u = t.evaluationReasons,\n l = t.streamReconnectDelay,\n d = ze(e, t);\n let f,\n g = !1,\n v = null,\n p = null,\n m = null,\n h = null,\n y = null,\n w = 0;\n function b() {\n const e = (t = function () {\n const e = l * Math.pow(2, w);\n return e > 3e4 ? 3e4 : e;\n }(), t - Math.trunc(.5 * Math.random() * t));\n var t;\n return w += 1, e;\n }\n function k(e) {\n if (e.status && \"number\" == typeof e.status && !_e(e.status)) return x(), i.error(ie.unrecoverableStreamError(e)), void (p && (clearTimeout(p), p = null));\n const t = b();\n g || (i.warn(ie.streamError(e, t)), g = !0), C(!1), x(), E(t);\n }\n function E(e) {\n p || (e ? p = setTimeout(D, e) : D());\n }\n function D() {\n let r;\n p = null;\n let a = \"\";\n const l = {\n headers: d,\n readTimeoutMillis: 3e5\n };\n if (e.eventSourceFactory) {\n null != h && (a = \"h=\" + h), c ? e.eventSourceAllowsReport ? (r = s, l.method = \"REPORT\", l.headers[\"Content-Type\"] = \"application/json\", l.body = JSON.stringify(m)) : (r = Me(o, \"/ping/\" + n), a = \"\") : r = s + \"/\" + qe(JSON.stringify(m)), l.headers = Ke(l.headers, t), u && (a = a + (a ? \"&\" : \"\") + \"withReasons=true\"), r = r + (a ? \"?\" : \"\") + a, x(), i.info(ie.streamConnecting(r)), f = new Date().getTime(), v = e.eventSourceFactory(r, l);\n for (const e in y) He(y, e) && v.addEventListener(e, y[e]);\n v.onerror = k, v.onopen = () => {\n w = 0;\n };\n }\n }\n function x() {\n v && (i.info(ie.streamClosing()), v.close(), v = null);\n }\n function C(e) {\n f && r && r.recordStreamInit(f, !e, new Date().getTime() - f), f = null;\n }\n return a.connect = function (e, t, n) {\n m = e, h = t, y = {};\n for (const e in n || {}) y[e] = function (t) {\n g = !1, C(!0), n[e] && n[e](t);\n };\n E();\n }, a.disconnect = function () {\n clearTimeout(p), p = null, x();\n }, a.isConnected = function () {\n return !!(v && e.eventSourceIsActive && e.eventSourceIsActive(v));\n }, a;\n};\nvar Be = function (e) {\n let t, n, r, o;\n const i = {\n addPromise: (i, a) => {\n t = i, n && n(), n = a, i.then(n => {\n t === i && (r(n), e && e());\n }, n => {\n t === i && (o(n), e && e());\n });\n }\n };\n return i.resultPromise = new Promise((e, t) => {\n r = e, o = t;\n }), i;\n};\nconst {\n transformHeaders: Ge,\n getLDHeaders: We\n } = ge,\n Xe = \"application/json\";\nvar Qe = function (e, t, n) {\n const r = t.baseUrl,\n o = t.useReport,\n i = t.evaluationReasons,\n a = t.logger,\n c = {},\n u = {};\n function l(n, r) {\n if (!e.httpRequest) return new Promise((e, t) => {\n t(new s.LDFlagFetchError(ie.httpUnavailable()));\n });\n const o = r ? \"REPORT\" : \"GET\",\n i = We(e, t);\n r && (i[\"Content-Type\"] = Xe);\n let a = u[n];\n a || (a = Be(() => {\n delete u[n];\n }), u[n] = a);\n const c = e.httpRequest(o, n, Ge(i, t), r),\n l = c.promise.then(e => {\n if (200 === e.status) {\n if (e.header(\"content-type\") && e.header(\"content-type\").substring(0, 16) === Xe) return JSON.parse(e.body);\n {\n const t = ie.invalidContentType(e.header(\"content-type\") || \"\");\n return Promise.reject(new s.LDFlagFetchError(t));\n }\n }\n return Promise.reject(function (e) {\n return 404 === e.status ? new s.LDInvalidEnvironmentIdError(ie.environmentNotFound()) : new s.LDFlagFetchError(ie.errorFetchingFlags(e.statusText || String(e.status)));\n }(e));\n }, e => Promise.reject(new s.LDFlagFetchError(ie.networkError(e))));\n return a.addPromise(l, () => {\n c.cancel && c.cancel();\n }), a.resultPromise;\n }\n return c.fetchJSON = function (e) {\n return l(S.appendUrlPath(r, e), null);\n }, c.fetchFlagSettings = function (e, t) {\n let s,\n c,\n u,\n d = \"\";\n return o ? (c = [r, \"/sdk/evalx/\", n, \"/context\"].join(\"\"), u = JSON.stringify(e)) : (s = S.base64URLEncode(JSON.stringify(e)), c = [r, \"/sdk/evalx/\", n, \"/contexts/\", s].join(\"\")), t && (d = \"h=\" + t), i && (d = d + (d ? \"&\" : \"\") + \"withReasons=true\"), c = c + (d ? \"?\" : \"\") + d, a.debug(ie.debugPolling(c)), l(c, u);\n }, c;\n};\nvar Ye = function (e, t) {\n const n = {};\n let r;\n return n.setContext = function (e) {\n r = S.sanitizeContext(e), r && t && t(S.clone(r));\n }, n.getContext = function () {\n return r ? S.clone(r) : null;\n }, e && n.setContext(e), n;\n};\nconst {\n v1: Ze\n } = ee,\n {\n getContextKinds: et\n } = ke;\nvar tt = function (e) {\n function t(e) {\n return null == e || \"user\" === e ? \"ld:$anonUserId\" : `ld:$contextKey:${e}`;\n }\n function n(n, r) {\n return null !== r.key && void 0 !== r.key ? (r.key = r.key.toString(), Promise.resolve(r)) : r.anonymous ? function (n) {\n return e.get(t(n));\n }(n).then(o => {\n if (o) return r.key = o, r;\n {\n const o = Ze();\n return r.key = o, function (n, r) {\n return e.set(t(r), n);\n }(o, n).then(() => r);\n }\n }) : Promise.reject(new s.LDInvalidUserError(ie.invalidContext()));\n }\n this.processContext = e => {\n if (!e) return Promise.reject(new s.LDInvalidUserError(ie.contextNotSpecified()));\n const t = S.clone(e);\n if (\"multi\" === e.kind) {\n const e = et(t);\n return Promise.all(e.map(e => n(e, t[e]))).then(() => t);\n }\n return n(e.kind, t);\n };\n};\nconst {\n v1: nt\n } = ee,\n {\n baseOptionDefs: rt\n } = de,\n {\n appendUrlPath: ot\n } = S;\nvar it = {\n DiagnosticId: function (e) {\n const t = {\n diagnosticId: nt()\n };\n return e && (t.sdkKeySuffix = e.length > 6 ? e.substring(e.length - 6) : e), t;\n },\n DiagnosticsAccumulator: function (e) {\n let t, n, r, o;\n function i(e) {\n t = e, n = 0, r = 0, o = [];\n }\n return i(e), {\n getProps: () => ({\n dataSinceDate: t,\n droppedEvents: n,\n eventsInLastBatch: r,\n streamInits: o\n }),\n setProps: e => {\n t = e.dataSinceDate, n = e.droppedEvents || 0, r = e.eventsInLastBatch || 0, o = e.streamInits || [];\n },\n incrementDroppedEvents: () => {\n n++;\n },\n setEventsInLastBatch: e => {\n r = e;\n },\n recordStreamInit: (e, t, n) => {\n const r = {\n timestamp: e,\n failed: t,\n durationMillis: n\n };\n o.push(r);\n },\n reset: i\n };\n },\n DiagnosticsManager: function (e, t, n, r, o, i, a) {\n const s = !!e.diagnosticUseCombinedEvent,\n c = \"ld:\" + o + \":$diagnostics\",\n u = ot(i.eventsUrl, \"/events/diagnostic/\" + o),\n l = i.diagnosticRecordingInterval,\n d = n;\n let f,\n g,\n v = !!i.streaming;\n const p = {};\n function m() {\n return {\n sdk: w(),\n configuration: b(),\n platform: e.diagnosticPlatformData\n };\n }\n function h(e) {\n i.logger && i.logger.debug(ie.debugPostingDiagnosticEvent(e)), r.sendEvents(e, u, !0).then(() => {}).catch(() => {});\n }\n function y() {\n h(function () {\n const e = new Date().getTime();\n let t = {\n kind: s ? \"diagnostic-combined\" : \"diagnostic\",\n id: a,\n creationDate: e,\n ...d.getProps()\n };\n return s && (t = {\n ...t,\n ...m()\n }), d.reset(e), t;\n }()), g = setTimeout(y, l), f = new Date().getTime(), s && function () {\n if (t.isEnabled()) {\n const e = {\n ...d.getProps()\n };\n t.set(c, JSON.stringify(e));\n }\n }();\n }\n function w() {\n const t = {\n ...e.diagnosticSdkData\n };\n return i.wrapperName && (t.wrapperName = i.wrapperName), i.wrapperVersion && (t.wrapperVersion = i.wrapperVersion), t;\n }\n function b() {\n return {\n customBaseURI: i.baseUrl !== rt.baseUrl.default,\n customStreamURI: i.streamUrl !== rt.streamUrl.default,\n customEventsURI: i.eventsUrl !== rt.eventsUrl.default,\n eventsCapacity: i.eventCapacity,\n eventsFlushIntervalMillis: i.flushInterval,\n reconnectTimeMillis: i.streamReconnectDelay,\n streamingDisabled: !v,\n allAttributesPrivate: !!i.allAttributesPrivate,\n diagnosticRecordingIntervalMillis: i.diagnosticRecordingInterval,\n usingSecureMode: !!i.hash,\n bootstrapMode: !!i.bootstrap,\n fetchGoalsDisabled: !i.fetchGoals,\n sendEventsOnlyForVariation: !!i.sendEventsOnlyForVariation\n };\n }\n return p.start = () => {\n s ? function (e) {\n if (!t.isEnabled()) return e(!1);\n t.get(c).then(t => {\n if (t) try {\n const e = JSON.parse(t);\n d.setProps(e), f = e.dataSinceDate;\n } catch (e) {}\n e(!0);\n }).catch(() => {\n e(!1);\n });\n }(e => {\n if (e) {\n const e = (f || 0) + l,\n t = new Date().getTime();\n t >= e ? y() : g = setTimeout(y, e - t);\n } else 0 === Math.floor(4 * Math.random()) ? y() : g = setTimeout(y, l);\n }) : (h({\n kind: \"diagnostic-init\",\n id: a,\n creationDate: d.getProps().dataSinceDate,\n ...m()\n }), g = setTimeout(y, l));\n }, p.stop = () => {\n g && clearTimeout(g);\n }, p.setStreaming = e => {\n v = e;\n }, p;\n }\n};\nvar at = function (e, t) {\n let n = !1;\n const r = {\n type: e.type,\n name: e.name,\n synchronous: e.synchronous,\n method: (...o) => {\n try {\n e.method(...o);\n } catch {\n n || (n = !0, t.warn(ie.inspectorMethodError(r.type, r.name)));\n }\n }\n };\n return r;\n};\nconst {\n onNextTick: st\n } = S,\n ct = {\n flagUsed: \"flag-used\",\n flagDetailsChanged: \"flag-details-changed\",\n flagDetailChanged: \"flag-detail-changed\",\n clientIdentityChanged: \"client-identity-changed\"\n };\nObject.freeze(ct);\nvar ut = {\n InspectorTypes: ct,\n InspectorManager: function (e, t) {\n const n = {},\n r = {\n [ct.flagUsed]: [],\n [ct.flagDetailsChanged]: [],\n [ct.flagDetailChanged]: [],\n [ct.clientIdentityChanged]: []\n },\n o = {\n [ct.flagUsed]: [],\n [ct.flagDetailsChanged]: [],\n [ct.flagDetailChanged]: [],\n [ct.clientIdentityChanged]: []\n },\n i = e && e.map(e => at(e, t));\n return i && i.forEach(e => {\n Object.prototype.hasOwnProperty.call(r, e.type) && !e.synchronous ? r[e.type].push(e) : Object.prototype.hasOwnProperty.call(o, e.type) && e.synchronous ? o[e.type].push(e) : t.warn(ie.invalidInspector(e.type, e.name));\n }), n.hasListeners = e => r[e] && r[e].length || o[e] && o[e].length, n.onFlagUsed = (e, t, n) => {\n const i = ct.flagUsed;\n o[i].length && o[i].forEach(r => r.method(e, t, n)), r[i].length && st(() => {\n r[i].forEach(r => r.method(e, t, n));\n });\n }, n.onFlags = e => {\n const t = ct.flagDetailsChanged;\n o[t].length && o[t].forEach(t => t.method(e)), r[t].length && st(() => {\n r[t].forEach(t => t.method(e));\n });\n }, n.onFlagChanged = (e, t) => {\n const n = ct.flagDetailChanged;\n o[n].length && o[n].forEach(n => n.method(e, t)), r[n].length && st(() => {\n r[n].forEach(n => n.method(e, t));\n });\n }, n.onIdentityChanged = e => {\n const t = ct.clientIdentityChanged;\n o[t].length && o[t].forEach(t => t.method(e)), r[t].length && st(() => {\n r[t].forEach(t => t.method(e));\n });\n }, n;\n }\n};\nconst {\n LDTimeoutError: lt\n} = s;\nvar dt = function (e, t) {\n return new Promise((n, r) => {\n setTimeout(() => {\n r(new lt(`${t} timed out after ${e} seconds.`));\n }, 1e3 * e);\n });\n};\nconst {\n commonBasicLogger: ft\n } = ne,\n {\n checkContext: gt,\n getContextKeys: vt\n } = ke,\n {\n InspectorTypes: pt,\n InspectorManager: mt\n } = ut,\n ht = \"change\",\n yt = \"internal-change\";\nvar wt = {\n initialize: function (e, t, n, r, o) {\n const i = function () {\n if (n && n.logger) return n.logger;\n return o && o.logger && o.logger.default || ft(\"warn\");\n }(),\n a = Re(i),\n c = Ne(a),\n u = de.validate(n, a, o, i),\n l = mt(u.inspectors, i),\n d = u.sendEvents;\n let f = e,\n g = u.hash;\n const v = Ve(r.localStorage, i),\n p = he(r, f, u),\n m = u.sendEvents && !u.diagnosticOptOut,\n h = m ? it.DiagnosticId(f) : null,\n y = m ? it.DiagnosticsAccumulator(new Date().getTime()) : null,\n w = m ? it.DiagnosticsManager(r, v, y, p, f, u, h) : null,\n b = Je(r, u, f, y),\n k = u.eventProcessor || Ue(r, u, f, y, a, p),\n E = Qe(r, u, f);\n let D,\n x,\n C,\n P = {},\n I = u.streaming,\n O = !1,\n T = !1,\n L = !0;\n const U = u.stateProvider,\n R = Ye(null, function (e) {\n (function (e) {\n if (U) return;\n e && F({\n kind: \"identify\",\n context: e,\n creationDate: new Date().getTime()\n });\n })(e), l.hasListeners(pt.clientIdentityChanged) && l.onIdentityChanged(R.getContext());\n }),\n A = new tt(v),\n j = v.isEnabled() ? $e(v, f, g, R) : null;\n function F(e) {\n f && (U && U.enqueueEvent && U.enqueueEvent(e) || (e.context ? (L = !1, !d || T || r.isDoNotTrack() || (i.debug(ie.debugEnqueueingEvent(e.kind)), k.enqueue(e))) : L && (i.warn(ie.eventWithoutContext()), L = !1)));\n }\n function N(e, t) {\n l.hasListeners(pt.flagDetailChanged) && l.onFlagChanged(e.key, H(t));\n }\n function $() {\n l.hasListeners(pt.flagDetailsChanged) && l.onFlags(Object.entries(P).map(([e, t]) => ({\n key: e,\n detail: H(t)\n })).reduce((e, t) => (e[t.key] = t.detail, e), {}));\n }\n function V(e, t, n, r) {\n const o = R.getContext(),\n i = new Date(),\n a = {\n kind: \"feature\",\n key: e,\n context: o,\n value: t ? t.value : null,\n variation: t ? t.variationIndex : null,\n default: n,\n creationDate: i.getTime()\n },\n s = P[e];\n s && (a.version = s.flagVersion ? s.flagVersion : s.version, a.trackEvents = s.trackEvents, a.debugEventsUntilDate = s.debugEventsUntilDate), (r || s && s.trackReason) && t && (a.reason = t.reason), F(a);\n }\n function M(e) {\n return gt(e, !1) ? Promise.resolve(e) : Promise.reject(new s.LDInvalidUserError(ie.invalidContext()));\n }\n function q(e, t, n, r, o, i) {\n let a, s;\n return P && S.objectHasOwnProperty(P, e) && P[e] && !P[e].deleted ? (s = P[e], a = H(s), null !== s.value && void 0 !== s.value || (a.value = t)) : a = {\n value: t,\n variationIndex: null,\n reason: {\n kind: \"ERROR\",\n errorKind: \"FLAG_NOT_FOUND\"\n }\n }, n && (o || s?.prerequisites?.forEach(e => {\n q(e, void 0, n, !1, !1, !1);\n }), V(e, a, t, r)), !o && i && function (e, t) {\n l.hasListeners(pt.flagUsed) && l.onFlagUsed(e, t, R.getContext());\n }(e, a), a;\n }\n function H(e) {\n return {\n value: e.value,\n variationIndex: void 0 === e.variation ? null : e.variation,\n reason: e.reason || null\n };\n }\n function z() {\n if (x = !0, !R.getContext()) return;\n const e = e => {\n try {\n return JSON.parse(e);\n } catch (e) {\n return void a.maybeReportError(new s.LDInvalidDataError(ie.invalidData()));\n }\n };\n b.connect(R.getContext(), g, {\n ping: function () {\n i.debug(ie.debugStreamPing());\n const e = R.getContext();\n E.fetchFlagSettings(e, g).then(t => {\n S.deepEquals(e, R.getContext()) && _(t || {});\n }).catch(e => {\n a.maybeReportError(new s.LDFlagFetchError(ie.errorFetchingFlags(e)));\n });\n },\n put: function (t) {\n const n = e(t.data);\n n && (i.debug(ie.debugStreamPut()), _(n));\n },\n patch: function (t) {\n const n = e(t.data);\n if (!n) return;\n const r = P[n.key];\n if (!r || !r.version || !n.version || r.version < n.version) {\n i.debug(ie.debugStreamPatch(n.key));\n const e = {},\n t = S.extend({}, n);\n delete t.key, P[n.key] = t;\n const o = H(t);\n e[n.key] = r ? {\n previous: r.value,\n current: o\n } : {\n current: o\n }, N(n, t), J(e);\n } else i.debug(ie.debugStreamPatchIgnored(n.key));\n },\n delete: function (t) {\n const n = e(t.data);\n if (n) if (!P[n.key] || P[n.key].version < n.version) {\n i.debug(ie.debugStreamDelete(n.key));\n const e = {};\n P[n.key] && !P[n.key].deleted && (e[n.key] = {\n previous: P[n.key].value\n }), P[n.key] = {\n version: n.version,\n deleted: !0\n }, N(n, P[n.key]), J(e);\n } else i.debug(ie.debugStreamDeleteIgnored(n.key));\n }\n });\n }\n function K() {\n x && (b.disconnect(), x = !1);\n }\n function _(e) {\n const t = {};\n if (!e) return Promise.resolve();\n for (const n in P) S.objectHasOwnProperty(P, n) && P[n] && (e[n] && !S.deepEquals(e[n].value, P[n].value) ? t[n] = {\n previous: P[n].value,\n current: H(e[n])\n } : e[n] && !e[n].deleted || (t[n] = {\n previous: P[n].value\n }));\n for (const n in e) S.objectHasOwnProperty(e, n) && e[n] && (!P[n] || P[n].deleted) && (t[n] = {\n current: H(e[n])\n });\n return P = {\n ...e\n }, $(), J(t).catch(() => {});\n }\n function J(e) {\n const t = Object.keys(e);\n if (t.length > 0) {\n const n = {};\n t.forEach(t => {\n const r = e[t].current,\n o = r ? r.value : void 0,\n i = e[t].previous;\n a.emit(ht + \":\" + t, o, i), n[t] = r ? {\n current: o,\n previous: i\n } : {\n previous: i\n };\n }), a.emit(ht, n), a.emit(yt, P), u.sendEventsOnlyForVariation || U || t.forEach(t => {\n V(t, e[t].current);\n });\n }\n return D && j ? j.saveFlags(P) : Promise.resolve();\n }\n function B() {\n const e = I || C && void 0 === I;\n e && !x ? z() : !e && x && K(), w && w.setStreaming(e);\n }\n function G(e) {\n return e === ht || e.substr(0, 7) === ht + \":\";\n }\n if (\"string\" == typeof u.bootstrap && \"LOCALSTORAGE\" === u.bootstrap.toUpperCase() && (j ? D = !0 : i.warn(ie.localStorageUnavailable())), \"object\" == typeof u.bootstrap && (P = function (e) {\n const t = Object.keys(e),\n n = \"$flagsState\",\n r = \"$valid\",\n o = e[n];\n !o && t.length && i.warn(ie.bootstrapOldFormat()), !1 === e[r] && i.warn(ie.bootstrapInvalid());\n const a = {};\n return t.forEach(t => {\n if (t !== n && t !== r) {\n let n = {\n value: e[t]\n };\n o && o[t] ? n = S.extend(n, o[t]) : n.version = 0, a[t] = n;\n }\n }), a;\n }(u.bootstrap)), U) {\n const e = U.getInitialState();\n e ? W(e) : U.on(\"init\", W), U.on(\"update\", function (e) {\n e.context && R.setContext(e.context);\n e.flags && _(e.flags);\n });\n } else (function () {\n if (!e) return Promise.reject(new s.LDInvalidEnvironmentIdError(ie.environmentNotSpecified()));\n return A.processContext(t).then(M).then(e => (R.setContext(e), \"object\" == typeof u.bootstrap ? X() : D ? j.loadFlags().then(e => null == e ? (P = {}, E.fetchFlagSettings(R.getContext(), g).then(e => _(e || {})).then(X).catch(e => {\n Q(new s.LDFlagFetchError(ie.errorFetchingFlags(e)));\n })) : (P = e, S.onNextTick(X), E.fetchFlagSettings(R.getContext(), g).then(e => _(e)).catch(e => a.maybeReportError(e)))) : E.fetchFlagSettings(R.getContext(), g).then(e => {\n P = e || {}, $(), X();\n }).catch(e => {\n P = {}, Q(e);\n })));\n })().catch(Q);\n function W(e) {\n f = e.environment, R.setContext(e.context), P = {\n ...e.flags\n }, S.onNextTick(X);\n }\n function X() {\n i.info(ie.clientInitialized()), O = !0, B(), c.signalSuccess();\n }\n function Q(e) {\n c.signalFailure(e);\n }\n const Y = {\n waitForInitialization: function (e = void 0) {\n if (null != e) {\n if (\"number\" == typeof e) return function (e) {\n e > 5 && i.warn(\"The waitForInitialization function was called with a timeout greater than 5 seconds. We recommend a timeout of 5 seconds or less.\");\n const t = c.getInitializationPromise(),\n n = dt(e, \"waitForInitialization\");\n return Promise.race([n, t]).catch(e => {\n throw e instanceof s.LDTimeoutError && i.error(`waitForInitialization error: ${e}`), e;\n });\n }(e);\n i.warn(\"The waitForInitialization method was provided with a non-numeric timeout.\");\n }\n return i.warn(\"The waitForInitialization function was called without a timeout specified. In a future version a default timeout will be applied.\"), c.getInitializationPromise();\n },\n waitUntilReady: () => c.getReadyPromise(),\n identify: function (e, t, n) {\n if (T) return S.wrapPromiseCallback(Promise.resolve({}), n);\n if (U) return i.warn(ie.identifyDisabled()), S.wrapPromiseCallback(Promise.resolve(S.transformVersionedValuesToValues(P)), n);\n const r = D && j ? j.clearFlags() : Promise.resolve();\n return S.wrapPromiseCallback(r.then(() => A.processContext(e)).then(M).then(e => E.fetchFlagSettings(e, t).then(n => {\n const r = S.transformVersionedValuesToValues(n);\n return R.setContext(e), g = t, n ? _(n).then(() => r) : r;\n })).then(e => (x && z(), e)).catch(e => (a.maybeReportError(e), Promise.reject(e))), n);\n },\n getContext: function () {\n return R.getContext();\n },\n variation: function (e, t) {\n return q(e, t, !0, !1, !1, !0).value;\n },\n variationDetail: function (e, t) {\n return q(e, t, !0, !0, !1, !0);\n },\n track: function (e, t, n) {\n if (\"string\" != typeof e) return void a.maybeReportError(new s.LDInvalidEventKeyError(ie.unknownCustomEventKey(e)));\n void 0 !== n && \"number\" != typeof n && i.warn(ie.invalidMetricValue(typeof n)), r.customEventFilter && !r.customEventFilter(e) && i.warn(ie.unknownCustomEventKey(e));\n const o = R.getContext(),\n c = {\n kind: \"custom\",\n key: e,\n context: o,\n url: r.getCurrentUrl(),\n creationDate: new Date().getTime()\n };\n o && o.anonymous && (c.contextKind = o.anonymous ? \"anonymousUser\" : \"user\"), null != t && (c.data = t), null != n && (c.metricValue = n), F(c);\n },\n on: function (e, t, n) {\n G(e) ? (C = !0, O && B(), a.on(e, t, n)) : a.on(...arguments);\n },\n off: function (e) {\n if (a.off(...arguments), G(e)) {\n let e = !1;\n a.getEvents().forEach(t => {\n G(t) && a.getEventListenerCount(t) > 0 && (e = !0);\n }), e || (C = !1, x && void 0 === I && K());\n }\n },\n setStreaming: function (e) {\n const t = null === e ? void 0 : e;\n t !== I && (I = t, B());\n },\n flush: function (e) {\n return S.wrapPromiseCallback(d ? k.flush() : Promise.resolve(), e);\n },\n allFlags: function () {\n const e = {};\n if (!P) return e;\n for (const t in P) S.objectHasOwnProperty(P, t) && !P[t].deleted && (e[t] = q(t, null, !u.sendEventsOnlyForVariation, !1, !0, !1).value);\n return e;\n },\n close: function (e) {\n if (T) return S.wrapPromiseCallback(Promise.resolve(), e);\n const t = () => {\n T = !0, P = {};\n },\n n = Promise.resolve().then(() => {\n if (K(), w && w.stop(), d) return k.stop(), k.flush();\n }).then(t).catch(t);\n return S.wrapPromiseCallback(n, e);\n }\n };\n return {\n client: Y,\n options: u,\n emitter: a,\n ident: R,\n logger: i,\n requestor: E,\n start: function () {\n d && (w && w.start(), k.start());\n },\n enqueueEvent: F,\n getFlagsInternal: function () {\n return P;\n },\n getEnvironmentId: () => f,\n internalChangeEventName: yt\n };\n },\n commonBasicLogger: ft,\n errors: s,\n messages: ie,\n utils: S,\n getContextKeys: vt\n },\n bt = wt.initialize,\n kt = wt.errors,\n Et = wt.messages;\nfunction Dt(e, t, n) {\n return (t = function (e) {\n var t = function (e, t) {\n if (\"object\" != typeof e || !e) return e;\n var n = e[Symbol.toPrimitive];\n if (void 0 !== n) {\n var r = n.call(e, t || \"default\");\n if (\"object\" != typeof r) return r;\n throw new TypeError(\"@@toPrimitive must return a primitive value.\");\n }\n return (\"string\" === t ? String : Number)(e);\n }(e, \"string\");\n return \"symbol\" == typeof t ? t : t + \"\";\n }(t)) in e ? Object.defineProperty(e, t, {\n value: n,\n enumerable: !0,\n configurable: !0,\n writable: !0\n }) : e[t] = n, e;\n}\nfunction xt(e, t) {\n var n = Object.keys(e);\n if (Object.getOwnPropertySymbols) {\n var r = Object.getOwnPropertySymbols(e);\n t && (r = r.filter(function (t) {\n return Object.getOwnPropertyDescriptor(e, t).enumerable;\n })), n.push.apply(n, r);\n }\n return n;\n}\nfunction Ct(e) {\n for (var t = 1; t < arguments.length; t++) {\n var n = null != arguments[t] ? arguments[t] : {};\n t % 2 ? xt(Object(n), !0).forEach(function (t) {\n Dt(e, t, n[t]);\n }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(n)) : xt(Object(n)).forEach(function (t) {\n Object.defineProperty(e, t, Object.getOwnPropertyDescriptor(n, t));\n });\n }\n return e;\n}\nvar Pt = wt.commonBasicLogger;\nvar St = function (e) {\n return Pt(Ct({\n destination: console.log\n }, e));\n};\nvar It = {\n promise: Promise.resolve({\n status: 200,\n header: function () {\n return null;\n },\n body: null\n })\n};\nfunction Ot(e, t, n, r, o) {\n if (o && !function () {\n var e = window.navigator && window.navigator.userAgent;\n if (e) {\n var t = e.match(/Chrom(e|ium)\\/([0-9]+)\\./);\n if (t) return parseInt(t[2], 10) < 73;\n }\n return !0;\n }()) return It;\n var i = new window.XMLHttpRequest();\n for (var a in i.open(e, t, !o), n || {}) Object.prototype.hasOwnProperty.call(n, a) && i.setRequestHeader(a, n[a]);\n if (o) {\n try {\n i.send(r);\n } catch (e) {}\n return It;\n }\n var s,\n c = new Promise(function (e, t) {\n i.addEventListener(\"load\", function () {\n s || e({\n status: i.status,\n header: function (e) {\n return i.getResponseHeader(e);\n },\n body: i.responseText\n });\n }), i.addEventListener(\"error\", function () {\n s || t(new Error());\n }), i.send(r);\n });\n return {\n promise: c,\n cancel: function () {\n s = !0, i.abort();\n }\n };\n}\nvar Tt = e => {\n if (\"string\" != typeof e) throw new TypeError(\"Expected a string\");\n return e.replace(/[|\\\\{}()[\\]^$+*?.]/g, \"\\\\$&\").replace(/-/g, \"\\\\x2d\");\n};\nfunction Lt(e, t, n, r) {\n var o,\n i,\n a = ((\"substring\" === e.kind || \"regex\" === e.kind) && r.includes(\"/\") ? t : t.replace(r, \"\")).replace(n, \"\");\n switch (e.kind) {\n case \"exact\":\n i = t, o = new RegExp(\"^\" + Tt(e.url) + \"/?$\");\n break;\n case \"canonical\":\n i = a, o = new RegExp(\"^\" + Tt(e.url) + \"/?$\");\n break;\n case \"substring\":\n i = a, o = new RegExp(\".*\" + Tt(e.substring) + \".*$\");\n break;\n case \"regex\":\n i = a, o = new RegExp(e.pattern);\n break;\n default:\n return !1;\n }\n return o.test(i);\n}\nfunction Ut(e, t) {\n for (var n = {}, r = null, o = [], i = 0; i < e.length; i++) for (var a = e[i], s = a.urls || [], c = 0; c < s.length; c++) if (Lt(s[c], window.location.href, window.location.search, window.location.hash)) {\n \"pageview\" === a.kind ? t(\"pageview\", a) : (o.push(a), t(\"click_pageview\", a));\n break;\n }\n return o.length > 0 && (r = function (e) {\n for (var n = function (e, t) {\n for (var n = [], r = 0; r < t.length; r++) for (var o = e.target, i = t[r], a = i.selector, s = document.querySelectorAll(a); o && s.length > 0;) {\n for (var c = 0; c < s.length; c++) o === s[c] && n.push(i);\n o = o.parentNode;\n }\n return n;\n }(e, o), r = 0; r < n.length; r++) t(\"click\", n[r]);\n }, document.addEventListener(\"click\", r)), n.dispose = function () {\n document.removeEventListener(\"click\", r);\n }, n;\n}\nfunction Rt(e, t) {\n var n, r;\n function o() {\n r && r.dispose(), n && n.length && (r = Ut(n, i));\n }\n function i(t, n) {\n var r = e.ident.getContext(),\n o = {\n kind: t,\n key: n.key,\n data: null,\n url: window.location.href,\n creationDate: new Date().getTime(),\n context: r\n };\n return \"click\" === t && (o.selector = n.selector), e.enqueueEvent(o);\n }\n return e.requestor.fetchJSON(\"/sdk/goals/\" + e.getEnvironmentId()).then(function (e) {\n e && e.length > 0 && (r = Ut(n = e, i), function (e, t) {\n var n,\n r = window.location.href;\n function o() {\n (n = window.location.href) !== r && (r = n, t());\n }\n !function e(t, n) {\n t(), setTimeout(function () {\n e(t, n);\n }, n);\n }(o, e), window.history && window.history.pushState ? window.addEventListener(\"popstate\", o) : window.addEventListener(\"hashchange\", o);\n }(300, o)), t();\n }).catch(function (n) {\n e.emitter.maybeReportError(new kt.LDUnexpectedResponseError((n && n.message, n.message))), t();\n }), {};\n}\nvar At = \"goalsReady\",\n jt = {\n fetchGoals: {\n default: !0\n },\n hash: {\n type: \"string\"\n },\n eventProcessor: {\n type: \"object\"\n },\n eventUrlTransformer: {\n type: \"function\"\n },\n disableSyncEventPost: {\n default: !1\n }\n };\nfunction Ft(e, t) {\n var n = arguments.length > 2 && void 0 !== arguments[2] ? arguments[2] : {},\n r = function (e) {\n var t,\n n = {\n userAgentHeaderName: \"X-LaunchDarkly-User-Agent\",\n synchronousFlush: !1\n };\n if (window.XMLHttpRequest) {\n var r = e && e.disableSyncEventPost;\n n.httpRequest = function (e, t, o, i) {\n var a = n.synchronousFlush & !r;\n return n.synchronousFlush = !1, Ot(e, t, o, i, a);\n };\n }\n n.httpAllowsPost = function () {\n return void 0 === t && (t = !!window.XMLHttpRequest && \"withCredentials\" in new window.XMLHttpRequest()), t;\n }, n.httpFallbackPing = function (e) {\n new window.Image().src = e;\n };\n var o,\n i = e && e.eventUrlTransformer;\n n.getCurrentUrl = function () {\n return i ? i(window.location.href) : window.location.href;\n }, n.isDoNotTrack = function () {\n var e;\n return 1 === (e = window.navigator && void 0 !== window.navigator.doNotTrack ? window.navigator.doNotTrack : window.navigator && void 0 !== window.navigator.msDoNotTrack ? window.navigator.msDoNotTrack : window.doNotTrack) || !0 === e || \"1\" === e || \"yes\" === e;\n };\n try {\n window.localStorage && (n.localStorage = {\n get: function (e) {\n return new Promise(function (t) {\n t(window.localStorage.getItem(e));\n });\n },\n set: function (e, t) {\n return new Promise(function (n) {\n window.localStorage.setItem(e, t), n();\n });\n },\n clear: function (e) {\n return new Promise(function (t) {\n window.localStorage.removeItem(e), t();\n });\n }\n });\n } catch (e) {\n n.localStorage = null;\n }\n if (e && e.useReport && \"function\" == typeof window.EventSourcePolyfill && window.EventSourcePolyfill.supportedOptions && window.EventSourcePolyfill.supportedOptions.method ? (n.eventSourceAllowsReport = !0, o = window.EventSourcePolyfill) : (n.eventSourceAllowsReport = !1, o = window.EventSource), window.EventSource) {\n var a = 3e5;\n n.eventSourceFactory = function (e, t) {\n var n = Ct(Ct({}, {\n heartbeatTimeout: a,\n silentTimeout: a,\n skipDefaultHeaders: !0\n }), t);\n return new o(e, n);\n }, n.eventSourceIsActive = function (e) {\n return e.readyState === window.EventSource.OPEN || e.readyState === window.EventSource.CONNECTING;\n };\n }\n return n.userAgent = \"JSClient\", n.version = \"3.5.0\", n.diagnosticSdkData = {\n name: \"js-client-sdk\",\n version: \"3.5.0\"\n }, n.diagnosticPlatformData = {\n name: \"JS\"\n }, n.diagnosticUseCombinedEvent = !0, n;\n }(n),\n o = bt(e, t, n, r, jt),\n i = o.client,\n a = o.options,\n s = o.emitter,\n c = new Promise(function (e) {\n var t = s.on(At, function () {\n s.off(At, t), e();\n });\n });\n i.waitUntilGoalsReady = function () {\n return c;\n }, a.fetchGoals ? Rt(o, function () {\n return s.emit(At);\n }) : s.emit(At), \"complete\" !== document.readyState ? window.addEventListener(\"load\", o.start) : o.start();\n var u = function () {\n r.synchronousFlush = !0, i.flush().catch(function () {}), r.synchronousFlush = !1;\n };\n return document.addEventListener(\"visibilitychange\", function () {\n \"hidden\" === document.visibilityState && u();\n }), window.addEventListener(\"pagehide\", u), i;\n}\nvar Nt = St,\n $t = void 0,\n Vt = \"3.5.0\";\nvar Mt = {\n initialize: function (e, t) {\n var n = arguments.length > 2 && void 0 !== arguments[2] ? arguments[2] : {};\n return console && console.warn && console.warn(Et.deprecated(\"default export\", \"named LDClient export\")), Ft(e, t, n);\n },\n version: Vt\n};\nexport { Nt as basicLogger, $t as createConsoleLogger, Mt as default, Ft as initialize, Vt as version };\n","import { Injectable, OnDestroy } from '@angular/core';\n\nimport {\n CookieName,\n CookieService,\n LaunchDarklyContextProviderService,\n Logger,\n TrackingService,\n UserEvent,\n UserLoginEvent,\n UserLogoutEvent,\n UserService,\n} from '@frontend/vanilla/core';\nimport { LDClient, LDEvaluationDetail, LDFlagChangeset, LDFlagSet, LDOptions, initialize } from 'launchdarkly-js-client-sdk';\nimport { BehaviorSubject, Observable, ReplaySubject, Subscription, catchError, map, merge, of, switchMap, timeout } from 'rxjs';\nimport { first } from 'rxjs/operators';\n\nimport { LaunchDarklyConfig } from './launch-darkly.client-config';\n\n/**\n * @stable\n */\n@Injectable({\n providedIn: 'root',\n})\nexport class LaunchDarklyService implements OnDestroy {\n /**\n * LaunchDarkly SDK client object.\n *\n * Can be used directly to access all properties and methods provided by the SDK.\n *\n * For more information, see the [SDK Reference Guide](https://docs.launchdarkly.com/sdk/client-side/javascript).\n */\n client: LDClient;\n defaultOptions: LDOptions = {\n inspectors: [\n {\n type: 'flag-used',\n name: 'flag-used-inspector',\n method: (flagKey: string, flagDetail: LDEvaluationDetail) => this.trackFlagUsed(flagKey, flagDetail),\n },\n ],\n };\n private flags: LDFlagSet = {};\n private userAuthenticationSubscriber: Subscription;\n private _featureFlags = new BehaviorSubject({});\n private isLaunchDarklyConfigured = false;\n\n constructor(\n private userService: UserService,\n private contextProviderService: LaunchDarklyContextProviderService,\n private config: LaunchDarklyConfig,\n private trackingService: TrackingService,\n private cookieService: CookieService,\n private logger: Logger,\n ) {}\n\n private _clientInitialized = new ReplaySubject(1);\n private get clientInitialized(): Observable {\n return this._clientInitialized.pipe(first());\n }\n\n async initialize(clientId: string) {\n this.isLaunchDarklyConfigured = true;\n\n if (!this.client) {\n const options = Object.assign({}, this.defaultOptions, this.config.options);\n const context = await this.contextProviderService.getContext();\n this.client = initialize(clientId, context, options);\n\n this.client\n .waitForInitialization()\n .then(() => {\n this.fetchLDFlags();\n this._clientInitialized.next();\n this.contextProviderService.contextChanged.subscribe(() => {\n this.updateContext();\n });\n this.subscribeToUserAuthenticationEvents();\n })\n .catch((error: unknown) => {\n this.logger.error('[LaunchDarkly] - Error while initializing SDK.', error);\n });\n\n this.client.on('change', async (changedFlags: LDFlagChangeset) => {\n await this.updateFlag(changedFlags);\n this.logger.info('[LaunchDarkly] - Flags changed', changedFlags);\n });\n\n this.client.on('error', (error: any) => {\n this.logger.error('[LaunchDarkly] - General error.', error);\n });\n }\n }\n\n /**\n * Emits the current value for the specified Feature flag.\n *\n * // Usage (component example):\n * export class HeaderComponent implements OnInit\n * {\n * buttonsEnabled = false;\n * ngOnInit() {\n * this.flagsService.getFeatureFlagValue('HeaderButtonsEnabled').subscribe((value) => {\n * this.buttonsEnabled = value;\n * });\n * }\n * }\n *\n * // Usage (Markup)\n *