File

src/app/resources/explore-books/explore-books.page.ts

Implements

OnInit OnDestroy

Metadata

Index

Properties
Methods

Constructor

constructor(contentService: ContentService, modalCtrl: ModalController, zone: NgZone, commonUtilService: CommonUtilService, headerService: AppHeaderService, appGlobalService: AppGlobalService, translate: TranslateService, telemetryGeneratorService: TelemetryGeneratorService, platform: Platform, router: Router, location: Location, navService: NavigationService)
Parameters :
Name Type Optional
contentService ContentService No
modalCtrl ModalController No
zone NgZone No
commonUtilService CommonUtilService No
headerService AppHeaderService No
appGlobalService AppGlobalService No
translate TranslateService No
telemetryGeneratorService TelemetryGeneratorService No
platform Platform No
router Router No
location Location No
navService NavigationService No

Methods

classClick
classClick(index)
Parameters :
Name Optional
index No
Returns : void
classClickedForTelemetry
classClickedForTelemetry(currentClass: string)
Parameters :
Name Type Optional
currentClass string No
Returns : void
fetchingBoardMediumList
fetchingBoardMediumList(facetFilters)
Parameters :
Name Optional
facetFilters No
Returns : any
generateMimeTypeClickedTelemetry
generateMimeTypeClickedTelemetry(mimeTypeName)
Parameters :
Name Optional
mimeTypeName No
Returns : void
handleBackButton
handleBackButton()
Returns : void
handleHeaderEvents
handleHeaderEvents($event)
Parameters :
Name Optional
$event No
Returns : void
ionViewWillEnter
ionViewWillEnter()
Returns : void
ionViewWillLeave
ionViewWillLeave()
Returns : void
ngOnDestroy
ngOnDestroy()
Returns : void
Async ngOnInit
ngOnInit()
Returns : any
onMimeTypeClicked
onMimeTypeClicked(mimeType, index)
Parameters :
Name Optional
mimeType No
index No
Returns : void
Private onSearchFormChange
onSearchFormChange()
Returns : Observable<undefined>
openContent
openContent(content, index)
Parameters :
Name Optional
content No
index No
Returns : void
Async openSortOptionsModal
openSortOptionsModal()
Returns : any
Private populateCData
populateCData(profileAtributes, correlationType)
Parameters :
Name Optional
profileAtributes No
correlationType No
Returns : Array<CorrelationData>
subjectClicked
subjectClicked(index, currentSubject: string)
Parameters :
Name Type Optional
index No
currentSubject string No
Returns : void
union
union(arrA: literal type[], arrB: literal type[])
Parameters :
Name Type Optional
arrA literal type[] No
arrB literal type[] No
Returns : literal type[]

Properties

boardList
Type : Array<FilterValue>
categoryGradeLevels
Type : Array<any>
checkedSortByButton
Default value : true
Public commonUtilService
Type : CommonUtilService
contentSearchResult
Type : Array<any>
Default value : []
corRelationList
Type : Array<CorrelationData>
currentSelectedClass
Type : any
Public filteredItemsQueryList
Type : QueryList<any>
Decorators :
@ViewChildren('filteredItems')
headerObservable
Type : any
hideSortByButton
Default value : () => {...}
LibraryCardTypes
Default value : LibraryCardTypes
mediumList
Type : Array<FilterValue>
mimeTypes
Type : []
Default value : [ { name: 'ALL', selected: true, value: [], iconNormal: '', iconActive: '' }, { name: 'TEXTBOOK', value: [MimeType.COLLECTION], iconNormal: './assets/imgs/book.svg', iconActive: './assets/imgs/book-active.svg' }, { name: 'VIDEOS', value: ['video/mp4', 'video/x-youtube', 'video/webm'], iconNormal: './assets/imgs/play.svg', iconActive: './assets/imgs/play-active.svg' }, { name: 'DOCS', value: ['application/pdf', 'application/epub'], iconNormal: './assets/imgs/doc.svg', iconActive: './assets/imgs/doc-active.svg' }, { name: 'INTERACTION', value: ['application/vnd.ekstep.ecml-archive', 'application/vnd.ekstep.h5p-archive', 'application/vnd.ekstep.html-archive'], iconNormal: './assets/imgs/touch.svg', iconActive: './assets/imgs/touch-active.svg' } ]
Public modalCtrl
Type : ModalController
Public pageId
Type : string
Default value : 'ExploreBooksPage'
primaryCategories
Type : Array<string>
Default value : []
searchForm
Type : FormGroup
Default value : new FormGroup({ grade: new FormControl([]), subject: new FormControl([]), board: new FormControl([]), medium: new FormControl([]), query: new FormControl('', { updateOn: 'submit' }), mimeType: new FormControl([]) })
Optional searchFormSubscription
Type : Subscription
Public searchInputRef
Type : ElementRef
Decorators :
@ViewChild('searchInput', {static: false})
selectedGrade
Type : string
selectedMedium
Type : string
selectedPrimartCategory
Type : string
Default value : 'all'
showLoader
Default value : false
showSortByButton
Default value : () => {...}
subjects
Type : any
unregisterBackButton
Type : Subscription
import {
  Component, ElementRef,
  Inject, NgZone, OnDestroy, QueryList,
  ViewChild, ViewChildren, OnInit
} from '@angular/core';
import { Platform, ModalController } from '@ionic/angular';
import { MimeType, Search, ExploreConstants } from 'app/app.constant';
import { Map } from 'app/telemetryutil';
import {
  Environment,
  ImpressionSubtype,
  ImpressionType,
  InteractSubtype,
  InteractType,
  PageId,
  CorReleationDataType
} from 'services/telemetry-constants';
import {
  ContentSearchCriteria,
  ContentSearchFilter,
  ContentSearchResult,
  ContentService,
  CorrelationData,
  FilterValue,
  SearchType
} from 'sunbird-sdk';
import { LibraryCardTypes } from '@project-sunbird/common-consumption';
import { AppGlobalService, AppHeaderService, CommonUtilService, TelemetryGeneratorService } from '@app/services';
import { animate, group, state, style, transition, trigger } from '@angular/animations';
import { TranslateService } from '@ngx-translate/core';
import { FormControl, FormGroup } from '@angular/forms';
import { Observable, Subscription, of } from 'rxjs';
import { Router, NavigationExtras } from '@angular/router';
import { Location } from '@angular/common';
import { ExploreBooksSortComponent } from '../explore-books-sort/explore-books-sort.component';
import { tap, switchMap, catchError, mapTo, debounceTime } from 'rxjs/operators';
import { NavigationService } from '@app/services/navigation-handler.service';
import { CsPrimaryCategory } from '@project-sunbird/client-services/services/content';

@Component({
  selector: 'app-explore-books',
  templateUrl: './explore-books.page.html',
  styleUrls: ['./explore-books.page.scss'],
  animations: [
    trigger('appear', [
      state('true', style({
        left: '{{left_indent}}',
      }), { params: { left_indent: 0 } }), // default parameters values required

      transition('* => classAnimate', [
        style({ width: 5, opacity: 0 }),
        group([
          animate('0.3s 0.2s ease', style({
            transform: 'translateX(0) scale(1.2)', width: '*',
          })),
          animate('0.2s ease', style({
            opacity: 1
          }))
        ])
      ]),
    ]),
    trigger('ScrollHorizontal', [
      state('true', style({
        left: '{{left_indent}}',
        transform: 'translateX(-100px)',
      }), { params: { left_indent: 0 } }), // default parameters values required

      transition('* => classAnimate', [
        // style({ width: 5, transform: 'translateX(-100px)', opacity: 0 }),
        group([
          animate('0.3s 0.5s ease', style({
            transform: 'translateX(-100px)'
          })),
          animate('0.3s ease', style({
            opacity: 1
          }))
        ])
      ]),
    ])
  ]

})
export class ExploreBooksPage implements OnInit, OnDestroy {
  public pageId = 'ExploreBooksPage';

  @ViewChild('searchInput', { static: false }) public searchInputRef: ElementRef;
  @ViewChildren('filteredItems') public filteredItemsQueryList: QueryList<any>;

  LibraryCardTypes = LibraryCardTypes;
  categoryGradeLevels: Array<any>;
  subjects: any;
  mimeTypes = [
    { name: 'ALL', selected: true, value: [], iconNormal: '', iconActive: '' },
    { name: 'TEXTBOOK', value: [MimeType.COLLECTION], iconNormal: './assets/imgs/book.svg', iconActive: './assets/imgs/book-active.svg' },
    {
      name: 'VIDEOS',
      value: ['video/mp4', 'video/x-youtube', 'video/webm'],
      iconNormal: './assets/imgs/play.svg',
      iconActive: './assets/imgs/play-active.svg'
    },
    {
      name: 'DOCS',
      value: ['application/pdf', 'application/epub'],
      iconNormal: './assets/imgs/doc.svg',
      iconActive: './assets/imgs/doc-active.svg'
    },
    {
      name: 'INTERACTION',
      value: ['application/vnd.ekstep.ecml-archive', 'application/vnd.ekstep.h5p-archive', 'application/vnd.ekstep.html-archive'],
      iconNormal: './assets/imgs/touch.svg', iconActive: './assets/imgs/touch-active.svg'
    }
  ];
  headerObservable: any;
  unregisterBackButton: Subscription;
  primaryCategories: Array<string> = [];
  contentSearchResult: Array<any> = [];
  showLoader = false;
  searchFormSubscription?: Subscription;
  selectedGrade: string;
  selectedMedium: string;
  selectedPrimartCategory = 'all';

  searchForm: FormGroup = new FormGroup({
    grade: new FormControl([]),
    subject: new FormControl([]),
    board: new FormControl([]),
    medium: new FormControl([]),
    query: new FormControl('', { updateOn: 'submit' }),
    mimeType: new FormControl([])
  });
  boardList: Array<FilterValue>;
  mediumList: Array<FilterValue>;
  corRelationList: Array<CorrelationData>;
  checkedSortByButton = true;
  currentSelectedClass: any;

  constructor(
    @Inject('CONTENT_SERVICE') private contentService: ContentService,
    public modalCtrl: ModalController,
    private zone: NgZone,
    public commonUtilService: CommonUtilService,
    private headerService: AppHeaderService,
    private appGlobalService: AppGlobalService,
    private translate: TranslateService,
    private telemetryGeneratorService: TelemetryGeneratorService,
    private platform: Platform,
    private router: Router,
    private location: Location,
    private navService: NavigationService
  ) {
    const extras = this.router.getCurrentNavigation().extras.state;
    if (extras) {
      this.selectedGrade = extras.selectedGrade;
      this.selectedMedium = extras.selectedMedium;
      this.categoryGradeLevels = extras.categoryGradeLevels;
      this.subjects = extras.subjects;
      this.subjects.unshift({ name: this.commonUtilService.translateMessage('ALL'), selected: true });
      this.primaryCategories = extras.primaryCategories;

      this.corRelationList = [
        ... this.populateCData(this.selectedGrade, CorReleationDataType.CLASS),
        ... this.populateCData(this.selectedMedium, CorReleationDataType.MEDIUM)
      ];

      const index = this.categoryGradeLevels.findIndex((grade) => grade.name === this.searchForm.value['grade'][0]);
      this.classClick(index);
    }
  }

  async ngOnInit() {
    this.telemetryGeneratorService.generateImpressionTelemetry(
      ImpressionType.VIEW,
      ImpressionSubtype.EXPLORE_MORE_CONTENT,
      PageId.EXPLORE_MORE_CONTENT,
      Environment.HOME,
      undefined,
      undefined,
      undefined,
      undefined,
      this.corRelationList);
  }

  ionViewWillEnter() {
    this.searchFormSubscription = this.onSearchFormChange()
      .subscribe(() => { });

    this.searchForm.patchValue({
      grade: this.selectedGrade,
      medium: this.selectedMedium
    });
    this.headerObservable = this.headerService.headerEventEmitted$.subscribe(eventName => {
      this.handleHeaderEvents(eventName);
    });
    this.handleBackButton();
    this.headerService.showHeaderWithBackButton();
    window.addEventListener('keyboardDidHide', this.showSortByButton);
    window.addEventListener('keyboardWillShow', this.hideSortByButton);
  }

  ngOnDestroy(): void {
    if (this.searchFormSubscription) {
      this.searchFormSubscription.unsubscribe();
    }
  }

  private populateCData(profileAtributes, correlationType): Array<CorrelationData> {
    const correlationList: Array<CorrelationData> = [];
    if (profileAtributes) {
      profileAtributes.forEach((value) => {
        correlationList.push({
          id: value || '',
          type: correlationType
        });
      });
    }
    return correlationList;
  }

  handleBackButton() {
    this.unregisterBackButton = this.platform.backButton.subscribeWithPriority(10, () => {
      this.telemetryGeneratorService.generateInteractTelemetry(
        InteractType.TOUCH,
        InteractSubtype.DEVICE_BACK_CLICKED,
        Environment.HOME,
        PageId.EXPLORE_MORE_CONTENT,
      );
      this.unregisterBackButton.unsubscribe();
      this.location.back();
    });
  }

  handleHeaderEvents($event) {
    if ($event.name === 'back') {
      this.telemetryGeneratorService.generateBackClickedTelemetry(
        PageId.EXPLORE_MORE_CONTENT, Environment.HOME, true);
      this.location.back();
    }
  }

  union(arrA: { name: string }[], arrB: { name: string }[]): { name: string }[] {
    return [
      ...arrA, ...arrB.filter((bItem) => !arrA.find((aItem) => bItem.name === aItem.name))
    ];
  }

  private onSearchFormChange(): Observable<undefined> {
    const value = new Map();
    return this.searchForm.valueChanges.pipe(
      tap(() => { }),
      debounceTime(200),
      switchMap(() => {
        const searchCriteria: ContentSearchCriteria = {
          ...this.searchForm.getRawValue(),
          query: this.searchInputRef.nativeElement['value'],
          searchType: SearchType.SEARCH,
          primaryCategories: this.selectedPrimartCategory === CsPrimaryCategory.DIGITAL_TEXTBOOK ?
            [CsPrimaryCategory.DIGITAL_TEXTBOOK] : this.primaryCategories,
          facets: Search.FACETS,
          audience: [],
          mode: 'soft',
          languageCode: this.translate.currentLang,
          fields: ExploreConstants.REQUIRED_FIELDS
        };
        const values = new Map();
        values['searchCriteria'] = searchCriteria;
        this.telemetryGeneratorService.generateInteractTelemetry(
          InteractType.OTHER,
          InteractSubtype.SEARCH_INITIATED,
          Environment.HOME,
          PageId.EXPLORE_MORE_CONTENT,
          undefined,
          values);
        this.showLoader = true;
        this.contentSearchResult = [];
        if (this.currentSelectedClass) {
          searchCriteria.grade[0] = this.currentSelectedClass;
        }
        return this.contentService.searchContent(searchCriteria).pipe(
          catchError(() => {
            this.zone.run(() => {
              this.showLoader = false;
              if (!this.commonUtilService.networkInfo.isNetworkAvailable) {
                this.commonUtilService.showToast('ERROR_OFFLINE_MODE');
              }
            });

            return of(undefined);
          })
        );
      }),
      tap(() => {
        (window as any).Keyboard.hide();
      }),
      tap((result?: ContentSearchResult) => {
        this.zone.run(() => {
          if (result) {
            let facetFilters: Array<ContentSearchFilter>;
            this.showLoader = false;
            facetFilters = result.filterCriteria.facetFilters;

            this.fetchingBoardMediumList(facetFilters);
            this.showLoader = false;
            const gradeLevel = result.filterCriteria.facetFilters.find((f) => f.name === 'se_gradeLevels').values;
            gradeLevel.sort((a, b) => b.count - a.count);
            this.categoryGradeLevels = this.union(this.categoryGradeLevels, gradeLevel);
            const subjects = result.filterCriteria.facetFilters.find((f) => f.name === 'subject').values;
            subjects.sort((a, b) => b.count - a.count);
            this.subjects = this.union(this.subjects, subjects);
            this.contentSearchResult = result.contentDataList || [];
            value['searchResult'] = this.contentSearchResult.length;
          }
        });
      }),
      tap(() => {
        this.telemetryGeneratorService.generateInteractTelemetry(
          InteractType.OTHER,
          InteractSubtype.SEARCH_COMPLETED,
          Environment.HOME,
          PageId.EXPLORE_MORE_CONTENT,
          undefined,
          value);
      }),
      mapTo(undefined)
    );
  }

  openContent(content, index) {
    const identifier = content.contentId || content.identifier;
    const value = new Map();
    value['identifier'] = identifier;
    const corRelationList = [{
      id: 'explore',
      type: CorReleationDataType.SOURCE
    }];

    const navigationExtras: NavigationExtras = {
      state: {
        content,
        corRelation: corRelationList
      }
    };

    this.navService.navigateToDetailPage(content, navigationExtras.state);

    this.telemetryGeneratorService.generateInteractTelemetry(
      InteractType.TOUCH,
      InteractSubtype.CONTENT_CLICKED,
      Environment.HOME,
      PageId.EXPLORE_MORE_CONTENT,
      undefined,
      value,
      undefined,
      corRelationList);

  }

  ionViewWillLeave() {
    if (this.headerObservable) {
      this.headerObservable.unsubscribe();
    }

    if (this.unregisterBackButton) {
      this.unregisterBackButton.unsubscribe();
    }

    if (this.searchFormSubscription) {
      this.searchFormSubscription.unsubscribe();
    }
    window.removeEventListener('keyboardDidHide', this.showSortByButton);
    window.removeEventListener('keyboardWillShow', this.hideSortByButton);
  }

  async openSortOptionsModal() {
    const sortOptionsModal = await this.modalCtrl.create({
      component: ExploreBooksSortComponent,
      componentProps:
      {
        searchForm: this.searchForm,
        boardList: this.boardList,
        mediumList: this.mediumList
      }
    });
    await sortOptionsModal.present();
    const { data } = await sortOptionsModal.onDidDismiss();
    if (data && data.values) {
      this.searchForm.patchValue({
        board: data.values.board || [],
        medium: data.values.medium || []
      });
      this.telemetryGeneratorService.generateInteractTelemetry(
        InteractType.TOUCH,
        InteractSubtype.SORT_BY_FILTER_SET,
        Environment.HOME,
        PageId.EXPLORE_MORE_CONTENT,
        undefined,
        undefined,
        undefined,
        [
          ...this.populateCData(data.values.board, CorReleationDataType.BOARD),
          ...this.populateCData(data.values.medium, CorReleationDataType.MEDIUM),
        ]);
    }
    if (!data) {
      this.telemetryGeneratorService.generateBackClickedTelemetry(
        PageId.EXPLORE_MORE_CONTENT,
        Environment.HOME,
        false);
    }
    this.telemetryGeneratorService.generateInteractTelemetry(
      InteractType.TOUCH,
      InteractSubtype.SORT_BY_CLICKED,
      Environment.HOME,
      PageId.EXPLORE_MORE_CONTENT
    );
  }

  onMimeTypeClicked(mimeType, index) {
    this.mimeTypes.forEach((value) => {
      value.selected = false;
    });

    this.mimeTypes[index].selected = true;

    const idx = this.mimeTypes.findIndex((value) => value.name === 'TEXTBOOK');

    this.generateMimeTypeClickedTelemetry(mimeType.name);

    if (idx === index) {
      this.selectedPrimartCategory = CsPrimaryCategory.DIGITAL_TEXTBOOK;
    } else {
      this.selectedPrimartCategory = 'all';
    }
  }

  fetchingBoardMediumList(facetFilters) {
    return facetFilters.filter(value => {
      if (value.name === 'se_boards') {
        this.boardList = value.values;
      }

      if (value.name === 'se_mediums') {
        this.mediumList = value.values;
      }
    });
  }

  classClick(index) {
    const el: HTMLElement | null = document.getElementById('gradeLevel' + index);
    setTimeout(() => {
      if (el) {
        el.scrollIntoView({ behavior: 'smooth', block: 'end', inline: 'start' });
      }
    }, 0);
  }

  classClickedForTelemetry(currentClass: string) {
    this.currentSelectedClass = currentClass;
    this.telemetryGeneratorService.generateInteractTelemetry(
      InteractType.TOUCH,
      InteractSubtype.CLASS_CLICKED,
      Environment.HOME,
      PageId.EXPLORE_MORE_CONTENT,
      undefined,
      undefined,
      undefined,
      [{
        id: currentClass,
        type: CorReleationDataType.CLASS
      }]
    );
  }

  subjectClicked(index, currentSubject: string) {
    this.telemetryGeneratorService.generateInteractTelemetry(
      InteractType.TOUCH,
      InteractSubtype.SUBJECT_CLICKED,
      Environment.HOME,
      PageId.EXPLORE_MORE_CONTENT,
      undefined,
      undefined,
      undefined,
      [{
        id: currentSubject,
        type: CorReleationDataType.SUBJECT
      }]);
  }

  generateMimeTypeClickedTelemetry(mimeTypeName) {
    this.telemetryGeneratorService.generateInteractTelemetry(
      InteractType.TOUCH,
      InteractSubtype.FILTER_CLICKED,
      Environment.HOME,
      PageId.EXPLORE_MORE_CONTENT,
      undefined,
      undefined,
      undefined,
      [{
        id: mimeTypeName,
        type: CorReleationDataType.MIMETYPE
      }]);
  }

  hideSortByButton = () => {
    this.zone.run(() => {
      this.checkedSortByButton = false;
    });
  }

  showSortByButton = () => {
    this.zone.run(() => {
      this.checkedSortByButton = true;
    });
  }
}
<ion-content class="ion-no-padding">
  <form [formGroup]="searchForm">
    <div class="sb-slider-pills-container">
      <div class="sb-pills-container sb-grade-pills-container" id="gradeScroll">
        <div class="pill" *ngFor="let grade of categoryGradeLevels; let i = index; let first = first"
          [class.active]="searchForm.get('grade').value[0] === grade.name" attr.id="gradeLevel{{i}}"
          [attr.aria-label]="(searchForm.get('grade').value[0] === grade.name) ? grade?.name + ', selected' : grade?.name"
          role="button"
          (click)="searchForm.get('grade').patchValue([grade.name]); classClick(i); classClickedForTelemetry(grade.name)">
          {{grade?.name}}</div>
      </div>
    </div>
    <div class="sb-slider-pills-container">
      <div class="sb-pills-container sb-medium-pills-container">
        <div class="pill" *ngFor="let subject of subjects; let i = index;"
          [class.active]="searchForm.get('subject').value[0] ? searchForm.get('subject').value[0] === subject.name : subject.name === 'All'"
          [attr.aria-label]="(searchForm.get('subject').value[0] ? searchForm.get('subject').value[0] === subject.name : subject.name === 'All') ? subject?.name + ', selected' : subject?.name "
          role="button"
          [@appear] (click)="searchForm.get('subject').patchValue([subject.name]); subjectClicked(i, subject.name)">
          {{subject?.name}}
        </div>
      </div>
    </div>
    <div class="sb-slider-pills-container">
      <div class="sb-pills-container sb-grade-pills-container">
        <div class="pill" *ngFor="let mimeType of mimeTypes; let i = index; let first = first"
          [class.active]="mimeType?.selected"
          [attr.aria-label]="mimeType?.selected ? mimeType?.name + ', selected' : mimeType?.name"
          role="button"
          (click)="searchForm.get('mimeType').patchValue(mimeType.value); onMimeTypeClicked(mimeType, i)">
          <span class="img-align" *ngIf="mimeType?.iconNormal && mimeType?.selected">
            <img class="filter-icon" src="{{mimeType?.iconActive}}" alt="filter">
          </span>
          <span class="img-align" *ngIf="mimeType?.iconNormal && !mimeType?.selected">
            <img class="filter-icon" src="{{mimeType?.iconNormal}}" alt="filter">
          </span>
          <span class="allign-middle">{{mimeType?.name | translate}}</span>
        </div>
      </div>
    </div>

    <div class="search-bar">
      <input type="search" (focus)="hideSortByButton()" (focusout)="showSortByButton()" #searchInput
        placeholder="{{'SEARCH_PLACEHOLDER' | translate}}" [formControl]="searchForm.get('query')" />
      <button type="submit" class="btn-link sb-btn-link">
        <ion-icon name="search"></ion-icon>
      </button>
    </div>
  </form>

  <div class="explore-results-section">
  <div class="empty-search-result ion-text-center" *ngIf="!showLoader && !contentSearchResult.length" padding-top>
    {{ 'EMPTY_SEARCH_RESULTS' | translate }}
  </div>

  <div class="spinner-container" *ngIf="showLoader">
    <ion-spinner class="loader ion-padding"></ion-spinner>
  </div>
  <ng-container *ngFor="let content of contentSearchResult; let i = index">
    <div class="sb-card-container sb-card-textbook-container">
      <sb-library-card [content]="content" [type]="LibraryCardTypes.MOBILE_TEXTBOOK"
        (click)="openContent(content, i)" [cardImg]="commonUtilService.getContentImg(content)">
      </sb-library-card>
    </div>
  </ng-container>

  <div class="mt-32 mb-32 ion-text-center ion-padding" *ngIf="!commonUtilService?.networkInfo?.isNetworkAvailable">
    <img src="assets/imgs/outline-cloud_off.svg" alt="offline" />
    <ion-text>
      <h6 class="offline-header">
        <strong>{{ 'NO_INTERNET_TITLE' | translate }}</strong>
      </h6>
    </ion-text>
    <p class="offline-content">{{ 'OFFLINE_WARNING_ETBUI' | translate }}</p>
  </div>
  </div>
</ion-content>
<ion-button expand="block" class="sort-by" *ngIf="!showLoader && contentSearchResult.length && checkedSortByButton"
  (click)="openSortOptionsModal()"> {{'SORT_BY' | translate}}</ion-button>

./explore-books.page.scss

@import "src/assets/styles/variables";
@import "src/assets/styles/_custom-mixins";
@import "src/assets/styles/fonts";
@import "src/assets/styles/_variables.scss";
:host {
  .scroll-content {
    padding-bottom: 30% !important;
  }
  .img-align {
    margin-top: 5px;
    display: inline-block;
    vertical-align: middle;
    width: 0.938rem;
    margin-right: 8px;
  }

  .allign-middle {
    display: inline-block;
    vertical-align: middle;
  }

  .filter-icon {
    height: 0.938rem;
    width: 0.938rem;
  }

  .search-bar {
    display: flex;
    align-items: center;
    justify-content: stretch;

    position: relative;
    padding: 8px 8px;
    margin: 1rem 1rem;

    border: 1px solid map-get($colors, light_blue_80);
    border-radius: 4px;
    background-color: map-get($colors, white);

    input {
      border: none;
      flex-grow: 1;
    }

    button {
      flex-shrink: 0;

      ion-icon {
        color: $gray-300;
      }
    }
  }

  .sb-textbook-container {
    margin: 0 !important;
    padding: 0 !important;

    &:last-child {
      margin-bottom: 0 !important;
    }
  }

  .sb-card-container {
    margin: 0 !important;
    padding: 0 !important;
  }

  .sb-card-textbook-container {
    margin: 0 !important;
    padding: 0 !important;
  }
  .bottom-button {
    padding: 1em 2em !important;
  }
  .sort-by {
    border-radius: 4px;
    --background: #{$blue};
    padding: 0 2rem 5px 2rem !important;
    margin-bottom: 0.2rem;
    height: 2.813rem;
  }
  .spinner-container {
    width: 100%;
    height: 100%;
    display: block;

    .loader {
      display: block;
      margin: auto;
      top: 23%;
    }
  }


}
Legend
Html element
Component
Html element with directive

results matching ""

    No results matching ""