File

src/app/pages/research-page/research-page.component.ts

Description

Research page with filtering, sorting, and dual view modes. Displays research outputs with multi-faceted filtering and query parameter sync.

Metadata

Index

Properties
Methods
Inputs

Constructor

constructor()

Initializes store with data and registers sidebar

Inputs

events
Type : ResearchData
Required :  true

Events research data

eventTypes
Type : ResearchTypesData
Required :  true

Event type definitions

funding
Type : ResearchData
Required :  true

Funding research data

fundingTypes
Type : ResearchTypesData
Required :  true

Funding type definitions

news
Type : ResearchData
Required :  true

News research data

people
Type : PeopleData
Required :  true

People data for filtering and display

publications
Type : ResearchData
Required :  true

Publications research data

publicationTypes
Type : ResearchTypesData
Required :  true

Publication type definitions

tags
Type : TagsData
Required :  true

Tags data from resolver

visualizations
Type : ResearchData
Required :  true

Visualizations research data

Methods

getTagItems
getTagItems(ids: TagId[])

Gets tag items from array of tag ids using the store's tags map

Parameters :
Name Type Optional Description
ids TagId[] No

tag ids

Returns : TagItem[]

tag items

Properties

Protected Readonly getImageUrl
Type : unknown
Default value : getImageUrl

Utility to get image URL for a research item

Protected Readonly sidebarStore
Type : unknown
Default value : inject(SidebarStore)

Sidebar store for managing sidebar visibility

Protected Readonly store
Type : unknown
Default value : inject(ResearchStore)

Research store for state management

import { ChangeDetectionStrategy, Component, computed, effect, inject, input, viewChild } from '@angular/core';
import { MatDivider } from '@angular/material/divider';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatSelectModule } from '@angular/material/select';
import { MatSidenav, MatSidenavModule } from '@angular/material/sidenav';
import { HraCommonModule } from '@hra-ui/common';
import { ButtonsModule } from '@hra-ui/design-system/buttons';
import { CardsModule } from '@hra-ui/design-system/cards';
import { TagItem } from '@hra-ui/design-system/cards/gallery-card';
import { ListViewComponent } from '@hra-ui/design-system/content-templates/list-view';
import { SectionLinkComponent } from '@hra-ui/design-system/content-templates/section-link';
import { FilterMenuComponent } from '@hra-ui/design-system/filter-menu';
import { GalleryGridComponent, GalleryGridItemDirective } from '@hra-ui/design-system/gallery-grid';
import { IconsModule } from '@hra-ui/design-system/icons';
import { EndOfResultsIndicatorComponent } from '@hra-ui/design-system/indicators/end-of-results';
import { NoResultsIndicatorComponent } from '@hra-ui/design-system/indicators/no-results-indicator';
import { ScrollingModule } from '@hra-ui/design-system/scrolling';
import { SearchFilterComponent } from '@hra-ui/design-system/search-filter';
import { FooterComponent } from '../../components/footer/footer.component';
import { PeopleData } from '../../schemas/people.schema';
import { ResearchTypesData } from '../../schemas/research-type.schema';
import { ResearchData } from '../../schemas/research.schema';
import { TagId, TagsData } from '../../schemas/tags.schema';
import { SidebarStore } from '../../state/sidebar/sidebar.store';
import { getImageUrl } from '../../utils/research-item-images';
import { ResearchStore } from './state/research.store';

/**
 * Research page with filtering, sorting, and dual view modes.
 * Displays research outputs with multi-faceted filtering and query parameter sync.
 */
@Component({
  selector: 'cns-research-page',
  imports: [
    HraCommonModule,
    ButtonsModule,
    CardsModule,
    EndOfResultsIndicatorComponent,
    FilterMenuComponent,
    FooterComponent,
    GalleryGridComponent,
    GalleryGridItemDirective,
    IconsModule,
    ListViewComponent,
    MatDivider,
    MatFormFieldModule,
    MatSelectModule,
    MatSidenavModule,
    NoResultsIndicatorComponent,
    SearchFilterComponent,
    SectionLinkComponent,
    ScrollingModule,
  ],
  templateUrl: './research-page.component.html',
  styleUrl: './research-page.component.scss',
  providers: [ResearchStore],
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ResearchPageComponent {
  /** News research data */
  readonly news = input.required<ResearchData>();
  /** Publications research data */
  readonly publications = input.required<ResearchData>();
  /** Events research data */
  readonly events = input.required<ResearchData>();
  /** Funding research data */
  readonly funding = input.required<ResearchData>();
  /** Visualizations research data */
  readonly visualizations = input.required<ResearchData>();
  /** People data for filtering and display */
  readonly people = input.required<PeopleData>();
  /** Publication type definitions */
  readonly publicationTypes = input.required<ResearchTypesData>();
  /** Event type definitions */
  readonly eventTypes = input.required<ResearchTypesData>();
  /** Funding type definitions */
  readonly fundingTypes = input.required<ResearchTypesData>();

  /** Tags data from resolver */
  readonly tags = input.required<TagsData>();

  /** Research store for state management */
  protected readonly store = inject(ResearchStore);
  /** Sidebar store for managing sidebar visibility */
  protected readonly sidebarStore = inject(SidebarStore);

  /** Sidebar component reference */
  private readonly sidebar = viewChild.required(MatSidenav);

  /** Combined research items from news, publications, events, funding, and visualizations */
  private readonly researchItems = computed(() => [
    ...this.news(),
    ...this.publications(),
    ...this.events(),
    ...this.funding(),
    ...this.visualizations(),
  ]);

  /** Utility to get image URL for a research item */
  protected readonly getImageUrl = getImageUrl;

  /** Initializes store with data and registers sidebar */
  constructor() {
    this.store.setResearchItems(this.researchItems);
    this.store.setPeopleItems(this.people);
    this.store.setPublicationTypes(this.publicationTypes);
    this.store.setEventTypes(this.eventTypes);
    this.store.setFundingTypes(this.fundingTypes);
    this.store.setTags(this.tags);

    effect((onCleanup) => {
      this.sidebarStore.setSidebar(this.sidebar());
      onCleanup(() => this.sidebarStore.clearSidebar());
    });
  }

  /**
   * Gets tag items from array of tag ids using the store's tags map
   * @param ids tag ids
   * @returns tag items
   */
  getTagItems(ids: TagId[]): TagItem[] {
    return ids.map((id) => {
      const tag = this.store.tagsMap().get(id);
      return tag ? { slug: id, name: tag.name, description: tag.description } : { slug: id, name: id, description: '' };
    });
  }
}
<mat-sidenav-container class="container">
  <mat-sidenav
    class="sidebar"
    aria-label="Filter research"
    [mode]="sidebarStore.mode()"
    [opened]="sidebarStore.isOpen()"
    [disableClose]="sidebarStore.mode() === 'side'"
    (closedStart)="sidebarStore.close()"
  >
    <hra-filter-menu
      tagline="Research at the Cyberinfrastructure for Network Science Center"
      [filters]="store.filters()"
      [counts]="store.counts()"
      (filtersChange)="store.updateFilters($event)"
    >
      <mat-form-field subscriptSizing="dynamic">
        <mat-icon class="select-icon" matPrefix>{{ store.view() === 'gallery' ? 'cards' : 'list' }}</mat-icon>
        <mat-label>View as</mat-label>
        <mat-select [value]="store.view()" (valueChange)="store.setView($event)">
          <mat-option value="gallery">
            <mat-icon class="select-icon" fontIcon="cards" />
            Gallery
          </mat-option>
          <mat-option value="list">
            <mat-icon class="select-icon" fontIcon="list" />
            List
          </mat-option>
        </mat-select>
      </mat-form-field>

      <mat-form-field subscriptSizing="dynamic">
        <mat-icon class="select-icon" matPrefix>sort</mat-icon>
        <mat-label>Sort by</mat-label>
        <mat-select [value]="store.sortBy()" (valueChange)="store.setSortBy($event)">
          <mat-option value="nameAsc">Ascending (A-Z) by title</mat-option>
          <mat-option value="nameDesc">Descending (Z-A) by title</mat-option>
          <mat-option value="newest">Newest</mat-option>
          <mat-option value="oldest">Oldest</mat-option>
        </mat-select>
      </mat-form-field>

      <mat-form-field subscriptSizing="dynamic">
        <mat-icon class="select-icon" matPrefix>category</mat-icon>
        <mat-label>Group by</mat-label>
        <mat-select [value]="store.groupBy()" (valueChange)="store.setGroupBy($event)">
          <mat-option [value]="null">None</mat-option>
          <mat-option value="publicationType">Publication type</mat-option>
          <mat-option value="year">Year</mat-option>
        </mat-select>
      </mat-form-field>
    </hra-filter-menu>
  </mat-sidenav>

  <mat-sidenav-content role="main">
    <hra-search-filter
      label="Search"
      separator="/"
      [totalCount]="store.numResearchItems()"
      [viewingCount]="store.numFilteredItems()"
      [search]="store.search() ?? ''"
      (searchChange)="store.setSearch($event)"
    />

    <ng-scrollbar hraScrollOverflowFade>
      <div class="content">
        @if (store.numFilteredItems() === 0) {
          <hra-no-results-indicator (clearFilters)="store.resetFilters()" />
        } @else if (store.view() === 'gallery') {
          @for (group of store.sortedGroupedItems(); track group.label) {
            <section class="gallery-group">
              @if (group.label) {
                <h3 hra-section-link underlined class="title">{{ group.label }}</h3>
              }

              <hra-gallery-grid [dataSource]="group.items">
                <hra-gallery-card
                  *hraGalleryGridItem="let item"
                  [imageSrc]="getImageUrl(item)"
                  [tagline]="item.title"
                  [date]="(item.dateStart | date) ?? ''"
                  [link]="item.link || ''"
                  [external]="true"
                  [tags]="getTagItems(item.tags)"
                />
              </hra-gallery-grid>
            </section>
          }
        } @else {
          <hra-list-view [data]="store.sortedGroupedListItems()" [groupBy]="store.groupBy() !== null" />
        }

        <div class="page-end">
          <mat-divider />
          <hra-end-of-results-indicator [count]="store.numFilteredItems()" />
        </div>

        <cns-footer />
      </div>
    </ng-scrollbar>
  </mat-sidenav-content>
</mat-sidenav-container>
Legend
Html element
Component
Html element with directive

results matching ""

    No results matching ""