From b3f3324d0d69b80798cb182648aa9babc7cc72d8 Mon Sep 17 00:00:00 2001
From: jojohoch <joachim.hoch@iqb.hu-berlin.de>
Date: Tue, 4 Jul 2023 11:11:25 +0200
Subject: [PATCH] [player] Save if the animation of an animated section was
 done

---
 .../player/modules/verona/models/verona.ts    |  4 +-
 .../src/app/classes/storable-timer.spec.ts    |  7 +++
 ...er-state-variable.ts => storable-timer.ts} | 10 ++--
 .../player/src/app/classes/storable.spec.ts   |  7 +++
 projects/player/src/app/classes/storable.ts   |  9 +++
 .../app/classes/timer-state-variable.spec.ts  |  7 ---
 .../app/components/page/page.component.html   |  3 +-
 .../section-visibility-handling.directive.ts  | 58 +++++++++++++------
 .../src/app/services/unit-state.service.ts    |  6 +-
 9 files changed, 74 insertions(+), 37 deletions(-)
 create mode 100644 projects/player/src/app/classes/storable-timer.spec.ts
 rename projects/player/src/app/classes/{timer-state-variable.ts => storable-timer.ts} (79%)
 create mode 100644 projects/player/src/app/classes/storable.spec.ts
 create mode 100644 projects/player/src/app/classes/storable.ts
 delete mode 100644 projects/player/src/app/classes/timer-state-variable.spec.ts

diff --git a/projects/player/modules/verona/models/verona.ts b/projects/player/modules/verona/models/verona.ts
index 19def54f5..f7610393b 100644
--- a/projects/player/modules/verona/models/verona.ts
+++ b/projects/player/modules/verona/models/verona.ts
@@ -5,8 +5,8 @@ export type RunningState = 'running' | 'stopped';
 export type Progress = 'none' | 'some' | 'complete';
 export type PagingMode = 'separate' | 'concat-scroll' | 'concat-scroll-snap';
 export type StateReportPolicy = 'none' | 'eager' | 'on-demand';
-export type ElementCodeStatus = 'DERIVED' | 'NOT_REACHED' | 'DISPLAYED' | 'VALUE_CHANGED';
-export enum ElementCodeStatusValue { DERIVED = 0, NOT_REACHED = 1, DISPLAYED = 2, VALUE_CHANGED = 3}
+export type ElementCodeStatus = 'VIRTUAL' | 'NOT_REACHED' | 'DISPLAYED' | 'VALUE_CHANGED';
+export enum ElementCodeStatusValue { VIRTUAL = 0, NOT_REACHED = 1, DISPLAYED = 2, VALUE_CHANGED = 3}
 
 export interface StatusChangeElement {
   id: string;
diff --git a/projects/player/src/app/classes/storable-timer.spec.ts b/projects/player/src/app/classes/storable-timer.spec.ts
new file mode 100644
index 000000000..33a24b995
--- /dev/null
+++ b/projects/player/src/app/classes/storable-timer.spec.ts
@@ -0,0 +1,7 @@
+import { StorableTimer } from './storable-timer';
+
+describe('StorableTimer', () => {
+  it('should create an instance', () => {
+    expect(new StorableTimer('test', 0, 3000)).toBeTruthy();
+  });
+});
diff --git a/projects/player/src/app/classes/timer-state-variable.ts b/projects/player/src/app/classes/storable-timer.ts
similarity index 79%
rename from projects/player/src/app/classes/timer-state-variable.ts
rename to projects/player/src/app/classes/storable-timer.ts
index 1bf405432..1d0cfc6b6 100644
--- a/projects/player/src/app/classes/timer-state-variable.ts
+++ b/projects/player/src/app/classes/storable-timer.ts
@@ -1,19 +1,17 @@
 import { EventEmitter } from '@angular/core';
 import { ValueChangeElement } from 'common/models/elements/element';
+import { Storable } from 'player/src/app/classes/storable';
 
-export class TimerStateVariable {
-  id: string;
-  value: number;
+export class StorableTimer extends Storable {
   duration: number;
   timerStateValueChanged = new EventEmitter<ValueChangeElement>();
   timerStateEnded = new EventEmitter();
 
   private interval: number = 0;
 
-  constructor(id: string, duration: number, value = 0) {
-    this.id = id;
+  constructor(id: string, duration: number, value: number = 0) {
+    super(id, value);
     this.duration = duration;
-    this.value = value;
   }
 
   run(): void {
diff --git a/projects/player/src/app/classes/storable.spec.ts b/projects/player/src/app/classes/storable.spec.ts
new file mode 100644
index 000000000..adf0ed559
--- /dev/null
+++ b/projects/player/src/app/classes/storable.spec.ts
@@ -0,0 +1,7 @@
+import { Storable } from './storable';
+
+describe('Storable', () => {
+  it('should create an instance', () => {
+    expect(new Storable('test', 1)).toBeTruthy();
+  });
+});
diff --git a/projects/player/src/app/classes/storable.ts b/projects/player/src/app/classes/storable.ts
new file mode 100644
index 000000000..54c05c0ca
--- /dev/null
+++ b/projects/player/src/app/classes/storable.ts
@@ -0,0 +1,9 @@
+export class Storable {
+  id: string;
+  value: number;
+
+  constructor(id: string, value: number) {
+    this.id = id;
+    this.value = value;
+  }
+}
diff --git a/projects/player/src/app/classes/timer-state-variable.spec.ts b/projects/player/src/app/classes/timer-state-variable.spec.ts
deleted file mode 100644
index 1e8af0f53..000000000
--- a/projects/player/src/app/classes/timer-state-variable.spec.ts
+++ /dev/null
@@ -1,7 +0,0 @@
-import { TimerStateVariable } from './timer-state-variable';
-
-describe('State', () => {
-  it('should create an instance', () => {
-    expect(new TimerStateVariable('test', 0, 3000)).toBeTruthy();
-  });
-});
diff --git a/projects/player/src/app/components/page/page.component.html b/projects/player/src/app/components/page/page.component.html
index f8aa0610f..231600c01 100644
--- a/projects/player/src/app/components/page/page.component.html
+++ b/projects/player/src/app/components/page/page.component.html
@@ -3,7 +3,7 @@
      [intersectionContainer]="pagesContainer"
      (intersecting)="selectedIndexChange.emit(scrollPageIndex)">
   <aspect-section
-    *ngFor="let section of page.sections"
+    *ngFor="let section of page.sections; let i = index"
     class="section"
     [class.static-section]="!section.dynamicPositioning"
     [style.backgroundColor]="section.dynamicPositioning ? null : section.backgroundColor"
@@ -11,6 +11,7 @@
     [pageIndex]=pageIndex
     [section]="section"
     aspectSectionVisibilityHandling
+    [sectionIndex]="i"
     [pageSections]="page.sections">
   </aspect-section>
 </div>
diff --git a/projects/player/src/app/directives/section-visibility-handling.directive.ts b/projects/player/src/app/directives/section-visibility-handling.directive.ts
index a197906ae..3586433c5 100644
--- a/projects/player/src/app/directives/section-visibility-handling.directive.ts
+++ b/projects/player/src/app/directives/section-visibility-handling.directive.ts
@@ -5,9 +5,10 @@ import { Subject } from 'rxjs';
 import { Section } from 'common/models/section';
 import { takeUntil } from 'rxjs/operators';
 import { UnitStateService } from 'player/src/app/services/unit-state.service';
-import { TimerStateVariable } from 'player/src/app/classes/timer-state-variable';
+import { StorableTimer } from 'player/src/app/classes/storable-timer';
 import { ValueChangeElement } from 'common/models/elements/element';
-import { ElementCode, ElementCodeStatusValue } from 'player/modules/verona/models/verona';
+import { ElementCode } from 'player/modules/verona/models/verona';
+import { Storable } from 'player/src/app/classes/storable';
 
 @Directive({
   selector: '[aspectSectionVisibilityHandling]'
@@ -15,6 +16,8 @@ import { ElementCode, ElementCodeStatusValue } from 'player/modules/verona/model
 export class SectionVisibilityHandlingDirective implements OnInit, OnDestroy {
   @Input() section!: Section;
   @Input() pageSections!: Section[];
+  @Input() pageIndex!: number;
+  @Input() sectionIndex!: number;
 
   private ngUnsubscribe = new Subject<void>();
   private rulesAreFulfilled: boolean = false;
@@ -31,7 +34,10 @@ export class SectionVisibilityHandlingDirective implements OnInit, OnDestroy {
   }
 
   private addSubscription(): void {
-    if (this.section.enableReHide || !this.hasSeenElements()) {
+    if (this.section.enableReHide ||
+      (this.section.visibilityDelay && !this.isTimerStateFullFilled) ||
+      (this.section.animatedVisibility && !this.isAnimatedVisibilityFullFilled)
+    ) {
       this.displayHiddenSection();
       this.unitStateService.elementCodeChanged
         .pipe(takeUntil(this.ngUnsubscribe))
@@ -43,21 +49,36 @@ export class SectionVisibilityHandlingDirective implements OnInit, OnDestroy {
     }
   }
 
+  private get isTimerStateFullFilled(): boolean {
+    return this.unitStateService
+      .getElementCodeById(this.timerStateVariableId)?.value as number >= this.section.visibilityDelay;
+  }
+
+  private get isAnimatedVisibilityFullFilled(): boolean {
+    return this.unitStateService
+      .getElementCodeById(this.animationVariableId)?.value === 1;
+  }
+
   private isRuleCode(code: ElementCode): boolean {
     return this.section.visibilityRules
       .map(rule => rule.id)
       .some(id => id === code.id) ||
-      (!!this.section.visibilityDelay && code.id === this.timerStateVariableId);
+      (!!this.section.visibilityDelay && code.id === this.timerStateVariableId) ||
+      (this.section.animatedVisibility && code.id === this.animationVariableId);
   }
 
   private get timerStateVariableId(): string {
-    return `${this.section.visibilityRules[0].id}-
-    ${this.section.visibilityRules[0].value}-
-    ${this.section.visibilityDelay}`;
+    const firstRule = this.section.visibilityRules[0];
+    return `${firstRule.id}-${firstRule.value}-${this.pageIndex}-${this.sectionIndex}-${this.section.visibilityDelay}`;
+  }
+
+  private get animationVariableId(): string {
+    const firstRule = this.section.visibilityRules[0];
+    return `${firstRule.id}-${firstRule.value}-${this.pageIndex}-${this.sectionIndex}-scroll-animation`;
   }
 
   private initTimerStateVariable(): void {
-    const timerStateVariable = new TimerStateVariable(
+    const timerStateVariable = new StorableTimer(
       this.timerStateVariableId, this.section.visibilityDelay
     );
     this.unitStateService.registerElement(timerStateVariable.id, timerStateVariable.value);
@@ -75,8 +96,7 @@ export class SectionVisibilityHandlingDirective implements OnInit, OnDestroy {
           this.rulesAreFulfilled = true;
           this.initTimerStateVariable();
         }
-        this.setVisibility((this.unitStateService
-          .getElementCodeById(this.timerStateVariableId)?.value as number) >= this.section.visibilityDelay);
+        this.setVisibility(this.isTimerStateFullFilled);
       } else {
         this.setVisibility(true);
       }
@@ -88,7 +108,9 @@ export class SectionVisibilityHandlingDirective implements OnInit, OnDestroy {
   private setVisibility(visible: boolean): void {
     this.elementRef.nativeElement.style.display = visible ? null : 'none';
     if (visible) {
-      if (this.section.animatedVisibility && !this.hasSeenElements()) {
+      if (this.section.animatedVisibility && !this.isAnimatedVisibilityFullFilled) {
+        const animationVariable = new Storable(this.animationVariableId, 1);
+        this.unitStateService.registerElement(animationVariable.id, 1);
         this.scrollIntoView();
       }
       if (!this.section.enableReHide) {
@@ -146,13 +168,13 @@ export class SectionVisibilityHandlingDirective implements OnInit, OnDestroy {
     });
   }
 
-  private hasSeenElements(): boolean {
-    return this.section.getAllElements()
-      .map(element => this.unitStateService.getElementCodeById(element.id))
-      .some(code => (code ?
-        ElementCodeStatusValue[code.status] > ElementCodeStatusValue.NOT_REACHED :
-        false));
-  }
+  // private hasSeenElements(): boolean {
+  //   return this.section.getAllElements()
+  //     .map(element => this.unitStateService.getElementCodeById(element.id))
+  //     .some(code => (code ?
+  //       ElementCodeStatusValue[code.status] > ElementCodeStatusValue.NOT_REACHED :
+  //       false));
+  // }
 
   ngOnDestroy(): void {
     this.ngUnsubscribe.next();
diff --git a/projects/player/src/app/services/unit-state.service.ts b/projects/player/src/app/services/unit-state.service.ts
index fc56c79fb..7a712246d 100644
--- a/projects/player/src/app/services/unit-state.service.ts
+++ b/projects/player/src/app/services/unit-state.service.ts
@@ -66,7 +66,7 @@ export class UnitStateService {
     this.setElementCodeValue(elementValue.id, elementValue.value);
     const unitStateElementCode = this.getElementCodeById(elementValue.id);
     if (unitStateElementCode) {
-      if (unitStateElementCode.status !== 'DERIVED') {
+      if (unitStateElementCode.status !== 'VIRTUAL') {
         this.setElementCodeStatus(elementValue.id, 'VALUE_CHANGED');
       } else {
         this._elementCodeChanged.next(unitStateElementCode);
@@ -152,12 +152,12 @@ export class UnitStateService {
     let unitStateElementCode = this.getElementCodeById(id);
     if (!unitStateElementCode) {
       // when reloading a unit, elementCodes are already pushed
-      const status = domElement ? 'NOT_REACHED' : 'DERIVED';
+      const status = domElement ? 'NOT_REACHED' : 'VIRTUAL';
       unitStateElementCode = { id, value, status };
       this.elementCodes.push(unitStateElementCode);
       this._elementCodeChanged.next(unitStateElementCode);
     } else if (Object.keys(this.elementIdPageIndexMap).length === this.elementCodes
-      .filter(e => e.status !== 'DERIVED').length) {
+      .filter(e => e.status !== 'VIRTUAL').length) {
       // if all elements are registered, we can rebuild the presentedPages array
       this.buildPresentedPages();
     }
-- 
GitLab