From b0b92974f20dbf332e97677acb308c4265935e23 Mon Sep 17 00:00:00 2001
From: Xiao Gui <xgui3783@gmail.com>
Date: Fri, 5 Jun 2020 17:40:07 +0200
Subject: [PATCH] bugfix: touch panel maximise chore: streamlined dataset
 action btns

---
 common/constants.js                           |   2 +
 docs/releases/v2.2.2.md                       |   2 +
 .../browsingForDatasets.prod.e2e-spec.js      |   6 +-
 e2e/src/advanced/favDatasets.prod.e2e-spec.js |  12 ++-
 e2e/src/util.js                               |   1 -
 src/components/components.module.ts           |   3 +
 .../dynamicMaterialBtn.component.ts           |  23 ++++
 .../dynamicMaterialBtn.template.html          |  84 +++++++++++++++
 .../previewCW.template.html                   |  66 ++++++------
 .../detailedView/singleDataset.template.html  | 102 +++++++++---------
 .../singleDataset/singleDataset.base.ts       |   7 +-
 .../nehubaContainer.template.html             |   4 -
 src/util/directives/mediaQuery.directive.ts   |  54 ++++++++++
 src/util/util.module.ts                       |   7 ++
 14 files changed, 281 insertions(+), 92 deletions(-)
 create mode 100644 src/components/dynamicMaterialBtn/dynamicMaterialBtn.component.ts
 create mode 100644 src/components/dynamicMaterialBtn/dynamicMaterialBtn.template.html
 create mode 100644 src/util/directives/mediaQuery.directive.ts

diff --git a/common/constants.js b/common/constants.js
index f9065be7a..e92bdb8be 100644
--- a/common/constants.js
+++ b/common/constants.js
@@ -2,12 +2,14 @@
 
   exports.ARIA_LABELS = {
     // dataset specific
+    SHOW_DATASET_PREVIEW: 'Show dataset preview',
     TOGGLE_EXPLORE_PANEL: `Toggle explore panel`,
     MODALITY_FILTER: `Toggle dataset modality filter`,
     LIST_OF_DATASETS: `List of datasets`,
     DOWNLOAD_PREVIEW: `Download`,
     DOWNLOAD_PREVIEW_CSV: `Download CSV`,
     DATASET_FILE_PREVIEW: `Preview of dataset`,
+    PIN_DATASET: 'Toggle pinning dataset',
 
     // overlay specific
     CONTEXT_MENU: `Viewer context menu`,
diff --git a/docs/releases/v2.2.2.md b/docs/releases/v2.2.2.md
index 128c1db48..575e922d9 100644
--- a/docs/releases/v2.2.2.md
+++ b/docs/releases/v2.2.2.md
@@ -9,6 +9,8 @@
 - Showing region status on region context menu (#520)
 - Dataset previews are now reflected in the URL state (#502) & they stagger now!
 - Slightly retweaked scroll dataset container condition, to make it less strict
+- Fixed maximise panel on touch enabled devices (#533)
+- Dynamically fit the action buttons under datasets
 
 ## Under the hood stuff
 
diff --git a/e2e/src/advanced/browsingForDatasets.prod.e2e-spec.js b/e2e/src/advanced/browsingForDatasets.prod.e2e-spec.js
index 9eeb93161..97c8fd982 100644
--- a/e2e/src/advanced/browsingForDatasets.prod.e2e-spec.js
+++ b/e2e/src/advanced/browsingForDatasets.prod.e2e-spec.js
@@ -89,8 +89,8 @@ describe('> receptor dataset previews', () => {
     const receptorIndex = datasets.indexOf(receptorName)
 
     await iavPage.clickNthDataset(receptorIndex)
-    await iavPage.waitFor(true, true)
-    await iavPage.clickModalBtnByText(/preview/i)
+    await iavPage.wait(500)
+    await iavPage.click(`[aria-label="${ARIA_LABELS.SHOW_DATASET_PREVIEW}"]`)
     await iavPage.waitFor(true, true)
   })
 
@@ -135,7 +135,7 @@ describe('> receptor dataset previews', () => {
     const files = await iavPage.getBottomSheetList()
     const imageIndex = files.findIndex(file => /image\//i.test(file))
     await iavPage.clickNthItemFromBottomSheetList(imageIndex)
-    await iavPage.waitFor(true, true)
+    await iavPage.wait(500)
     const modalHasImage = await iavPage.modalHasChild('div[data-img-src]')
     expect(modalHasImage).toEqual(true)
 
diff --git a/e2e/src/advanced/favDatasets.prod.e2e-spec.js b/e2e/src/advanced/favDatasets.prod.e2e-spec.js
index 1d3ebebe3..c745200e5 100644
--- a/e2e/src/advanced/favDatasets.prod.e2e-spec.js
+++ b/e2e/src/advanced/favDatasets.prod.e2e-spec.js
@@ -1,4 +1,5 @@
 const { AtlasPage } = require('../util')
+const { ARIA_LABELS } = require('../../../common/constants')
 
 const template = 'ICBM 2009c Nonlinear Asymmetric'
 const area = 'Area hOc1 (V1, 17, CalcS)'
@@ -155,7 +156,9 @@ describe(`fav'ing dataset`, () => {
       const receptorIndex = datasets.indexOf(receptorName)
       await iavPage.clickNthDataset(receptorIndex)
       await iavPage.wait(500)
-      await iavPage.clickModalBtnByText(/pin\ this\ dataset/i)
+
+      // specificity is required, because single dataset card for julich brain also can be selected, and will be clicked and fail this test
+      await iavPage.click(`mat-dialog-container [aria-label="${ARIA_LABELS.PIN_DATASET}"]`)
       await iavPage.wait(500)
 
       const txt = await iavPage.getSnackbarMessage()
@@ -178,7 +181,9 @@ describe(`fav'ing dataset`, () => {
       const receptorIndex = datasets.indexOf(receptorName)
       await iavPage.clickNthDataset(receptorIndex)
       await iavPage.wait(500)
-      await iavPage.clickModalBtnByText(/pin\ this\ dataset/i)
+
+      // specificity is required, because single dataset card for julich brain also can be selected, and will be clicked and fail this test
+      await iavPage.click(`mat-dialog-container [aria-label="${ARIA_LABELS.PIN_DATASET}"]`)
       await iavPage.wait(500)
 
       const numberOfFav = await iavPage.getNumberOfFavDataset()
@@ -187,7 +192,8 @@ describe(`fav'ing dataset`, () => {
       // this wait is unfortunately necessary, as the snack bar sometimes obscures the unpin this dataset button	
       await iavPage.wait(5000)
 
-      await iavPage.clickModalBtnByText(/unpin\ this\ dataset/i)
+      // specificity is required, because single dataset card for julich brain also can be selected, and will be clicked and fail this test
+      await iavPage.click(`mat-dialog-container [aria-label="${ARIA_LABELS.PIN_DATASET}"]`)
       await iavPage.wait(500)
 
       const txt = await iavPage.getSnackbarMessage()
diff --git a/e2e/src/util.js b/e2e/src/util.js
index 219080d5f..c96a61803 100644
--- a/e2e/src/util.js
+++ b/e2e/src/util.js
@@ -481,7 +481,6 @@ class WdLayoutPage extends WdBase{
         .isDisplayed()
       return isDisplayed
     } catch (e) {
-      console.warn(`modalhaschild thrown error`, e)
       return false
     }
   }
diff --git a/src/components/components.module.ts b/src/components/components.module.ts
index f6cdb091c..f3c9c22e3 100644
--- a/src/components/components.module.ts
+++ b/src/components/components.module.ts
@@ -32,6 +32,7 @@ import { TimerComponent } from './timer/timer.component';
 import { TreeComponent } from './tree/tree.component';
 import { TreeBaseDirective } from './tree/treeBase.directive';
 import { IAVVerticalButton } from './vButton/vButton.component';
+import { DynamicMaterialBtn } from './dynamicMaterialBtn/dynamicMaterialBtn.component';
 
 @NgModule({
   imports : [
@@ -58,6 +59,7 @@ import { IAVVerticalButton } from './vButton/vButton.component';
     DialogComponent,
     ConfirmDialogComponent,
     IAVVerticalButton,
+    DynamicMaterialBtn,
 
     /* directive */
     HoverableBlockDirective,
@@ -91,6 +93,7 @@ import { IAVVerticalButton } from './vButton/vButton.component';
     DialogComponent,
     ConfirmDialogComponent,
     IAVVerticalButton,
+    DynamicMaterialBtn,
 
     SearchResultPaginationPipe,
     TreeSearchPipe,
diff --git a/src/components/dynamicMaterialBtn/dynamicMaterialBtn.component.ts b/src/components/dynamicMaterialBtn/dynamicMaterialBtn.component.ts
new file mode 100644
index 000000000..610ff397f
--- /dev/null
+++ b/src/components/dynamicMaterialBtn/dynamicMaterialBtn.component.ts
@@ -0,0 +1,23 @@
+import { Component, Input } from "@angular/core";
+
+type TypeMatBtnStyle = 'mat-button' | 'mat-raised-button' | 'mat-stroked-button' | 'mat-flat-button' | 'mat-icon-button' | 'mat-fab' | 'mat-mini-fab'
+type TypeMatBtnColor = 'basic' | 'primary' | 'accent' | 'warn'
+
+@Component({
+  selector: 'iav-dynamic-mat-button',
+  templateUrl: './dynamicMaterialBtn.template.html'
+})
+
+export class DynamicMaterialBtn{
+  @Input('iav-dynamic-mat-button-style')
+  matBtnStyle: TypeMatBtnStyle = 'mat-button'
+
+  @Input('iav-dynamic-mat-button-color')
+  matBtnColor: TypeMatBtnColor = 'basic'
+
+  @Input('iav-dynamic-mat-button-aria-label')
+  matBtnAriaLabel: string
+
+  @Input('iav-dynamic-mat-button-disabled')
+  matBtnDisabled: boolean = false
+}
diff --git a/src/components/dynamicMaterialBtn/dynamicMaterialBtn.template.html b/src/components/dynamicMaterialBtn/dynamicMaterialBtn.template.html
new file mode 100644
index 000000000..98fc13925
--- /dev/null
+++ b/src/components/dynamicMaterialBtn/dynamicMaterialBtn.template.html
@@ -0,0 +1,84 @@
+<ng-container [ngSwitch]="matBtnStyle">
+
+  <!-- mat-raise-button -->
+  <button *ngSwitchCase="'mat-raised-button'"
+    [attr.aria-label]="matBtnAriaLabel"
+    [disabled]="matBtnDisabled"
+    mat-raised-button
+    [color]="matBtnColor">
+    
+    <ng-container *ngTemplateOutlet="transcludedContent">
+    </ng-container>
+  </button>
+
+  <!-- mat-stroked-button -->
+  <button *ngSwitchCase="'mat-stroked-button'"
+    [attr.aria-label]="matBtnAriaLabel"
+    [disabled]="matBtnDisabled"
+    mat-stroked-button
+    [color]="matBtnColor">
+
+    <ng-container *ngTemplateOutlet="transcludedContent">
+    </ng-container>
+  </button>
+
+  <!-- mat-flat-button -->
+  <button *ngSwitchCase="'mat-flat-button'"
+    [attr.aria-label]="matBtnAriaLabel"
+    [disabled]="matBtnDisabled"
+    mat-flat-button
+    [color]="matBtnColor">
+
+    <ng-container *ngTemplateOutlet="transcludedContent">
+    </ng-container>
+  </button>
+
+  <!-- mat-icon-button -->
+  <button *ngSwitchCase="'mat-icon-button'"
+    [attr.aria-label]="matBtnAriaLabel"
+    [disabled]="matBtnDisabled"
+    mat-icon-button
+    [color]="matBtnColor">
+
+    <ng-container *ngTemplateOutlet="transcludedContent">
+    </ng-container>
+  </button>
+
+  <!-- mat-fab -->
+  <button *ngSwitchCase="'mat-fab'"
+    [attr.aria-label]="matBtnAriaLabel"
+    [disabled]="matBtnDisabled"
+    mat-fab
+    [color]="matBtnColor">
+
+    <ng-container *ngTemplateOutlet="transcludedContent">
+    </ng-container>
+  </button>
+
+  <!-- mat-mini-fab -->
+  <button *ngSwitchCase="'mat-mini-fab'"
+    [attr.aria-label]="matBtnAriaLabel"
+    [disabled]="matBtnDisabled"
+    mat-mini-fab
+    [color]="matBtnColor">
+
+    <ng-container *ngTemplateOutlet="transcludedContent">
+    </ng-container>
+  </button>
+
+  <!-- mat-mini-fab -->
+  <button *ngSwitchDefault
+    [attr.aria-label]="matBtnAriaLabel"
+    [disabled]="matBtnDisabled"
+    mat-button
+    [color]="matBtnColor">
+
+    <ng-container *ngTemplateOutlet="transcludedContent">
+    </ng-container>
+  </button>
+
+</ng-container>
+
+<ng-template #transcludedContent>
+  <ng-content></ng-content>
+</ng-template>
\ No newline at end of file
diff --git a/src/ui/databrowserModule/preview/previewComponentWrapper/previewCW.template.html b/src/ui/databrowserModule/preview/previewComponentWrapper/previewCW.template.html
index 03f4a6d52..71c478682 100644
--- a/src/ui/databrowserModule/preview/previewComponentWrapper/previewCW.template.html
+++ b/src/ui/databrowserModule/preview/previewComponentWrapper/previewCW.template.html
@@ -30,34 +30,40 @@
   </div>
 </h1>
 
-<kg-dataset-previewer
-  class="flex-grow-1 flex-shrink-1"
-  [darkmode]="darktheme$ | async"
-  [filename]="filename"
-  [kgId]="kgId"
-  [backendUrl]="backendUrl"
-  #dataPreviewerStencilCmp>
-
-</kg-dataset-previewer>
-
-<div class="flex-shrink-0 flex-grow-0 d-inline-flex">
-  <a *ngIf="downloadHref"
-    [attr.href]="downloadHref"
-    [attr.download]="filename"
-    [attr.aria-label]="DOWNLOAD_PREVIEW_ARIA_LABEL"
-    target="_blank"
-    mat-icon-button
-    color="primary">
-    <i class="fas fa-download"></i>
-  </a>
-
-  <a *ngIf="downloadCsvHref"
-    [attr.href]="downloadCsvHref"
-    [attr.download]="filename"
-    [attr.aria-label]="DOWNLOAD_PREVIEW_CSV_ARIA_LABEL"
-    target="_blank"
-    mat-icon-button
-    color="primary">
-    <i class="fas fa-file-csv"></i>
-  </a>
+<div mat-dialog-content class="h-100 d-flex flex-column">
+
+  <div class="h-0 flex-grow-1 flex-shrink-1">
+
+    <kg-dataset-previewer
+      class="h-100 w-100"
+      [darkmode]="darktheme$ | async"
+      [filename]="filename"
+      [kgId]="kgId"
+      [backendUrl]="backendUrl"
+      #dataPreviewerStencilCmp>
+
+    </kg-dataset-previewer>
+  </div>
+
+  <div class="flex-shrink-0 flex-grow-0 d-inline-flex">
+    <a *ngIf="downloadHref"
+      [attr.href]="downloadHref"
+      [attr.download]="filename"
+      [attr.aria-label]="DOWNLOAD_PREVIEW_ARIA_LABEL"
+      target="_blank"
+      mat-icon-button
+      color="primary">
+      <i class="fas fa-download"></i>
+    </a>
+
+    <a *ngIf="downloadCsvHref"
+      [attr.href]="downloadCsvHref"
+      [attr.download]="filename"
+      [attr.aria-label]="DOWNLOAD_PREVIEW_CSV_ARIA_LABEL"
+      target="_blank"
+      mat-icon-button
+      color="primary">
+      <i class="fas fa-file-csv"></i>
+    </a>
+  </div>
 </div>
diff --git a/src/ui/databrowserModule/singleDataset/detailedView/singleDataset.template.html b/src/ui/databrowserModule/singleDataset/detailedView/singleDataset.template.html
index cb0f4bfac..7d8113427 100644
--- a/src/ui/databrowserModule/singleDataset/detailedView/singleDataset.template.html
+++ b/src/ui/databrowserModule/singleDataset/detailedView/singleDataset.template.html
@@ -45,70 +45,79 @@
 
 
 <!-- footer -->
-<mat-card-actions>
+<mat-card-actions iav-media-query #iavMediaQuery="iavMediaQuery">
+  <ng-container *ngTemplateOutlet="actionBtns; context: { $implicit: (iavMediaQuery.mediaBreakPoint$ | async) }" >
+  </ng-container>
+</mat-card-actions>
+
+<mat-card-footer></mat-card-footer>
+
+<ng-template #previewFilesListTemplate>
+  <dataset-preview-list
+    [kgId]="kgId">
+
+  </dataset-preview-list>
+</ng-template>
+
+<!-- using ng template for context binding of media breakpoints -->
+<ng-template #actionBtns let-mediaBreakPoint>
 
   <!-- explore -->
   <ng-container *ngIf="!strictLocal">
 
     <a *ngFor="let kgRef of kgReference"
-      class="m-2"
       [href]="kgRef | doiParserPipe"
       target="_blank">
-      <button 
-        mat-raised-button
-        color="primary">
-        Explore
+      <iav-dynamic-mat-button
+        [iav-dynamic-mat-button-style]="mediaBreakPoint < 2 ? 'mat-raised-button' : 'mat-icon-button'"
+        iav-dynamic-mat-button-color="primary">
+
+        <span *ngIf="mediaBreakPoint < 2">
+          Explore
+        </span>
         <i class="fas fa-external-link-alt"></i>
-      </button>
+      </iav-dynamic-mat-button>
     </a>
   </ng-container>
 
   <!-- pin data -->
   <ng-container *ngIf="downloadEnabled && kgId">
 
-    <!-- Is currently fav'ed -->
-    <ng-container *ngIf="favedDataentries$ | async | datasetIsFaved : dataset; else notCurrentlyFav">
-
-      <button mat-button
-        iav-stop="click mousedown"
-        (click)="undoableRemoveFav()"
-        color="primary">
-        Unpin this dataset
-        <i class="fas fa-thumbtack"></i>
-      </button>
+    <ng-container *ngTemplateOutlet="favDatasetBtn; context: { $implicit: (favedDataentries$ | async | datasetIsFaved : dataset) }">
     </ng-container>
 
-    <!-- Is NOT currently fav'ed -->
-    <ng-template #notCurrentlyFav>
-      
-      <button mat-button
+    <ng-template #favDatasetBtn let-isFav>
+      <iav-dynamic-mat-button
+        (click)="isFav ? undoableRemoveFav() : undoableAddFav()"
         iav-stop="click mousedown"
-        (click)="undoableAddFav()"
-        color="default">
-        Pin this dataset
+        [iav-dynamic-mat-button-aria-label]="PIN_DATASET_ARIA_LABEL"
+        [iav-dynamic-mat-button-style]="mediaBreakPoint < 2 ? 'mat-button' : 'mat-icon-button'"
+        [iav-dynamic-mat-button-color]="isFav ? 'primary' : 'basic'">
+
+        <span *ngIf="mediaBreakPoint < 2">
+          {{ isFav ? 'Unpin this dataset' : 'Pin this dataset' }}
+        </span>
         <i class="fas fa-thumbtack"></i>
-      </button>
+      </iav-dynamic-mat-button>
     </ng-template>
   </ng-container>
 
-
   <!-- download -->
   <ng-container *ngIf="!strictLocal">
 
     <a *ngIf="files && files.length > 0"
       [href]="dlFromKgHref"
       target="_blank">
-      <button
-        [disabled]="downloadInProgress"
+      <iav-dynamic-mat-button
         [matTooltip]="tooltipText"
-        class="m-2"
-        mat-button
-        color="basic">
-        <span>
-          Download as Zip
+        [disabled]="downloadInProgress"
+        [iav-dynamic-mat-button-style]="mediaBreakPoint < 2 ? 'mat-button' : 'mat-icon-button'">
+
+        <span *ngIf="mediaBreakPoint < 2">
+          Download Zip
         </span>
         <i class="ml-1 fas" [ngClass]="!downloadInProgress? 'fa-download' :'fa-spinner fa-pulse'"></i>
-      </button>
+      </iav-dynamic-mat-button>
     </a>
   </ng-container>
 
@@ -123,24 +132,17 @@
 
   </kg-dataset-list>
 
-  <button mat-button
-    mat-dialog-close
+  <iav-dynamic-mat-button
     *ngIf="hasPreview"
-    (click)="showPreviewList(previewFilesListTemplate)"
-    color="basic">
-    <span>
+    mat-dialog-close
+    [iav-dynamic-mat-button-style]="mediaBreakPoint < 2 ? 'mat-button' : 'mat-icon-button'"
+    [iav-dynamic-mat-button-aria-label]="SHOW_DATASET_PREVIEW_ARIA_LABEL"
+    (click)="showPreviewList(previewFilesListTemplate)">
+
+    <span *ngIf="mediaBreakPoint < 2">
       Preview
     </span>
     <i class="ml-1 far fa-eye"></i>
-  </button>
-
-</mat-card-actions>
-
-<mat-card-footer></mat-card-footer>
+  </iav-dynamic-mat-button>
 
-<ng-template #previewFilesListTemplate>
-  <dataset-preview-list
-    [kgId]="kgId">
-
-  </dataset-preview-list>
-</ng-template>
+</ng-template>
\ No newline at end of file
diff --git a/src/ui/databrowserModule/singleDataset/singleDataset.base.ts b/src/ui/databrowserModule/singleDataset/singleDataset.base.ts
index 5872a6099..9b2ed6869 100644
--- a/src/ui/databrowserModule/singleDataset/singleDataset.base.ts
+++ b/src/ui/databrowserModule/singleDataset/singleDataset.base.ts
@@ -1,6 +1,6 @@
 import { ChangeDetectorRef, Input, OnInit, TemplateRef, OnChanges } from "@angular/core";
 import { Observable } from "rxjs";
-import { IDataEntry, IFile, IPublication, ViewerPreviewFile } from 'src/services/state/dataStore.store'
+import { IDataEntry, IFile, IPublication } from 'src/services/state/dataStore.store'
 import { HumanReadableFileSizePipe } from "src/util/pipes/humanReadableFileSize.pipe";
 import { DatabrowserService } from "../databrowser.service";
 import { KgSingleDatasetService } from "../kgSingleDatasetService.service";
@@ -9,6 +9,8 @@ import { DS_PREVIEW_URL } from 'src/util/constants'
 import { getKgSchemaIdFromFullId } from "../util/getKgSchemaIdFromFullId.pipe";
 import { MatSnackBar } from "@angular/material/snack-bar";
 
+import { ARIA_LABELS } from 'common/constants'
+
 export {
   DatabrowserService,
   KgSingleDatasetService,
@@ -17,6 +19,9 @@ export {
 
 export class SingleDatasetBase implements OnInit, OnChanges {
 
+  public SHOW_DATASET_PREVIEW_ARIA_LABEL = ARIA_LABELS.SHOW_DATASET_PREVIEW
+  public PIN_DATASET_ARIA_LABEL = ARIA_LABELS.PIN_DATASET
+
   @Input() public ripple: boolean = false
 
   /**
diff --git a/src/ui/nehubaContainer/nehubaContainer.template.html b/src/ui/nehubaContainer/nehubaContainer.template.html
index b6ecacc37..5d4fd613d 100644
--- a/src/ui/nehubaContainer/nehubaContainer.template.html
+++ b/src/ui/nehubaContainer/nehubaContainer.template.html
@@ -80,7 +80,6 @@
 
     <!-- maximise/minimise button -->
     <maximise-panel-button
-      (touchend)="toggleMaximiseMinimise(0)"
       (click)="toggleMaximiseMinimise(0)"
       [ngClass]="{ onHover: (panelOrder$ | async | reorderPanelIndexPipe : ( hoveredPanelIndices$ | async  )) === 0 }"
       [touch-side-class]="0"
@@ -109,7 +108,6 @@
 
     <!-- maximise/minimise button -->
     <maximise-panel-button
-      (touchend)="toggleMaximiseMinimise(1)"
       (click)="toggleMaximiseMinimise(1)"
       [ngClass]="{ onHover: (panelOrder$ | async | reorderPanelIndexPipe : ( hoveredPanelIndices$ | async  )) === 1 }"
       [touch-side-class]="1"
@@ -138,7 +136,6 @@
 
     <!-- maximise/minimise button -->
     <maximise-panel-button
-      (touchend)="toggleMaximiseMinimise(2)"
       (click)="toggleMaximiseMinimise(2)"
       [ngClass]="{ onHover: (panelOrder$ | async | reorderPanelIndexPipe : ( hoveredPanelIndices$ | async  )) === 2 }"
       [touch-side-class]="2"
@@ -158,7 +155,6 @@
 
     <!-- maximise/minimise button -->
     <maximise-panel-button
-      (touchend)="toggleMaximiseMinimise(3)"
       (click)="toggleMaximiseMinimise(3)"
       [ngClass]="{ onHover: (panelOrder$ | async | reorderPanelIndexPipe : ( hoveredPanelIndices$ | async  )) === 3 }"
       [touch-side-class]="3"
diff --git a/src/util/directives/mediaQuery.directive.ts b/src/util/directives/mediaQuery.directive.ts
new file mode 100644
index 000000000..08d77130d
--- /dev/null
+++ b/src/util/directives/mediaQuery.directive.ts
@@ -0,0 +1,54 @@
+import { Directive } from "@angular/core";
+import { BreakpointObserver } from "@angular/cdk/layout";
+import { Observable } from "rxjs";
+import { map } from "rxjs/operators";
+
+const mediaBreakPoints = [
+  '(min-width: 576px)',
+  '(min-width: 768px)',
+  '(min-width: 992px)',
+  '(min-width: 1200px)',
+
+  // xxl, by popular demand
+  '(min-width: 2000px)'
+]
+
+enum EnumMediaBreakPoints{
+  s,
+  m,
+  l,
+  xl,
+  xxl,
+  xxxl
+}
+
+@Directive({
+  selector: '[iav-media-query]',
+  exportAs: 'iavMediaQuery'
+})
+
+export class MediaQueryDirective{
+
+  public mediaBreakPoint$: Observable<EnumMediaBreakPoints>
+  constructor(
+    bpObs: BreakpointObserver
+  ){
+    this.mediaBreakPoint$ = bpObs.observe(mediaBreakPoints).pipe(
+      map(({ breakpoints, matches }) => {
+        if (!matches) return EnumMediaBreakPoints.xxxl
+        let tally = 0
+        for (const key in breakpoints) {
+          if (breakpoints[key]) tally += 1
+        }
+        switch(tally){
+        case 5: return EnumMediaBreakPoints.s
+        case 4: return EnumMediaBreakPoints.m
+        case 3: return EnumMediaBreakPoints.l
+        case 2: return EnumMediaBreakPoints.xl
+        case 1: return EnumMediaBreakPoints.xxl
+        default: return EnumMediaBreakPoints.xl
+        }
+      })
+    )
+  }
+}
diff --git a/src/util/util.module.ts b/src/util/util.module.ts
index 47d094f5f..1f8c3f09f 100644
--- a/src/util/util.module.ts
+++ b/src/util/util.module.ts
@@ -11,8 +11,13 @@ import { CaptureClickListenerDirective } from "./directives/captureClickListener
 import { AddUnitAndJoin } from "./pipes/addUnitAndJoin.pipe";
 import { NmToMm } from "./pipes/numbers.pipe";
 import { SwitchDirective } from "./directives/switch.directive";
+import { MediaQueryDirective } from './directives/mediaQuery.directive'
+import { LayoutModule } from "@angular/cdk/layout";
 
 @NgModule({
+  imports:[
+    LayoutModule
+  ],
   declarations: [
     FilterNullPipe,
     FilterRowsByVisbilityPipe,
@@ -28,6 +33,7 @@ import { SwitchDirective } from "./directives/switch.directive";
     AddUnitAndJoin,
     NmToMm,
     SwitchDirective,
+    MediaQueryDirective
   ],
   exports: [
     FilterNullPipe,
@@ -44,6 +50,7 @@ import { SwitchDirective } from "./directives/switch.directive";
     AddUnitAndJoin,
     NmToMm,
     SwitchDirective,
+    MediaQueryDirective
   ]
 })
 
-- 
GitLab