diff --git a/docs/release-notes-editor.md b/docs/release-notes-editor.md index 8047de06a7cf655301fec38aa8f5fe91d072cc63..6f44dad9aa41050eca287246724e72755a9dfa75 100644 --- a/docs/release-notes-editor.md +++ b/docs/release-notes-editor.md @@ -1,5 +1,10 @@ Editor ====== +## 2.1.0 +### Neue Funktionen +- Für die Bedingungen zur Sichtbarkeit von Abschnitten kann nun die + logische Verknüpfung festgelegt werden: UND / ODER + ## 2.0.3 ### Fehlerbehebungen - Element-IDs gelöschter Abschnitte werden korrekt verfügbar gemacht diff --git a/docs/release-notes-player.md b/docs/release-notes-player.md index e57fadd82944e10eaad6e07b6096515486fd9179..99ce1892feb089bed5827805e71bb52273860e76 100644 --- a/docs/release-notes-player.md +++ b/docs/release-notes-player.md @@ -1,6 +1,12 @@ Player ====== -## 2.0.4 +## 2.1.0 +### Neue Funktionen +- Der Player zeigt nur Unitdefinitionen an, die mit der gleichen Editor-Version erstellt wurden. + Ältere Unitdefinitionen müssen daher zunächst mit einem Editor in der Version 2.1 geöffnet + und gespeichert werden. + +### Fehlerbehebungen - Klappliste-Element repariert (ist wieder anklickbar) - Optionsfelder: Textausrichtung bei mehrzeiligen Optionen orientiert sich wieder an der ersten Zeile - Optionsfelder mit Bild: Optionen richten sich wieder nach unten aus diff --git a/docs/unit_definition_changelog.txt b/docs/unit_definition_changelog.txt index 014a71e375c7715b9db5d1b333a929a124ceaa62..8b6519804ef477b952c426dccda5bf628ad5b759 100644 --- a/docs/unit_definition_changelog.txt +++ b/docs/unit_definition_changelog.txt @@ -121,3 +121,7 @@ iqb-aspect-definition@1.0.0 - TextAreaElement, TextFieldElement, TextFieldSimpleElement, SpellCorrectElement: - remove property: softwareKeyboardShowFrench - new property: addInputAssistanceToKeyboard + +4.0.0 + - Section + - new property: logicalConnectiveOfRules: 'disjunction' | 'conjunction' diff --git a/docs/version-history.md b/docs/version-history.md index 5414ca84e9c8c3806b59c97fd81417ca2118750f..188c56cbd11b9ea6e590f107c30bb6e0f8677caa 100644 --- a/docs/version-history.md +++ b/docs/version-history.md @@ -1,5 +1,6 @@ | Editor | Unit Def | Player | |--------|----------|--------| +| 2.1.0 | 4.1.0 | 2.1.0 | | 2.0.0 | 4.0.0 | 2.0.0 | | 1.39.0 | 3.10.0 | 1.32.0 | | 1.38.0 | 3.10.0 | 1.31.0 | diff --git a/projects/common/models/page.ts b/projects/common/models/page.ts index 161ac8e00c5790ccf7321dfad8850decbdfcbf55..0fcc78a1ae560dd814e48039017d91b2bba3f679 100644 --- a/projects/common/models/page.ts +++ b/projects/common/models/page.ts @@ -2,6 +2,7 @@ import { Section } from 'common/models/section'; import { UIElement } from 'common/models/elements/element'; import { AnswerScheme } from 'common/models/elements/answer-scheme-interfaces'; import { environment } from 'common/environment'; +import { InstantiationEror } from 'common/util/errors'; export class Page { [index: string]: unknown; @@ -26,7 +27,7 @@ export class Page { this.sections = page.sections.map(section => new Section(section)); } else { if (environment.strictInstantiation) { - throw Error('Error at Page instantiation'); + throw new InstantiationEror('Error at Page instantiation'); } if (page?.hasMaxWidth !== undefined) this.hasMaxWidth = page.hasMaxWidth; if (page?.maxWidth !== undefined) this.maxWidth = page.maxWidth; diff --git a/projects/common/models/section.ts b/projects/common/models/section.ts index d21be97a0683f33fecf6ea6ab5b56da068b70188..b460deef6c64d877211a4970f054effdf86f1df1 100644 --- a/projects/common/models/section.ts +++ b/projects/common/models/section.ts @@ -12,6 +12,7 @@ import { AnswerScheme } from 'common/models/elements/answer-scheme-interfaces'; import { VisibilityRule } from 'common/models/visibility-rule'; import { ElementFactory } from 'common/util/element.factory'; import { environment } from 'common/environment'; +import { InstantiationEror } from 'common/util/errors'; export class Section { [index: string]: unknown; @@ -26,6 +27,7 @@ export class Section { visibilityDelay: number = 0; animatedVisibility: boolean = false; enableReHide: boolean = false; + logicalConnectiveOfRules: 'disjunction' | 'conjunction' = 'disjunction'; visibilityRules: VisibilityRule[] = []; constructor(section?: SectionProperties) { @@ -40,12 +42,13 @@ export class Section { this.visibilityDelay = section.visibilityDelay; this.animatedVisibility = section.animatedVisibility; this.enableReHide = section.enableReHide; + this.logicalConnectiveOfRules = section.logicalConnectiveOfRules; this.visibilityRules = section.visibilityRules; this.elements = section.elements .map(element => ElementFactory.createElement(element)) as PositionedUIElement[]; } else { if (environment.strictInstantiation) { - throw Error('Error at Section instantiation'); + throw new InstantiationEror('Error at Section instantiation'); } if (section?.height !== undefined) this.height = section.height; if (section?.backgroundColor !== undefined) this.backgroundColor = section.backgroundColor; @@ -57,6 +60,7 @@ export class Section { if (section?.visibilityDelay !== undefined) this.visibilityDelay = section.visibilityDelay; if (section?.animatedVisibility !== undefined) this.animatedVisibility = section.animatedVisibility; if (section?.enableReHide !== undefined) this.enableReHide = section.enableReHide; + if (section?.logicalConnectiveOfRules !== undefined) this.logicalConnectiveOfRules = section.logicalConnectiveOfRules; if (section?.visibilityRules !== undefined) this.visibilityRules = section.visibilityRules; this.elements = section?.elements !== undefined ? section.elements.map(element => ElementFactory.createElement(element)) as PositionedUIElement[] : @@ -104,6 +108,7 @@ export interface SectionProperties { visibilityDelay: number; animatedVisibility: boolean; enableReHide: boolean; + logicalConnectiveOfRules: 'disjunction' | 'conjunction'; visibilityRules: VisibilityRule[]; } @@ -120,5 +125,6 @@ function isValid(blueprint?: SectionProperties): boolean { blueprint.visibilityDelay !== undefined && blueprint.animatedVisibility !== undefined && blueprint.enableReHide !== undefined && + blueprint.logicalConnectiveOfRules !== undefined && blueprint.visibilityRules !== undefined; } diff --git a/projects/common/models/unit.ts b/projects/common/models/unit.ts index b0beed434dd1c3a2442a07b6e08d7b999bcb9843..3b70dc4ac28a3d422ec2d37b66bfecd168b44cd3 100644 --- a/projects/common/models/unit.ts +++ b/projects/common/models/unit.ts @@ -4,6 +4,7 @@ import { AnswerScheme } from 'common/models/elements/answer-scheme-interfaces'; import { StateVariable } from 'common/models/state-variable'; import { environment } from 'common/environment'; import { VersionManager } from 'common/services/version-manager'; +import { InstantiationEror } from 'common/util/errors'; import { ArrayUtils } from 'common/util/array'; export class Unit implements UnitProperties { @@ -19,7 +20,7 @@ export class Unit implements UnitProperties { this.pages = unit.pages.map(page => new Page(page)); } else { if (environment.strictInstantiation) { - throw Error('Error at unit instantiation'); + throw new InstantiationEror('Error at unit instantiation'); } this.version = VersionManager.getCurrentVersion(); if (unit?.stateVariables !== undefined) this.stateVariables = unit.stateVariables; diff --git a/projects/editor/src/app/components/canvas/section-menu.component.ts b/projects/editor/src/app/components/canvas/section-menu.component.ts index 8bd8e1e81cf17d6721c8502d2e9b1661f0cd80c6..098dd4f041832e1e3f7945548bd86fc9ae3acd9e 100644 --- a/projects/editor/src/app/components/canvas/section-menu.component.ts +++ b/projects/editor/src/app/components/canvas/section-menu.component.ts @@ -293,6 +293,7 @@ export class SectionMenuComponent implements OnDestroy { this.dialogService .showVisibilityRulesDialog( this.section.visibilityRules, + this.section.logicalConnectiveOfRules, this.getControlIds(), this.section.visibilityDelay, this.section.animatedVisibility, @@ -301,6 +302,7 @@ export class SectionMenuComponent implements OnDestroy { .subscribe(visibilityConfig => { if (visibilityConfig) { this.updateModel('visibilityRules', visibilityConfig.visibilityRules); + this.updateModel('logicalConnectiveOfRules', visibilityConfig.logicalConnectiveOfRules); this.updateModel('visibilityDelay', visibilityConfig.visibilityDelay); this.updateModel('animatedVisibility', visibilityConfig.animatedVisibility); this.updateModel('enableReHide', visibilityConfig.enableReHide); diff --git a/projects/editor/src/app/components/dialogs/visibility-rules-dialog/visibility-rules-dialog.component.html b/projects/editor/src/app/components/dialogs/visibility-rules-dialog/visibility-rules-dialog.component.html index 901ddfcdfb58368838864b97bdf4c0ec4fbb5dc9..1f8055eff4eafc5c95a66ea6c4a059e1e5997639 100644 --- a/projects/editor/src/app/components/dialogs/visibility-rules-dialog/visibility-rules-dialog.component.html +++ b/projects/editor/src/app/components/dialogs/visibility-rules-dialog/visibility-rules-dialog.component.html @@ -2,6 +2,11 @@ <div mat-dialog-content class="fx-column-start-stretch fx-gap-20"> <div *ngIf="controlIds.length && visibilityRules.length" class="fx-column-start-start"> + <label>{{'section.logicalConnectiveOfRules' | translate}}</label> + <mat-radio-group [(ngModel)]="logicalConnectiveOfRules"> + <mat-radio-button value="disjunction">{{'section.rulesDisjunction' | translate}}</mat-radio-button> + <mat-radio-button value="conjunction">{{'section.rulesConjunction' | translate}}</mat-radio-button> + </mat-radio-group> <mat-checkbox [(ngModel)]="enableReHide"> {{'section.enableReHide' | translate}} @@ -47,7 +52,9 @@ <div mat-dialog-actions> <button *ngIf="controlIds.length" mat-button - [mat-dialog-close]="{visibilityRules, visibilityDelay, animatedVisibility, enableReHide}"> + [mat-dialog-close]="{ + visibilityRules, logicalConnectiveOfRules, visibilityDelay, animatedVisibility, enableReHide + }"> {{'save' | translate}} </button> <button mat-button diff --git a/projects/editor/src/app/components/dialogs/visibility-rules-dialog/visibility-rules-dialog.component.ts b/projects/editor/src/app/components/dialogs/visibility-rules-dialog/visibility-rules-dialog.component.ts index 78fa0f594f3c5818ff3e2dd69d1a47089149a67c..1d59a88ec7f87b831ee4f4a8009bff7b55980fc6 100644 --- a/projects/editor/src/app/components/dialogs/visibility-rules-dialog/visibility-rules-dialog.component.ts +++ b/projects/editor/src/app/components/dialogs/visibility-rules-dialog/visibility-rules-dialog.component.ts @@ -19,6 +19,7 @@ import { MAT_DIALOG_DATA } from '@angular/material/dialog'; }) export class VisibilityRulesDialogComponent { visibilityRules!: VisibilityRule[]; + logicalConnectiveOfRules!: 'disjunction' | 'conjunction'; controlIds!: string[]; visibilityDelay!: number; animatedVisibility!: boolean; @@ -27,6 +28,7 @@ export class VisibilityRulesDialogComponent { constructor( @Inject(MAT_DIALOG_DATA) private data: { visibilityRules: VisibilityRule[], + logicalConnectiveOfRules: 'disjunction' | 'conjunction', visibilityDelay: number, animatedVisibility: boolean, controlIds: string[], @@ -34,6 +36,7 @@ export class VisibilityRulesDialogComponent { } ) { this.visibilityRules = [...data.visibilityRules]; + this.logicalConnectiveOfRules = data.logicalConnectiveOfRules; this.visibilityDelay = data.visibilityDelay; this.animatedVisibility = data.animatedVisibility; this.enableReHide = data.enableReHide; diff --git a/projects/editor/src/app/services/dialog.service.ts b/projects/editor/src/app/services/dialog.service.ts index 00c7c8afc5dfe08220f01526483adf6e8b37a218..b990658ef1af21a851f5fbbb40c381e7e57bf718 100644 --- a/projects/editor/src/app/services/dialog.service.ts +++ b/projects/editor/src/app/services/dialog.service.ts @@ -163,12 +163,14 @@ export class DialogService { } showVisibilityRulesDialog(visibilityRules: VisibilityRule[], + logicalConnectiveOfRules: 'disjunction' | 'conjunction', controlIds: string[], visibilityDelay: number, animatedVisibility: boolean, enableReHide: boolean ): Observable<{ visibilityRules: VisibilityRule[], + logicalConnectiveOfRules: 'disjunction' | 'conjunction', visibilityDelay: number, animatedVisibility: boolean, enableReHide: boolean @@ -177,6 +179,7 @@ export class DialogService { .open(VisibilityRulesDialogComponent, { data: { visibilityRules, + logicalConnectiveOfRules, controlIds, visibilityDelay, animatedVisibility, diff --git a/projects/editor/src/assets/i18n/de.json b/projects/editor/src/assets/i18n/de.json index 5a3ab1e7117250f59b671b435d7be177a5d19cda..c2b52d1503df020373a82aa80ee70ce0b3db74c2 100644 --- a/projects/editor/src/assets/i18n/de.json +++ b/projects/editor/src/assets/i18n/de.json @@ -40,14 +40,17 @@ "stateVariableId": "ID", "manageStateVariables": "Zustandsvariablen verwalten", "section": { - "manageVisibilityRules": "Sichtbarkeit verwalten", + "manageVisibilityRules": "Bedingungen zur Sichtbarkeit von Abschnitten bearbeiten", "animatedVisibility": "Einblenden mit Scroll-Animation", "visibilityDelay": "Verzögertes Einblenden (in ms)", "enableReHide": "Erneutes Ausblenden erlauben, wenn die Bedingungen nicht mehr erfüllt sind", "controlId": "Variablen/Element-ID", "operator": "Operator", "addElementsOrStateVariables": "Bitte zuerst Elemente oder Zustandsvariablen anlegen", - "value": "Wert" + "value": "Wert", + "rulesDisjunction": "ODER", + "rulesConjunction": "UND", + "logicalConnectiveOfRules": "Logische Verknüpfung der Bedingungen:" }, "visibilityRule": { "contains": "Enthält", diff --git a/projects/player/src/app/components/unit/unit.component.ts b/projects/player/src/app/components/unit/unit.component.ts index 47913142cc045ee17fd70b36feb652420bd6dcd2..b0ac32a2c63caaba5a4cf10593c7d5c98fbd4936 100644 --- a/projects/player/src/app/components/unit/unit.component.ts +++ b/projects/player/src/app/components/unit/unit.component.ts @@ -82,8 +82,8 @@ export class UnitComponent implements OnInit { } private checkUnitDefinitionVersion(unitDefinition: Record<string, unknown>): void { - if (!VersionManager.hasCompatibleVersion(unitDefinition)) { - if (VersionManager.isNewer(unitDefinition)) { + if (unitDefinition.version !== VersionManager.getCurrentVersion()) { + if (!VersionManager.hasCompatibleVersion(unitDefinition) && VersionManager.isNewer(unitDefinition)) { throw Error(this.translateService.instant('errorMessage.unitDefinitionIsNewer')); } throw Error(this.translateService.instant('errorMessage.unitDefinitionIsOutdated')); 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 72a3540f6faeb2391ccb556e0083cc0b7f2b5387..5523d3a4d104dc25873c601480189d47d57c29d5 100644 --- a/projects/player/src/app/directives/section-visibility-handling.directive.ts +++ b/projects/player/src/app/directives/section-visibility-handling.directive.ts @@ -10,6 +10,7 @@ import { ValueChangeElement } from 'common/models/elements/element'; import { ElementCode } from 'player/modules/verona/models/verona'; import { Storable } from 'player/src/app/classes/storable'; import { StateVariableStateService } from 'player/src/app/services/state-variable-state.service'; +import { VisibilityRule } from 'common/models/visibility-rule'; @Directive({ selector: '[aspectSectionVisibilityHandling]' @@ -140,39 +141,56 @@ export class SectionVisibilityHandlingDirective implements OnInit, OnDestroy { } private areVisibilityRulesFulfilled(): boolean { - return this.section.visibilityRules.some(rule => { + const methodName = this.section.logicalConnectiveOfRules === 'disjunction' ? 'some' : 'every'; + return this.section.visibilityRules[methodName](rule => { if (this.getAnyElementCodeById(rule.id)) { - const codeValue = this.getAnyElementCodeById(rule.id)?.value; - const value = codeValue || codeValue === 0 ? codeValue : ''; - switch (rule.operator) { - case '=': - return value.toString() === rule.value; - case '≠': - return value.toString() !== rule.value; - case '>': - return Number(value) > Number(rule.value); - case '<': - return Number(value) < Number(rule.value); - case '≥': - return Number(value) >= Number(rule.value); - case '≤': - return Number(value) <= Number(rule.value); - case 'contains': - return value.toString().includes(rule.value); - case 'pattern': - return SectionVisibilityHandlingDirective.isPatternMatching(value.toString(), rule.value); - case 'minLength': - return value.toString().length >= Number(rule.value); - case 'maxLength': - return value.toString().length <= Number(rule.value); - default: - return false; - } + return this.isRuleFullFilled(rule); } return false; }); } + isRuleFullFilled(rule: VisibilityRule): boolean { + let isFullFilled; + const codeValue = this.getAnyElementCodeById(rule.id)?.value; + const value = codeValue || codeValue === 0 ? codeValue : ''; + switch (rule.operator) { + case '=': + isFullFilled = value.toString() === rule.value; + break; + case '≠': + isFullFilled = value.toString() !== rule.value; + break; + case '>': + isFullFilled = Number(value) > Number(rule.value); + break; + case '<': + isFullFilled = Number(value) < Number(rule.value); + break; + case '≥': + isFullFilled = Number(value) >= Number(rule.value); + break; + case '≤': + isFullFilled = Number(value) <= Number(rule.value); + break; + case 'contains': + isFullFilled = value.toString().includes(rule.value); + break; + case 'pattern': + isFullFilled = SectionVisibilityHandlingDirective.isPatternMatching(value.toString(), rule.value); + break; + case 'minLength': + isFullFilled = value.toString().length >= Number(rule.value); + break; + case 'maxLength': + isFullFilled = value.toString().length <= Number(rule.value); + break; + default: + isFullFilled = false; + } + return isFullFilled; + } + private static isPatternMatching(value: string, ruleValue: string): boolean { // We use a similar implementation to Angular's PatternValidator let regexStr = (ruleValue.charAt(0) !== '^') ? `^${ruleValue}` : ruleValue;