From dae0f897b05a85781477e289b5ffab8539f0ac27 Mon Sep 17 00:00:00 2001 From: rhenck <richard.henck@iqb.hu-berlin.de> Date: Thu, 30 May 2024 17:25:06 +0200 Subject: [PATCH] [editor] Add static icons and names to element classes Separating code changes is rather hard right now, so there is a lot of stuff that is part of this commit that should not. Details below. - All elements have static fields that holds their title and icon. This way dynamic lists can be built (for example for delete dialog) which show title and icon for all elements. ALso icons and names can be changed in one place. - Template-Tab has been added. This should have templates to help creating groups of ui elements. This is work in progress and there are actually no templates yet. So this will change or even be removed in the future. This is mostly UI, the code is "TODO". - Refactoring and improvement for delete and reference dialogs - Delete logic refactored for elements, sections and pages. There are no longer different dialogs when references are found, but instead one dialog that can do both. --- .../components/reference-list.component.ts | 29 +- .../common/models/elements/button/button.ts | 3 + .../elements/compound-elements/cloze/cloze.ts | 3 + .../compound-elements/likert/likert.ts | 3 + .../common/models/elements/frame/frame.ts | 3 + .../models/elements/geometry/geometry.ts | 3 + .../elements/input-elements/checkbox.ts | 3 + .../elements/input-elements/drop-list.ts | 3 + .../elements/input-elements/dropdown.ts | 3 + .../elements/input-elements/hotspot-image.ts | 3 + .../elements/input-elements/math-field.ts | 3 + .../elements/input-elements/math-table.ts | 3 + .../radio-button-group-complex.ts | 3 + .../input-elements/radio-button-group.ts | 3 + .../models/elements/input-elements/slider.ts | 3 + .../elements/input-elements/spell-correct.ts | 3 + .../elements/input-elements/text-area-math.ts | 3 + .../elements/input-elements/text-area.ts | 3 + .../elements/input-elements/text-field.ts | 3 + .../models/elements/media-elements/audio.ts | 3 + .../models/elements/media-elements/image.ts | 3 + .../models/elements/media-elements/video.ts | 3 + projects/common/models/elements/text/text.ts | 3 + .../common/models/elements/trigger/trigger.ts | 3 + projects/common/models/section.ts | 4 + projects/editor/src/app/app.module.ts | 4 +- .../canvas/section-menu.component.ts | 27 +- .../dialogs/confirmation-dialog.component.ts | 22 -- .../delete-confirmation-dialog.component.ts | 46 +++ .../delete-reference-dialog.component.ts | 6 +- .../ui-element-toolbox.component.css | 15 + .../ui-element-toolbox.component.html | 348 ++++++++++-------- .../ui-element-toolbox.component.ts | 53 ++- .../unit-view/page-menu.component.ts | 38 +- .../editor/src/app/services/dialog.service.ts | 10 +- .../src/app/services/history.service.ts | 3 +- .../services/unit-services/element.service.ts | 170 +++++---- .../services/unit-services/page.service.ts | 21 +- .../services/unit-services/section.service.ts | 19 +- .../services/unit-services/unit.service.ts | 59 ++- 40 files changed, 564 insertions(+), 379 deletions(-) delete mode 100644 projects/editor/src/app/components/dialogs/confirmation-dialog.component.ts create mode 100644 projects/editor/src/app/components/dialogs/delete-confirmation-dialog.component.ts diff --git a/projects/common/components/reference-list.component.ts b/projects/common/components/reference-list.component.ts index 277a84939..be57f8584 100644 --- a/projects/common/components/reference-list.component.ts +++ b/projects/common/components/reference-list.component.ts @@ -2,8 +2,6 @@ import { Component, Inject, Input, Optional } from '@angular/core'; import { MAT_SNACK_BAR_DATA } from '@angular/material/snack-bar'; -import { UIElement } from 'common/models/elements/element'; -import { DropListElement } from 'common/models/elements/input-elements/drop-list'; import { ReferenceList } from 'editor/src/app/services/reference-manager'; @Component({ @@ -15,30 +13,11 @@ import { ReferenceList } from 'editor/src/app/services/reference-manager'; </span> <mat-list> <mat-list-item *ngFor="let element of refGroup.refs"> - <!--Grouping with ng-container does not work, because it break layouting with Material.--> - <mat-icon *ngIf="element.type == 'drop-list'" matListItemIcon> - drag_indicator + <mat-icon matListItemIcon> + {{$any(element.constructor).icon}} </mat-icon> - <div *ngIf="element.type == 'drop-list'" matListItemTitle> - Ablegeliste: {{element.id}} - </div> - <mat-icon *ngIf="element.type == 'button'" matListItemIcon> - smart_button - </mat-icon> - <div *ngIf="element.type == 'button'" matListItemTitle> - Knopf: {{element.id}} - </div> - <mat-icon *ngIf="element.type == 'audio'" matListItemIcon> - volume_up - </mat-icon> - <div *ngIf="element.type == 'audio'" matListItemTitle> - Audio: {{element.id}} - </div> - <mat-icon *ngIf="element.type == 'video'" matListItemIcon> - ondemand_video - </mat-icon> - <div *ngIf="element.type == 'video'" matListItemTitle> - Video: {{element.id}} + <div matListItemTitle> + {{$any(element.constructor).title}}: <i>{{element.id}}</i> </div> </mat-list-item> </mat-list> diff --git a/projects/common/models/elements/button/button.ts b/projects/common/models/elements/button/button.ts index a7556f6a2..a74b3cb8c 100644 --- a/projects/common/models/elements/button/button.ts +++ b/projects/common/models/elements/button/button.ts @@ -23,6 +23,9 @@ export class ButtonElement extends UIElement implements ButtonProperties { labelAlignment: 'super' | 'sub' | 'baseline' = 'baseline'; styling: BasicStyles & BorderStyles; + static title: string = 'Knopf'; + static icon: string = 'smart_button'; + constructor(element?: ButtonProperties) { super(element); if (element && isValid(element)) { diff --git a/projects/common/models/elements/compound-elements/cloze/cloze.ts b/projects/common/models/elements/compound-elements/cloze/cloze.ts index b9e156758..e5c90acd4 100644 --- a/projects/common/models/elements/compound-elements/cloze/cloze.ts +++ b/projects/common/models/elements/compound-elements/cloze/cloze.ts @@ -29,6 +29,9 @@ export class ClozeElement extends CompoundElement implements PositionedUIElement lineHeight: number; }; + static title: string = 'Lückentext'; + static icon: string = 'vertical_split'; + static validChildElements = ['TextField', 'DropList', 'ToggleButton', 'Button', 'Checkbox']; constructor(element?: ClozeProperties) { diff --git a/projects/common/models/elements/compound-elements/likert/likert.ts b/projects/common/models/elements/compound-elements/likert/likert.ts index e567cd743..248d5ed17 100644 --- a/projects/common/models/elements/compound-elements/likert/likert.ts +++ b/projects/common/models/elements/compound-elements/likert/likert.ts @@ -31,6 +31,9 @@ export class LikertElement extends CompoundElement implements PositionedUIElemen firstLineColoringColor: string; }; + static title: string = 'Optionentabelle'; + static icon: string = 'margin'; + constructor(element?: LikertProperties) { super(element); if (element && isValid(element)) { diff --git a/projects/common/models/elements/frame/frame.ts b/projects/common/models/elements/frame/frame.ts index 955eef5ed..e5571070f 100644 --- a/projects/common/models/elements/frame/frame.ts +++ b/projects/common/models/elements/frame/frame.ts @@ -19,6 +19,9 @@ export class FrameElement extends UIElement implements PositionedUIElement, Fram position: PositionProperties; styling: BorderStyles & { backgroundColor: string; }; + static title: string = 'Rahmen'; + static icon: string = 'crop_square'; + constructor(element?: FrameProperties) { super(element); if (element && isValid(element)) { diff --git a/projects/common/models/elements/geometry/geometry.ts b/projects/common/models/elements/geometry/geometry.ts index 93167390c..58329937e 100644 --- a/projects/common/models/elements/geometry/geometry.ts +++ b/projects/common/models/elements/geometry/geometry.ts @@ -25,6 +25,9 @@ export class GeometryElement extends UIElement implements PositionedUIElement, G customToolbar: string = ''; position: PositionProperties; + static title: string = 'Geometrie'; + static icon: string = 'architecture'; + constructor(element?: GeometryProperties) { super(element); if (element && isValid(element)) { diff --git a/projects/common/models/elements/input-elements/checkbox.ts b/projects/common/models/elements/input-elements/checkbox.ts index 821b21187..2d602d4d7 100644 --- a/projects/common/models/elements/input-elements/checkbox.ts +++ b/projects/common/models/elements/input-elements/checkbox.ts @@ -17,6 +17,9 @@ export class CheckboxElement extends InputElement implements CheckboxProperties crossOutChecked: boolean = false; styling: BasicStyles; + static title: string = 'Kontrollkästchen'; + static icon: string = 'check_box'; + constructor(element?: CheckboxProperties) { super(element); if (element && isValid(element)) { diff --git a/projects/common/models/elements/input-elements/drop-list.ts b/projects/common/models/elements/input-elements/drop-list.ts index 21e108403..30a2c5401 100644 --- a/projects/common/models/elements/input-elements/drop-list.ts +++ b/projects/common/models/elements/input-elements/drop-list.ts @@ -27,6 +27,9 @@ export class DropListElement extends InputElement implements DropListProperties itemBackgroundColor: string; }; + static title: string = 'Ablegeliste'; + static icon: string = 'drag_indicator'; + constructor(element?: DropListProperties) { super(element); if (element && isValid(element)) { diff --git a/projects/common/models/elements/input-elements/dropdown.ts b/projects/common/models/elements/input-elements/dropdown.ts index 2d0894b22..c52babd2f 100644 --- a/projects/common/models/elements/input-elements/dropdown.ts +++ b/projects/common/models/elements/input-elements/dropdown.ts @@ -19,6 +19,9 @@ export class DropdownElement extends InputElement implements PositionedUIElement position: PositionProperties; styling: BasicStyles; + static title: string = 'Klappliste'; + static icon: string = 'menu_open'; + constructor(element?: DropdownProperties) { super(element); if (element && isValid(element)) { diff --git a/projects/common/models/elements/input-elements/hotspot-image.ts b/projects/common/models/elements/input-elements/hotspot-image.ts index a3ca59417..257726184 100644 --- a/projects/common/models/elements/input-elements/hotspot-image.ts +++ b/projects/common/models/elements/input-elements/hotspot-image.ts @@ -34,6 +34,9 @@ export class HotspotImageElement extends InputElement implements PositionedUIEle src: string | null = null; position: PositionProperties; + static title: string = 'Bildbereiche'; + static icon: string = 'ads_click'; + constructor(element?: HotspotImageProperties) { super(element); if (element && isValid(element)) { diff --git a/projects/common/models/elements/input-elements/math-field.ts b/projects/common/models/elements/input-elements/math-field.ts index 63dc35751..06f45c283 100644 --- a/projects/common/models/elements/input-elements/math-field.ts +++ b/projects/common/models/elements/input-elements/math-field.ts @@ -19,6 +19,9 @@ export class MathFieldElement extends InputElement implements MathFieldPropertie lineHeight: number; }; + static title: string = 'Formelfeld'; + static icon: string = 'calculate'; + constructor(element?: MathFieldProperties) { super(element); if (element && isValid(element)) { diff --git a/projects/common/models/elements/input-elements/math-table.ts b/projects/common/models/elements/input-elements/math-table.ts index ce6564be3..435994da0 100644 --- a/projects/common/models/elements/input-elements/math-table.ts +++ b/projects/common/models/elements/input-elements/math-table.ts @@ -47,6 +47,9 @@ export class MathTableElement extends UIElement implements MathTableProperties, helperRowColor: string; }; + static title: string = 'Rechenkästchen'; + static icon: string = 'apps'; + constructor(element?: MathTableProperties) { super(element); if (element && isValid(element)) { diff --git a/projects/common/models/elements/input-elements/radio-button-group-complex.ts b/projects/common/models/elements/input-elements/radio-button-group-complex.ts index 2fe3e1563..75d016be3 100644 --- a/projects/common/models/elements/input-elements/radio-button-group-complex.ts +++ b/projects/common/models/elements/input-elements/radio-button-group-complex.ts @@ -20,6 +20,9 @@ export class RadioButtonGroupComplexElement extends InputElement position: PositionProperties; styling: BasicStyles; + static title: string = 'Optionsfelder (mit Bild)'; + static icon: string = 'radio_button_checked'; + constructor(element?: RadioButtonGroupComplexProperties) { super(element); if (element && isValid(element)) { diff --git a/projects/common/models/elements/input-elements/radio-button-group.ts b/projects/common/models/elements/input-elements/radio-button-group.ts index 57f58316a..5f1bbcd4f 100644 --- a/projects/common/models/elements/input-elements/radio-button-group.ts +++ b/projects/common/models/elements/input-elements/radio-button-group.ts @@ -23,6 +23,9 @@ export class RadioButtonGroupElement extends InputElement lineHeight: number; }; + static title: string = 'Optionsfelder'; + static icon: string = 'radio_button_checked'; + constructor(element?: RadioButtonGroupProperties) { super(element); if (element && isValid(element)) { diff --git a/projects/common/models/elements/input-elements/slider.ts b/projects/common/models/elements/input-elements/slider.ts index 5e1f5f89a..ee74e1074 100644 --- a/projects/common/models/elements/input-elements/slider.ts +++ b/projects/common/models/elements/input-elements/slider.ts @@ -23,6 +23,9 @@ export class SliderElement extends InputElement implements PositionedUIElement, lineHeight: number; }; + static title: string = 'Schieberegler'; + static icon: string = 'linear_scale'; + constructor(element?: SliderProperties) { super(element); if (element && isValid(element)) { diff --git a/projects/common/models/elements/input-elements/spell-correct.ts b/projects/common/models/elements/input-elements/spell-correct.ts index 1d7c7c7c9..f78013227 100644 --- a/projects/common/models/elements/input-elements/spell-correct.ts +++ b/projects/common/models/elements/input-elements/spell-correct.ts @@ -16,6 +16,9 @@ export class SpellCorrectElement extends TextInputElement implements PositionedU position: PositionProperties; styling: BasicStyles; + static title: string = 'Wort korrigieren'; + static icon: string = 'format_strikethrough'; + constructor(element?: SpellCorrectProperties) { super(element); if (element && isValid(element)) { diff --git a/projects/common/models/elements/input-elements/text-area-math.ts b/projects/common/models/elements/input-elements/text-area-math.ts index 66bc277a7..2babf8cae 100644 --- a/projects/common/models/elements/input-elements/text-area-math.ts +++ b/projects/common/models/elements/input-elements/text-area-math.ts @@ -24,6 +24,9 @@ export class TextAreaMathElement extends InputElement implements TextAreaMathPro lineHeight: number; }; + static title: string = 'Formelbereich'; + static icon: string = 'calculate'; + constructor(element?: TextAreaMathProperties) { super(element); if (element && isValid(element)) { diff --git a/projects/common/models/elements/input-elements/text-area.ts b/projects/common/models/elements/input-elements/text-area.ts index 4b7575655..23a65de9c 100644 --- a/projects/common/models/elements/input-elements/text-area.ts +++ b/projects/common/models/elements/input-elements/text-area.ts @@ -26,6 +26,9 @@ export class TextAreaElement extends TextInputElement implements PositionedUIEle lineHeight: number; }; + static title: string = 'Eingabebereich'; + static icon: string = 'edit_note'; + constructor(element?: TextAreaProperties) { super(element); if (element && isValid(element)) { diff --git a/projects/common/models/elements/input-elements/text-field.ts b/projects/common/models/elements/input-elements/text-field.ts index 0156b920a..4b891e2a9 100644 --- a/projects/common/models/elements/input-elements/text-field.ts +++ b/projects/common/models/elements/input-elements/text-field.ts @@ -28,6 +28,9 @@ export class TextFieldElement extends TextInputElement implements PositionedUIEl lineHeight: number; }; + static title: string = 'Eingabefeld'; + static icon: string = 'edit'; + constructor(element?: TextFieldProperties) { super(element); if (element && isValid(element)) { diff --git a/projects/common/models/elements/media-elements/audio.ts b/projects/common/models/elements/media-elements/audio.ts index d5dde5b28..23b6fd302 100644 --- a/projects/common/models/elements/media-elements/audio.ts +++ b/projects/common/models/elements/media-elements/audio.ts @@ -15,6 +15,9 @@ export class AudioElement extends PlayerElement implements PositionedUIElement, src: string | null = null; position: PositionProperties; + static title: string = 'Audio'; + static icon: string = 'volume_up'; + constructor(element?: AudioProperties) { super(element); if (element && isValid(element)) { diff --git a/projects/common/models/elements/media-elements/image.ts b/projects/common/models/elements/media-elements/image.ts index 46c13deac..340612c7b 100644 --- a/projects/common/models/elements/media-elements/image.ts +++ b/projects/common/models/elements/media-elements/image.ts @@ -22,6 +22,9 @@ export class ImageElement extends UIElement implements PositionedUIElement, Imag magnifierUsed: boolean = false; position: PositionProperties; + static title: string = 'Bild'; + static icon: string = 'image'; + constructor(element?: ImageProperties) { super(element); if (element && isValid(element)) { diff --git a/projects/common/models/elements/media-elements/video.ts b/projects/common/models/elements/media-elements/video.ts index d26ed22c3..cf22cbbce 100644 --- a/projects/common/models/elements/media-elements/video.ts +++ b/projects/common/models/elements/media-elements/video.ts @@ -16,6 +16,9 @@ export class VideoElement extends PlayerElement implements PositionedUIElement, scale: boolean = false; position: PositionProperties; + static title: string = 'Video'; + static icon: string = 'ondemand_video'; + constructor(element?: VideoProperties) { super(element); if (element && isValid(element)) { diff --git a/projects/common/models/elements/text/text.ts b/projects/common/models/elements/text/text.ts index 0e6f67a5e..6a432ba0c 100644 --- a/projects/common/models/elements/text/text.ts +++ b/projects/common/models/elements/text/text.ts @@ -26,6 +26,9 @@ export class TextElement extends UIElement implements PositionedUIElement, TextP lineHeight: number; }; + static title: string = 'Text'; + static icon: string = 'text_snippet'; + constructor(element?: TextProperties) { super(element); if (element && isValid(element)) { diff --git a/projects/common/models/elements/trigger/trigger.ts b/projects/common/models/elements/trigger/trigger.ts index e79f0800b..c581e9e40 100644 --- a/projects/common/models/elements/trigger/trigger.ts +++ b/projects/common/models/elements/trigger/trigger.ts @@ -13,6 +13,9 @@ export class TriggerElement extends UIElement implements TriggerProperties { action: null | TriggerAction = null; actionParam: null | string | StateVariable = null; + static title: string = 'Auslöser'; + static icon: string = 'bolt'; + constructor(element?: TriggerProperties) { super(element); if (element && isValid(element)) { diff --git a/projects/common/models/section.ts b/projects/common/models/section.ts index b0b0d3b09..fe57cca4c 100644 --- a/projects/common/models/section.ts +++ b/projects/common/models/section.ts @@ -74,6 +74,10 @@ export class Section { this.elements.push(element); } + deleteElement(id: string): void { + this.elements = this.elements.filter(el => el.id !== id); + } + /* Includes children of children, i.e. compound children. */ getAllElements(elementType?: string): UIElement[] { let allElements: UIElement[] = diff --git a/projects/editor/src/app/app.module.ts b/projects/editor/src/app/app.module.ts index 355416d17..361ceb9c8 100644 --- a/projects/editor/src/app/app.module.ts +++ b/projects/editor/src/app/app.module.ts @@ -71,7 +71,7 @@ import { SectionMenuComponent } from './components/canvas/section-menu.component import { SectionStaticComponent } from './components/canvas/section-static.component'; import { SectionDynamicComponent } from './components/canvas/section-dynamic.component'; import { RichTextEditorComponent } from './text-editor/rich-text-editor.component'; -import { ConfirmationDialogComponent } from './components/dialogs/confirmation-dialog.component'; +import { DeleteConfirmationDialogComponent } from './components/dialogs/delete-confirmation-dialog.component'; import { TextEditDialogComponent } from './components/dialogs/text-edit-dialog.component'; import { TextEditMultilineDialogComponent } from './components/dialogs/text-edit-multiline-dialog.component'; import { @@ -155,7 +155,7 @@ export const myCustomTooltipDefaults: MatTooltipDefaultOptions = { ButtonNodeviewComponent, ElementStylePropertiesComponent, ElementPositionPropertiesComponent, - ConfirmationDialogComponent, + DeleteConfirmationDialogComponent, TextEditDialogComponent, TextEditMultilineDialogComponent, PlayerEditDialogComponent, 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 3155ba6d6..e7ac740f8 100644 --- a/projects/editor/src/app/components/canvas/section-menu.component.ts +++ b/projects/editor/src/app/components/canvas/section-menu.component.ts @@ -203,32 +203,7 @@ export class SectionMenuComponent implements OnDestroy { } deleteSection(): void { - const refs = - this.unitService.referenceManager.getSectionElementsReferences([this.section]); - if (refs.length > 0) { - this.dialogService.showDeleteReferenceDialog(refs) - .pipe(takeUntil(this.ngUnsubscribe)) - .subscribe((result: boolean) => { - if (result) { - ReferenceManager.deleteReferences(refs); - this.sectionService.deleteSection(this.selectionService.selectedPageIndex, this.sectionIndex); - this.selectionService.selectedSectionIndex = - Math.max(0, this.selectionService.selectedSectionIndex - 1); - } else { - this.messageService.showReferencePanel(refs); - } - }); - } else { - this.dialogService.showConfirmDialog('Abschnitt löschen?') - .pipe(takeUntil(this.ngUnsubscribe)) - .subscribe((result: boolean) => { - if (result) { - this.sectionService.deleteSection(this.selectionService.selectedPageIndex, this.sectionIndex); - this.selectionService.selectedSectionIndex = - Math.max(0, this.selectionService.selectedSectionIndex - 1); - } - }); - } + this.sectionService.deleteSection(this.selectionService.selectedPageIndex, this.sectionIndex); } /* Add or remove elements to size array. Default value 1fr. */ diff --git a/projects/editor/src/app/components/dialogs/confirmation-dialog.component.ts b/projects/editor/src/app/components/dialogs/confirmation-dialog.component.ts deleted file mode 100644 index f59ae93fc..000000000 --- a/projects/editor/src/app/components/dialogs/confirmation-dialog.component.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { Component, Inject } from '@angular/core'; -import { MAT_DIALOG_DATA } from '@angular/material/dialog'; - -@Component({ - selector: 'aspect-confirmation-dialog', - template: ` - <div mat-dialog-title>Bestätigen</div> - <div mat-dialog-content [class.warning] = "data.isWarning"> - {{data.text}} - </div> - <div mat-dialog-actions> - <button mat-button [mat-dialog-close]="true">{{'confirm' | translate }}</button> - <button mat-button mat-dialog-close>{{'cancel' | translate }}</button> - </div> - `, - styles: [ - '.warning {color: red;}' - ] -}) -export class ConfirmationDialogComponent { - constructor(@Inject(MAT_DIALOG_DATA) public data: { text: string, isWarning: boolean }) { } -} diff --git a/projects/editor/src/app/components/dialogs/delete-confirmation-dialog.component.ts b/projects/editor/src/app/components/dialogs/delete-confirmation-dialog.component.ts new file mode 100644 index 000000000..7aa0f8bd4 --- /dev/null +++ b/projects/editor/src/app/components/dialogs/delete-confirmation-dialog.component.ts @@ -0,0 +1,46 @@ +import { Component, Inject } from '@angular/core'; +import { MAT_DIALOG_DATA } from '@angular/material/dialog'; +import { ReferenceList } from 'editor/src/app/services/reference-manager'; +import { UIElement } from 'common/models/elements/element'; + +@Component({ + selector: 'aspect-confirmation-dialog', + template: ` + <div mat-dialog-title>Löschen bestätigen</div> + <div mat-dialog-content> + {{data.text}} + <mat-list *ngIf="data.elementList && data.elementList.length > 0"> + <mat-list-item *ngFor="let element of data.elementList"> + <mat-icon matListItemIcon> + {{$any(element.constructor).icon}} + </mat-icon> + <div matListItemTitle> + {{$any(element.constructor).title}}: <i>{{element.id}}</i> + </div> + </mat-list-item> + </mat-list> + <fieldset *ngIf="data.refs && data.refs.length > 0"> + <legend [style.background-color]="'orange'">Referenzen festgestellt</legend> + <aspect-reference-list [refs]="data.refs"></aspect-reference-list> + <p [style.color]="'orange'" [style.font-weight]="'bold'"> + Gefundene Referenzen werden entfernt.</p> + <p [style.color]="'orange'" [style.font-weight]="'bold'"> + Entfernte Referenzen werden beim Rückgängigmachen nicht wiederhergestellt.</p> + <p>Bei Abbruch wird eine Liste der Referenzen zur manuellen Überprüfung abgezeigt.</p> + </fieldset> + </div> + <div mat-dialog-actions> + <button mat-button [mat-dialog-close]="true">{{'confirm' | translate }}</button> + <button mat-button mat-dialog-close>{{'cancel' | translate }}</button> + </div> + `, + styles: [ + ':host ::ng-deep mat-icon {margin-right: 6px !important;}', + 'fieldset {margin-top: 10px !important;}' + ] +}) +export class DeleteConfirmationDialogComponent { + constructor(@Inject(MAT_DIALOG_DATA) public data: { text: string, elementList?: UIElement[], refs?: ReferenceList[] }) { + console.log('dialog refs', this.data); + } +} diff --git a/projects/editor/src/app/components/dialogs/delete-reference-dialog.component.ts b/projects/editor/src/app/components/dialogs/delete-reference-dialog.component.ts index bfd5756f1..49e6135c2 100644 --- a/projects/editor/src/app/components/dialogs/delete-reference-dialog.component.ts +++ b/projects/editor/src/app/components/dialogs/delete-reference-dialog.component.ts @@ -10,8 +10,7 @@ import { ReferenceList } from 'editor/src/app/services/reference-manager'; <aspect-reference-list [refs]="data.refs"></aspect-reference-list> </div> <div mat-dialog-actions> - <button mat-button - [mat-dialog-close]="false"> + <button mat-button [mat-dialog-close]="false"> Abbrechen </button> <button mat-button [matTooltipPosition]="'above'" @@ -23,7 +22,8 @@ import { ReferenceList } from 'editor/src/app/services/reference-manager'; </div> `, styles: [ - '.mat-mdc-dialog-content {display: flex; flex-direction: column;}' + '.mat-mdc-dialog-content {display: flex; flex-direction: column;}', + ':host ::ng-deep mat-icon {margin-right: 6px !important;}' ] }) export class DeleteReferenceDialogComponent { diff --git a/projects/editor/src/app/components/new-ui-element-panel/ui-element-toolbox.component.css b/projects/editor/src/app/components/new-ui-element-panel/ui-element-toolbox.component.css index 6df762f2a..aa3e911b2 100644 --- a/projects/editor/src/app/components/new-ui-element-panel/ui-element-toolbox.component.css +++ b/projects/editor/src/app/components/new-ui-element-panel/ui-element-toolbox.component.css @@ -6,6 +6,10 @@ button { display: inline-block; } +.template-list { + margin-top: 10px; +} + .mat-expansion-panel-header { font-size: large; } @@ -36,3 +40,14 @@ button { .button-group { border: 3px groove; } + +mat-tab-group { + overflow: auto; + padding: 0 5px; +} + +:host ::ng-deep .mat-mdc-tab-body-wrapper {height: 100%} + +:host ::ng-deep .mdc-tab { + min-width: unset; +} diff --git a/projects/editor/src/app/components/new-ui-element-panel/ui-element-toolbox.component.html b/projects/editor/src/app/components/new-ui-element-panel/ui-element-toolbox.component.html index f117b4da0..b5ec7d44b 100644 --- a/projects/editor/src/app/components/new-ui-element-panel/ui-element-toolbox.component.html +++ b/projects/editor/src/app/components/new-ui-element-panel/ui-element-toolbox.component.html @@ -1,166 +1,190 @@ <div class="fx-column-start-stretch fill no-overflow"> - <div class="fx-flex no-overflow"> - <mat-accordion multi > - <mat-expansion-panel expanded> - <mat-expansion-panel-header> - <mat-panel-description> - Medium - </mat-panel-description> - </mat-expansion-panel-header> - <button mat-stroked-button (click)="addUIElement('text')" - draggable="true" (dragstart)="$event.dataTransfer?.setData('elementType','text');"> - <mat-icon>text_snippet</mat-icon> - {{'toolbox.text' | translate }} - </button> - <button mat-stroked-button (click)="addUIElement('image')" - draggable="true" (dragstart)="$event.dataTransfer?.setData('elementType','image')"> - <mat-icon>image</mat-icon> - {{'toolbox.image' | translate }} - </button> - <button mat-stroked-button (click)="addUIElement('audio')" - draggable="true" (dragstart)="$event.dataTransfer?.setData('elementType','audio')"> - <mat-icon>volume_up</mat-icon> - {{'toolbox.audio' | translate }} - </button> - <button mat-stroked-button (click)="addUIElement('video')" - draggable="true" (dragstart)="$event.dataTransfer?.setData('elementType','video')"> - <mat-icon>ondemand_video</mat-icon> - {{'toolbox.video' | translate }} - </button> - </mat-expansion-panel> - <mat-expansion-panel expanded> - <mat-expansion-panel-header> - <mat-panel-description> - Eingabe - </mat-panel-description> - </mat-expansion-panel-header> - <button mat-stroked-button (click)="addUIElement('text-field')" - draggable="true" (dragstart)="$event.dataTransfer?.setData('elementType','text-field')"> - <mat-icon>edit</mat-icon> - {{'toolbox.text-field' | translate }} - </button> - <button mat-stroked-button (click)="addUIElement('text-area')" - draggable="true" (dragstart)="$event.dataTransfer?.setData('elementType','text-area')"> - <mat-icon>edit_note</mat-icon> - {{'toolbox.text-area' | translate }} - </button> - <button mat-stroked-button (click)="addUIElement('cloze')" - draggable="true" (dragstart)="$event.dataTransfer?.setData('elementType','cloze')"> - <mat-icon>vertical_split</mat-icon> - {{'toolbox.cloze' | translate }} - </button> - <button mat-stroked-button (click)="addUIElement('spell-correct')" - draggable="true" (dragstart)="$event.dataTransfer?.setData('elementType','spell-correct')"> - <mat-icon>format_strikethrough</mat-icon> - {{'toolbox.spell-correct' | translate }} - </button> - <div class="fx-row-start-stretch" (mouseover)="hoverFormulaButton = true" (mouseleave)="hoverFormulaButton = false"> - <button *ngIf="!hoverFormulaButton" mat-stroked-button> - <mat-icon>calculate</mat-icon> - {{'toolbox.formula' | translate }} - </button> - <button *ngIf="hoverFormulaButton" mat-raised-button (click)="addUIElement('math-field')" - draggable="true" (dragstart)="$event.dataTransfer?.setData('elementType','math-field')"> - {{'toolbox.math-field' | translate }} - </button> - <button *ngIf="hoverFormulaButton" mat-raised-button (click)="addUIElement('text-area-math')" - draggable="true" (dragstart)="$event.dataTransfer?.setData('elementType','text-area-math')"> - {{'toolbox.math-area' | translate }} - </button> - </div> - <button mat-stroked-button (click)="addUIElement('math-table')" - draggable="true" (dragstart)="$event.dataTransfer?.setData('elementType','math-table');"> - <mat-icon>apps</mat-icon> - Rechenkästchen - </button> - </mat-expansion-panel> - <mat-expansion-panel expanded> - <mat-expansion-panel-header> - <mat-panel-description> - Auswahl - </mat-panel-description> - </mat-expansion-panel-header> - <div class="fx-row-start-stretch" (mouseover)="hoverRadioButton = true" (mouseleave)="hoverRadioButton = false"> - <button *ngIf="!hoverRadioButton" mat-stroked-button> - <mat-icon>radio_button_checked</mat-icon> - {{'toolbox.radio' | translate }} - </button> - <button *ngIf="hoverRadioButton" mat-raised-button (click)="addUIElement('radio')" - draggable="true" (dragstart)="$event.dataTransfer?.setData('elementType','radio')"> - {{'toolbox.simple' | translate }} - </button> - <button *ngIf="hoverRadioButton" mat-raised-button (click)="addUIElement('radio-group-images')" - draggable="true" (dragstart)="$event.dataTransfer?.setData('elementType','radio-group-images')"> - {{'toolbox.complex' | translate }} - </button> - </div> - <button mat-stroked-button (click)="addUIElement('likert')" - draggable="true" (dragstart)="$event.dataTransfer?.setData('elementType','likert')"> - <mat-icon>margin</mat-icon> - {{'toolbox.likert' | translate }} - </button> - <button mat-stroked-button (click)="addUIElement('dropdown')" - draggable="true" (dragstart)="$event.dataTransfer?.setData('elementType','dropdown')"> - <mat-icon>menu_open</mat-icon> - {{'toolbox.dropdown' | translate }} - </button> - <button mat-stroked-button (click)="addUIElement('checkbox')" - draggable="true" (dragstart)="$event.dataTransfer?.setData('elementType','checkbox')"> - <mat-icon>check_box</mat-icon> - {{'toolbox.checkbox' | translate }} - </button> - <button mat-stroked-button (click)="addUIElement('slider')" - draggable="true" (dragstart)="$event.dataTransfer?.setData('elementType','slider')"> - <mat-icon>linear_scale</mat-icon> - {{'toolbox.slider' | translate }} - </button> - <button mat-stroked-button (click)="addUIElement('hotspot-image')" - draggable="true" (dragstart)="$event.dataTransfer?.setData('elementType','hotspot-image')"> - <mat-icon>ads_click</mat-icon> - {{'toolbox.hotspot-image' | translate }} - </button> - </mat-expansion-panel> - <mat-expansion-panel> - <mat-expansion-panel-header> - <mat-panel-description> - (Zu)Ordnung - </mat-panel-description> - </mat-expansion-panel-header> - <button mat-stroked-button (click)="addUIElement('drop-list')" - draggable="true" (dragstart)="$event.dataTransfer?.setData('elementType','drop-list')"> - <mat-icon>drag_indicator</mat-icon> - {{'toolbox.drop-list' | translate }} - </button> - </mat-expansion-panel> - <mat-expansion-panel> - <mat-expansion-panel-header> - <mat-panel-description> - Sonstige - </mat-panel-description> - </mat-expansion-panel-header> - <button mat-stroked-button (click)="addUIElement('button')" - draggable="true" (dragstart)="$event.dataTransfer?.setData('elementType','button')"> - <mat-icon>smart_button</mat-icon> - {{'toolbox.button' | translate }} - </button> - <button mat-stroked-button (click)="addUIElement('frame')" - draggable="true" (dragstart)="$event.dataTransfer?.setData('elementType','frame')"> - <mat-icon>crop_square</mat-icon> - {{'toolbox.frame' | translate }} - </button> - <button mat-stroked-button (click)="addUIElement('geometry')" - draggable="true" (dragstart)="$event.dataTransfer?.setData('elementType','geometry');"> - <mat-icon>architecture</mat-icon> - {{'toolbox.geometry' | translate }} - </button> - <button mat-stroked-button (click)="addUIElement('trigger')" - draggable="true" (dragstart)="$event.dataTransfer?.setData('elementType','trigger')"> - <mat-icon>bolt</mat-icon> - {{'toolbox.trigger' | translate }} - </button> - </mat-expansion-panel> - </mat-accordion> - </div> + <mat-tab-group class="fx-flex" mat-stretch-tabs> + <mat-tab> + <ng-template mat-tab-label> + <mat-icon class="example-tab-icon" [matTooltip]="'Elementliste'"> + directions_walk + </mat-icon> + </ng-template> + <div class="fx-flex no-overflow element-list"> + <mat-accordion multi > + <mat-expansion-panel expanded> + <mat-expansion-panel-header> + <mat-panel-description> + Medium + </mat-panel-description> + </mat-expansion-panel-header> + <button mat-stroked-button (click)="addUIElement('text')" + draggable="true" (dragstart)="$event.dataTransfer?.setData('elementType','text');"> + <mat-icon>{{TextElement.icon}}</mat-icon> + {{TextElement.title}} + </button> + <button mat-stroked-button (click)="addUIElement('image')" + draggable="true" (dragstart)="$event.dataTransfer?.setData('elementType','image')"> + <mat-icon>{{ImageElement.icon}}</mat-icon> + {{ImageElement.title}} + </button> + <button mat-stroked-button (click)="addUIElement('audio')" + draggable="true" (dragstart)="$event.dataTransfer?.setData('elementType','audio')"> + <mat-icon>{{AudioElement.icon}}</mat-icon> + {{AudioElement.title}} + </button> + <button mat-stroked-button (click)="addUIElement('video')" + draggable="true" (dragstart)="$event.dataTransfer?.setData('elementType','video')"> + <mat-icon>{{VideoElement.icon}}</mat-icon> + {{VideoElement.title}} + </button> + </mat-expansion-panel> + <mat-expansion-panel expanded> + <mat-expansion-panel-header> + <mat-panel-description> + Eingabe + </mat-panel-description> + </mat-expansion-panel-header> + <button mat-stroked-button (click)="addUIElement('text-field')" + draggable="true" (dragstart)="$event.dataTransfer?.setData('elementType','text-field')"> + <mat-icon>{{TextFieldElement.icon}}</mat-icon> + {{TextFieldElement.title}} + </button> + <button mat-stroked-button (click)="addUIElement('text-area')" + draggable="true" (dragstart)="$event.dataTransfer?.setData('elementType','text-area')"> + <mat-icon>{{TextAreaElement.icon}}</mat-icon> + {{TextAreaElement.title}} + </button> + <button mat-stroked-button (click)="addUIElement('cloze')" + draggable="true" (dragstart)="$event.dataTransfer?.setData('elementType','cloze')"> + <mat-icon>{{ClozeElement.icon}}</mat-icon> + {{ClozeElement.title}} + </button> + <button mat-stroked-button (click)="addUIElement('spell-correct')" + draggable="true" (dragstart)="$event.dataTransfer?.setData('elementType','spell-correct')"> + <mat-icon>{{SpellCorrectElement.icon}}</mat-icon> + {{SpellCorrectElement.title}} + </button> + <div class="fx-row-start-stretch" (mouseover)="hoverFormulaButton = true" (mouseleave)="hoverFormulaButton = false"> + <button *ngIf="!hoverFormulaButton" mat-stroked-button> + <mat-icon>calculate</mat-icon> + {{'toolbox.formula' | translate }} + </button> + <button *ngIf="hoverFormulaButton" mat-raised-button (click)="addUIElement('math-field')" + draggable="true" (dragstart)="$event.dataTransfer?.setData('elementType','math-field')"> + {{'toolbox.math-field' | translate }} + </button> + <button *ngIf="hoverFormulaButton" mat-raised-button (click)="addUIElement('text-area-math')" + draggable="true" (dragstart)="$event.dataTransfer?.setData('elementType','text-area-math')"> + {{'toolbox.math-area' | translate }} + </button> + </div> + <button mat-stroked-button (click)="addUIElement('math-table')" + draggable="true" (dragstart)="$event.dataTransfer?.setData('elementType','math-table');"> + <mat-icon>{{MathTableElement.icon}}</mat-icon> + {{MathTableElement.title}} + </button> + </mat-expansion-panel> + <mat-expansion-panel expanded> + <mat-expansion-panel-header> + <mat-panel-description> + Auswahl + </mat-panel-description> + </mat-expansion-panel-header> + <div class="fx-row-start-stretch" (mouseover)="hoverRadioButton = true" (mouseleave)="hoverRadioButton = false"> + <button *ngIf="!hoverRadioButton" mat-stroked-button> + <mat-icon>radio_button_checked</mat-icon> + {{'toolbox.radio' | translate }} + </button> + <button *ngIf="hoverRadioButton" mat-raised-button (click)="addUIElement('radio')" + draggable="true" (dragstart)="$event.dataTransfer?.setData('elementType','radio')"> + {{'toolbox.simple' | translate }} + </button> + <button *ngIf="hoverRadioButton" mat-raised-button (click)="addUIElement('radio-group-images')" + draggable="true" (dragstart)="$event.dataTransfer?.setData('elementType','radio-group-images')"> + {{'toolbox.complex' | translate }} + </button> + </div> + <button mat-stroked-button (click)="addUIElement('likert')" + draggable="true" (dragstart)="$event.dataTransfer?.setData('elementType','likert')"> + <mat-icon>{{LikertElement.icon}}</mat-icon> + {{LikertElement.title}} + </button> + <button mat-stroked-button (click)="addUIElement('dropdown')" + draggable="true" (dragstart)="$event.dataTransfer?.setData('elementType','dropdown')"> + <mat-icon>{{DropdownElement.icon}}</mat-icon> + {{DropdownElement.title}} + </button> + <button mat-stroked-button (click)="addUIElement('checkbox')" + draggable="true" (dragstart)="$event.dataTransfer?.setData('elementType','checkbox')"> + <mat-icon>{{CheckboxElement.icon}}</mat-icon> + {{CheckboxElement.title}} + </button> + <button mat-stroked-button (click)="addUIElement('slider')" + draggable="true" (dragstart)="$event.dataTransfer?.setData('elementType','slider')"> + <mat-icon>{{SliderElement.icon}}</mat-icon> + {{SliderElement.title}} + </button> + <button mat-stroked-button (click)="addUIElement('hotspot-image')" + draggable="true" (dragstart)="$event.dataTransfer?.setData('elementType','hotspot-image')"> + <mat-icon>{{HotspotImageElement.icon}}</mat-icon> + {{HotspotImageElement.title}} + </button> + </mat-expansion-panel> + <mat-expansion-panel> + <mat-expansion-panel-header> + <mat-panel-description> + (Zu)Ordnung + </mat-panel-description> + </mat-expansion-panel-header> + <button mat-stroked-button (click)="addUIElement('drop-list')" + draggable="true" (dragstart)="$event.dataTransfer?.setData('elementType','drop-list')"> + <mat-icon>{{DropListElement.icon}}</mat-icon> + {{DropListElement.title}} + </button> + </mat-expansion-panel> + <mat-expansion-panel> + <mat-expansion-panel-header> + <mat-panel-description> + Sonstige + </mat-panel-description> + </mat-expansion-panel-header> + <button mat-stroked-button (click)="addUIElement('button')" + draggable="true" (dragstart)="$event.dataTransfer?.setData('elementType','button')"> + <mat-icon>{{ButtonElement.icon}}</mat-icon> + {{ButtonElement.title}} + </button> + <button mat-stroked-button (click)="addUIElement('frame')" + draggable="true" (dragstart)="$event.dataTransfer?.setData('elementType','frame')"> + <mat-icon>{{FrameElement.icon}}</mat-icon> + {{FrameElement.title}} + </button> + <button mat-stroked-button (click)="addUIElement('geometry')" + draggable="true" (dragstart)="$event.dataTransfer?.setData('elementType','geometry');"> + <mat-icon>{{GeometryElement.icon}}</mat-icon> + {{GeometryElement.title}} + </button> + <button mat-stroked-button (click)="addUIElement('trigger')" + draggable="true" (dragstart)="$event.dataTransfer?.setData('elementType','trigger')"> + <mat-icon>{{TriggerElement.icon}}</mat-icon> + {{TriggerElement.title}} + </button> + </mat-expansion-panel> + </mat-accordion> + </div> + </mat-tab> + + <mat-tab> + <ng-template mat-tab-label> + <mat-icon class="example-tab-icon" [matTooltip]="'Vorlagen'"> + <svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#5f6368"> + <path d="m520-120 34-331q-57-15-86-39.5T410-544l-95 94 85 85v245h-80v-210l-31-28 7 54-147 189-63-49 126-162-57-112q-8-17-9-42.5t17-43.5l134-132q12-12 26.5-18t29.5-6q24 0 38 9t19 14l80 79q27 27 66 42.5t84 15.5h66q23 0 40 15.5t19 38.5l26 254q13 8 21 21.5t8 30.5q0 25-17.5 42.5T760-100q-25 0-43-17.5T699-160q0-17 8-30.5t22-21.5l-5-48H594l-14 140h-60Zm-20-540q-33 0-56.5-23.5T420-740q0-33 23.5-56.5T500-820q33 0 56.5 23.5T580-740q0 33-23.5 56.5T500-660Zm100 340h118l-14-140h-89l-15 140Z"/> + </svg></mat-icon> + </ng-template> + <div class="template-list"> + <button mat-stroked-button (click)="applyTemplate('table')"> + <mat-icon>table_view</mat-icon> + Tabellenassistent + </button> + </div> + </mat-tab> + </mat-tab-group> <aspect-show-state-variables-button class="button-group" [stateVariablesCount]="unitService.unit.stateVariables.length"> </aspect-show-state-variables-button> diff --git a/projects/editor/src/app/components/new-ui-element-panel/ui-element-toolbox.component.ts b/projects/editor/src/app/components/new-ui-element-panel/ui-element-toolbox.component.ts index c4446d2b3..07f677d4b 100644 --- a/projects/editor/src/app/components/new-ui-element-panel/ui-element-toolbox.component.ts +++ b/projects/editor/src/app/components/new-ui-element-panel/ui-element-toolbox.component.ts @@ -3,6 +3,26 @@ import { UIElementType } from 'common/models/elements/element'; import { UnitService } from '../../services/unit-services/unit.service'; import { SelectionService } from '../../services/selection.service'; import { ElementService } from 'editor/src/app/services/unit-services/element.service'; +import { ClozeElement } from 'common/models/elements/compound-elements/cloze/cloze'; +import { ImageElement } from 'common/models/elements/media-elements/image'; +import { AudioElement } from 'common/models/elements/media-elements/audio'; +import { VideoElement } from 'common/models/elements/media-elements/video'; +import { TextFieldElement } from 'common/models/elements/input-elements/text-field'; +import { TextAreaElement } from 'common/models/elements/input-elements/text-area'; +import { SpellCorrectElement } from 'common/models/elements/input-elements/spell-correct'; +import { MathFieldElement } from 'common/models/elements/input-elements/math-field'; +import { MathTableElement } from 'common/models/elements/input-elements/math-table'; +import { LikertElement } from 'common/models/elements/compound-elements/likert/likert'; +import { DropdownElement } from 'common/models/elements/input-elements/dropdown'; +import { CheckboxElement } from 'common/models/elements/input-elements/checkbox'; +import { SliderElement } from 'common/models/elements/input-elements/slider'; +import { HotspotImageElement } from 'common/models/elements/input-elements/hotspot-image'; +import { DropListElement } from 'common/models/elements/input-elements/drop-list'; +import { ButtonElement } from 'common/models/elements/button/button'; +import { FrameElement } from 'common/models/elements/frame/frame'; +import { GeometryElement } from 'common/models/elements/geometry/geometry'; +import { TriggerElement } from 'common/models/elements/trigger/trigger'; +import { TextElement } from 'common/models/elements/text/text'; @Component({ selector: 'aspect-ui-element-toolbox', @@ -17,9 +37,34 @@ export class UiElementToolboxComponent { public unitService: UnitService, private elementService: ElementService) { } - async addUIElement(elementType: UIElementType): Promise<void> { - this.elementService.addElementToSectionByIndex(elementType, - this.selectionService.selectedPageIndex, - this.selectionService.selectedPageSectionIndex); + addUIElement(elementType: UIElementType): void { + this.elementService.addElementToSection( + elementType, + this.unitService.unit.pages[this.selectionService.selectedPageIndex].sections[this.selectionService.selectedSectionIndex]); } + + applyTemplate(templateName: string) { + this.unitService.applyTemplate(templateName); + } + + protected readonly ClozeElement = ClozeElement; + protected readonly ImageElement = ImageElement; + protected readonly AudioElement = AudioElement; + protected readonly VideoElement = VideoElement; + protected readonly TextFieldElement = TextFieldElement; + protected readonly TextAreaElement = TextAreaElement; + protected readonly SpellCorrectElement = SpellCorrectElement; + protected readonly MathFieldElement = MathFieldElement; + protected readonly MathTableElement = MathTableElement; + protected readonly LikertElement = LikertElement; + protected readonly DropdownElement = DropdownElement; + protected readonly CheckboxElement = CheckboxElement; + protected readonly SliderElement = SliderElement; + protected readonly HotspotImageElement = HotspotImageElement; + protected readonly DropListElement = DropListElement; + protected readonly ButtonElement = ButtonElement; + protected readonly FrameElement = FrameElement; + protected readonly GeometryElement = GeometryElement; + protected readonly TriggerElement = TriggerElement; + protected readonly TextElement = TextElement; } diff --git a/projects/editor/src/app/components/unit-view/page-menu.component.ts b/projects/editor/src/app/components/unit-view/page-menu.component.ts index 4dec3f9eb..8b4809dba 100644 --- a/projects/editor/src/app/components/unit-view/page-menu.component.ts +++ b/projects/editor/src/app/components/unit-view/page-menu.component.ts @@ -1,4 +1,4 @@ -import { ChangeDetectorRef, Component, EventEmitter, Input, OnDestroy, Output } from '@angular/core'; +import { Component, EventEmitter, Input, OnDestroy, Output } from '@angular/core'; import { MatCheckboxModule } from '@angular/material/checkbox'; import { MatDividerModule } from '@angular/material/divider'; import { MatFormFieldModule } from '@angular/material/form-field'; @@ -11,11 +11,8 @@ import { NgForOf, NgIf } from '@angular/common'; import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import { TranslateModule } from '@ngx-translate/core'; import { Page } from 'common/models/page'; -import { takeUntil } from 'rxjs/operators'; -import { ReferenceManager } from 'editor/src/app/services/reference-manager'; import { SelectionService } from 'editor/src/app/services/selection.service'; import { UnitService } from 'editor/src/app/services/unit-services/unit.service'; -import { DialogService } from 'editor/src/app/services/dialog.service'; import { MessageService } from 'common/services/message.service'; import { Subject } from 'rxjs'; import { MatTooltipModule } from '@angular/material/tooltip'; @@ -73,7 +70,6 @@ export class PageMenu implements OnDestroy { constructor(public unitService: UnitService, public pageService: PageService, public selectionService: SelectionService, - private dialogService: DialogService, private messageService: MessageService) {} movePage(direction: 'left' | 'right'): void { @@ -82,37 +78,7 @@ export class PageMenu implements OnDestroy { } deletePage(): void { - let refs = this.unitService.referenceManager.getPageElementsReferences( - this.unitService.unit.pages[this.selectionService.selectedPageIndex] - ); - - const pageNavButtonRefs = this.unitService.referenceManager.getButtonReferencesForPage( - this.selectionService.selectedPageIndex - ); - refs = refs.concat(pageNavButtonRefs); - - if (refs.length > 0) { - this.dialogService.showDeleteReferenceDialog(refs) - .pipe(takeUntil(this.ngUnsubscribe)) - .subscribe((result: boolean) => { - if (result) { - ReferenceManager.deleteReferences(refs); - this.pageService.deletePage(this.selectionService.selectedPageIndex); - this.selectionService.selectPreviousPage(); - } else { - this.messageService.showReferencePanel(refs); - } - }); - } else { - this.dialogService.showConfirmDialog('Seite löschen?') - .pipe(takeUntil(this.ngUnsubscribe)) - .subscribe((result: boolean) => { - if (result) { - this.pageService.deletePage(this.selectionService.selectedPageIndex); - this.selectionService.selectPreviousPage(); - } - }); - } + this.pageService.deletePage(this.selectionService.selectedPageIndex); } updateModel(page: Page, property: string, value: number | boolean, isInputValid: boolean | null = true): void { diff --git a/projects/editor/src/app/services/dialog.service.ts b/projects/editor/src/app/services/dialog.service.ts index acc1916aa..4eabc9fe6 100644 --- a/projects/editor/src/app/services/dialog.service.ts +++ b/projects/editor/src/app/services/dialog.service.ts @@ -27,8 +27,8 @@ import { SanitizationDialogComponent } from 'editor/src/app/components/dialogs/s import { TooltipPropertiesDialogComponent } from 'editor/src/app/components/dialogs/tooltip-properties-dialog.component'; -import { TooltipPosition } from 'common/models/elements/element'; -import { ConfirmationDialogComponent } from '../components/dialogs/confirmation-dialog.component'; +import { TooltipPosition, UIElement } from 'common/models/elements/element'; +import { DeleteConfirmationDialogComponent } from '../components/dialogs/delete-confirmation-dialog.component'; import { TextEditDialogComponent } from '../components/dialogs/text-edit-dialog.component'; import { TextEditMultilineDialogComponent } from '../components/dialogs/text-edit-multiline-dialog.component'; import { RichTextEditDialogComponent } from '../components/dialogs/rich-text-edit-dialog.component'; @@ -50,9 +50,9 @@ export class DialogService { return dialogRef.afterClosed(); } - showConfirmDialog(text: string, isWarning: boolean = false): Observable<boolean> { - const dialogRef = this.dialog.open(ConfirmationDialogComponent, { - data: { text, isWarning } + showDeleteConfirmDialog(text: string, elementList?: UIElement[], refs?: ReferenceList[]): Observable<boolean> { + const dialogRef = this.dialog.open(DeleteConfirmationDialogComponent, { + data: { text, elementList, refs } }); return dialogRef.afterClosed(); } diff --git a/projects/editor/src/app/services/history.service.ts b/projects/editor/src/app/services/history.service.ts index 4b20af793..c774aa3c1 100644 --- a/projects/editor/src/app/services/history.service.ts +++ b/projects/editor/src/app/services/history.service.ts @@ -8,7 +8,6 @@ export class HistoryService { addCommand(command: UnitUpdateCommand, deletedData: Record<string, unknown>): void { this.commandList.push({ ...command, deletedData: deletedData }); - console.log('HISTORY', this.commandList); } rollback(): void { @@ -20,7 +19,7 @@ export class HistoryService { export interface UnitUpdateCommand { title: string; - command: () => Record<string, unknown>; + command: () => Record<string, unknown> | Promise<Record<string, unknown>>; rollback: (deletedData: Record<string, unknown>) => void; } diff --git a/projects/editor/src/app/services/unit-services/element.service.ts b/projects/editor/src/app/services/unit-services/element.service.ts index 0f0e1c209..ddcb063b1 100644 --- a/projects/editor/src/app/services/unit-services/element.service.ts +++ b/projects/editor/src/app/services/unit-services/element.service.ts @@ -42,97 +42,115 @@ export class ElementService { private idService: IDService, private sanitizer: DomSanitizer) { } - addElementToSectionByIndex(elementType: UIElementType, - pageIndex: number, - sectionIndex: number): void { - this.addElementToSection(elementType, this.unitService.unit.pages[pageIndex].sections[sectionIndex]); - } - async addElementToSection(elementType: UIElementType, section: Section, coordinates?: { x: number, y: number }): Promise<void> { - const newElementProperties: Partial<UIElementProperties> = {}; - if (['geometry'].includes(elementType)) { - (newElementProperties as GeometryProperties).appDefinition = - await firstValueFrom(this.dialogService.showGeogebraAppDefinitionDialog()); - if (!(newElementProperties as GeometryProperties).appDefinition) return; // dialog canceled - } - if (['audio', 'video', 'image', 'hotspot-image'].includes(elementType)) { - let mediaSrc = ''; - switch (elementType) { - case 'hotspot-image': - case 'image': - mediaSrc = await FileService.loadImage(); - break; - case 'audio': - mediaSrc = await FileService.loadAudio(); - break; - case 'video': - mediaSrc = await FileService.loadVideo(); - break; - // no default + this.unitService.updateUnitDefinition({ + title: `Element hinzugefügt (${elementType})`, + command: async () => { + let newElementProperties: Partial<UIElementProperties> = {}; + try { + newElementProperties = await this.prepareElementProps(elementType, section.dynamicPositioning, coordinates); + } catch (e) { + if (e == 'dialogCanceled') return {}; + } + const newElementID = this.idService.getAndRegisterNewID(elementType); + section.addElement(ElementFactory.createElement({ + type: elementType, + position: PropertyGroupGenerators.generatePositionProps(newElementProperties.position), + ...newElementProperties, + id: newElementID + }) as PositionedUIElement); + return {newElementID}; + }, + rollback: (deletedData: Record<string, unknown>) => { + this.idService.unregisterID(deletedData.newElementID as string); + section.deleteElement(deletedData.newElementID as string); } - (newElementProperties as AudioProperties | VideoProperties | ImageProperties).src = mediaSrc; + }); + } + + private async prepareElementProps(elementType: UIElementType, + dynamicPositioning: boolean, + coordinates?: { x: number, y: number }): Promise<Partial<UIElementProperties>> { + const newElementProperties: Partial<UIElementProperties> = {}; + + switch (elementType) { + case "geometry": + (newElementProperties as GeometryProperties).appDefinition = + await firstValueFrom(this.dialogService.showGeogebraAppDefinitionDialog()); + if (!(newElementProperties as GeometryProperties).appDefinition) return Promise.reject('dialogCanceled'); // dialog canceled + break; + case "audio": + (newElementProperties as AudioProperties).src = await FileService.loadAudio(); + break; + case "video": + (newElementProperties as VideoProperties).src = await FileService.loadVideo(); + break; + case "image": + case "hotspot-image": + (newElementProperties as ImageProperties).src = await FileService.loadImage(); + break; + case 'frame': + newElementProperties.position = { + zIndex: -1, + ...newElementProperties.position + } as PositionProperties; + break; + // no default } - // Coordinates are given if an element is dragged directly into a cell + // Coordinates are given if an element is dragged directly onto the canvas if (coordinates) { newElementProperties.position = { - ...(section.dynamicPositioning && { gridColumn: coordinates.x }), - ...(section.dynamicPositioning && { gridRow: coordinates.y }), - ...(!section.dynamicPositioning && { yPosition: coordinates.y }), - ...(!section.dynamicPositioning && { yPosition: coordinates.y }) + ...dynamicPositioning && { gridColumn: coordinates.x }, + ...dynamicPositioning && { gridRow: coordinates.y }, + ...!dynamicPositioning && { xPosition: coordinates.x }, + ...!dynamicPositioning && { yPosition: coordinates.y } } as PositionProperties; } - - // Use z-index -1 for frames - newElementProperties.position = { - zIndex: elementType === 'frame' ? -1 : 0, - ...newElementProperties.position - } as PositionProperties; - - section.addElement(ElementFactory.createElement({ - type: elementType, - position: PropertyGroupGenerators.generatePositionProps(newElementProperties.position), - ...newElementProperties, - id: this.idService.getAndRegisterNewID(elementType) - }) as PositionedUIElement); - this.unitService.updateUnitDefinition(); + return newElementProperties; } - deleteElements(elements: UIElement[]): void { - const refs = - this.unitService.referenceManager.getElementsReferences(elements); - // console.log('element refs', refs); - if (refs.length > 0) { - this.dialogService.showDeleteReferenceDialog(refs) - .subscribe((result: boolean) => { - if (result) { - ReferenceManager.deleteReferences(refs); - this.unitService.unregisterIDs(elements); - this.unitService.unit.pages[this.selectionService.selectedPageIndex].sections.forEach(section => { - section.elements = section.elements.filter(element => !elements.includes(element)); - }); - this.unitService.updateUnitDefinition(); - } else { - this.messageService.showReferencePanel(refs); - } - }); - } else { - this.dialogService.showConfirmDialog('Element(e) löschen?') - .subscribe((result: boolean) => { - if (result) { - this.unitService.unregisterIDs(elements); - this.unitService.unit.pages[this.selectionService.selectedPageIndex].sections.forEach(section => { - section.elements = section.elements.filter(element => !elements.includes(element)); - }); - this.unitService.updateUnitDefinition(); - } + async deleteElements(elements: UIElement[]): Promise<void> { + this.unitService.updateUnitDefinition({ + title: elements.length > 1 ? 'Elemente gelöscht' : `Element ${elements[0].id} gelöscht`, + command: async () => { + if (await this.unitService.prepareDelete('elements', elements)) { + this.unitService.unregisterIDs(elements); + const elementSections = this.findElementsInSections(elements); + elementSections.forEach(x => { + this.unitService.unit.pages[this.selectionService.selectedPageIndex].sections[x.sectionIndex] + .deleteElement(x.element.id); + }); + return {elementSections}; + } + return {}; + }, + rollback: (deletedData: Record<string, unknown>) => { + this.unitService.registerIDs(elements); + (deletedData.elementSections as {sectionIndex: number, element: UIElement}[]).forEach(x => { + this.unitService.unit + .pages[this.selectionService.selectedPageIndex] + .sections[x.sectionIndex] + .addElement(x.element as PositionedUIElement); // TODO order check }); - } + } + }); + } + + private findElementsInSections(elements: UIElement[]): {sectionIndex: number, element: UIElement}[] { + const elementSections: {sectionIndex: number, element: UIElement}[] = [] + elements.forEach(element => { + this.unitService.unit.pages[this.selectionService.selectedPageIndex].sections.forEach((section, i) => { + if (section.elements.map(el => el.id).includes(element.id)) { + elementSections.push({sectionIndex: i, element}); + } + }); + }); + return elementSections; } updateElementsProperty(elements: UIElement[], property: string, value: unknown): void { - console.log('updateElementProperty', elements, property, value); elements.forEach(element => { if (property === 'id') { if (this.idService.validateAndAddNewID(value as string, element.id)) { diff --git a/projects/editor/src/app/services/unit-services/page.service.ts b/projects/editor/src/app/services/unit-services/page.service.ts index cd0553a24..4fcaec7cf 100644 --- a/projects/editor/src/app/services/unit-services/page.service.ts +++ b/projects/editor/src/app/services/unit-services/page.service.ts @@ -28,17 +28,24 @@ export class PageService { }); } - deletePage(pageIndex: number): void { + async deletePage(pageIndex: number): Promise<void> { this.unitService.updateUnitDefinition({ title: 'Seite gelöscht', - command: () => { - const deletedpage = this.unitService.unit.pages.splice(pageIndex, 1)[0]; - return { - pageIndex, - deletedpage - }; + command: async () => { + const selectedPage = this.unitService.unit.pages[this.selectionService.selectedPageIndex]; + if (await this.unitService.prepareDelete('page', selectedPage)) { + this.unitService.unregisterIDs(selectedPage.getAllElements()); + const deletedpage = this.unitService.unit.pages.splice(pageIndex, 1)[0]; + this.selectionService.selectPreviousPage(); + return { + pageIndex, + deletedpage + }; + } + return {}; }, rollback: (deletedData: Record<string, unknown>) => { + this.unitService.registerIDs((deletedData['deletedpage'] as Page).getAllElements()); this.unitService.unit.pages.splice(deletedData['pageIndex'] as number, 0, deletedData['deletedpage'] as Page); } }); diff --git a/projects/editor/src/app/services/unit-services/section.service.ts b/projects/editor/src/app/services/unit-services/section.service.ts index faebe9a5b..e160aa6ae 100644 --- a/projects/editor/src/app/services/unit-services/section.service.ts +++ b/projects/editor/src/app/services/unit-services/section.service.ts @@ -64,15 +64,22 @@ export class SectionService { deleteSection(pageIndex: number, sectionIndex: number): void { this.unitService.updateUnitDefinition({ title: `Abschnitt gelöscht - Seite ${pageIndex + 1}, Abschnitt ${sectionIndex + 1}`, - command: () => { - const deletedSection = this.unitService.unit.pages[pageIndex].sections[sectionIndex]; - this.unitService.unregisterIDs(this.unitService.unit.pages[pageIndex].sections[sectionIndex].getAllElements()); - this.unitService.unit.pages[pageIndex].sections.splice(sectionIndex, 1); - return {deletedSection, pageIndex, sectionIndex}; + command: async () => { + const sectionToDelete = this.unitService.unit.pages[pageIndex].sections[sectionIndex]; + if (await this.unitService.prepareDelete('section', sectionToDelete)) { + this.unitService.unregisterIDs(sectionToDelete.getAllElements()); + this.unitService.unit.pages[pageIndex].sections.splice(sectionIndex, 1); + this.selectionService.selectedSectionIndex = + Math.max(0, this.selectionService.selectedSectionIndex - 1); + return {deletedSection: sectionToDelete, pageIndex, sectionIndex}; + } + return {}; }, rollback: (deletedData: Record<string, unknown>) => { this.unitService.registerIDs((deletedData.deletedSection as Section).getAllElements()); - this.unitService.unit.pages[deletedData.pageIndex as number].addSection(deletedData.deletedSection as Section, sectionIndex) + this.unitService.unit.pages[deletedData.pageIndex as number].addSection(deletedData.deletedSection as Section, sectionIndex); + this.selectionService.selectedSectionIndex = + Math.max(0, this.selectionService.selectedSectionIndex - 1); } }); } diff --git a/projects/editor/src/app/services/unit-services/unit.service.ts b/projects/editor/src/app/services/unit-services/unit.service.ts index ee96e07e2..697d8a7c3 100644 --- a/projects/editor/src/app/services/unit-services/unit.service.ts +++ b/projects/editor/src/app/services/unit-services/unit.service.ts @@ -10,13 +10,15 @@ import { import { DropListElement } from 'common/models/elements/input-elements/drop-list'; import { StateVariable } from 'common/models/state-variable'; import { VersionManager } from 'common/services/version-manager'; -import { ReferenceManager } from 'editor/src/app/services/reference-manager'; +import { ReferenceList, ReferenceManager } from 'editor/src/app/services/reference-manager'; import { DialogService } from '../dialog.service'; import { VeronaAPIService } from '../verona-api.service'; import { SelectionService } from '../selection.service'; import { IDService } from '../id.service'; import { UnitDefinitionSanitizer } from '../sanitizer'; import { HistoryService, UnitUpdateCommand } from 'editor/src/app/services/history.service'; +import { Page } from 'common/models/page'; +import { Section } from 'common/models/section'; @Injectable({ providedIn: 'root' @@ -87,8 +89,14 @@ export class UnitService { updateUnitDefinition(command?: UnitUpdateCommand): void { if (command) { - const deletedData = command.command(); - this.historyService.addCommand(command, deletedData); + let deletedData = command.command(); + if (deletedData instanceof Promise) { + deletedData.then((deletedData) => { + this.historyService.addCommand(command, deletedData); + }); + } else { + this.historyService.addCommand(command, deletedData); + } } this.veronaApiService.sendChanged(this.unit); } @@ -155,4 +163,49 @@ export class UnitService { this.unit.stateVariables = stateVariables; this.updateUnitDefinition(); } + + /* Check references and confirm */ + prepareDelete(deletedObjectType: 'page' | 'section' | 'elements', object: Page | Section | UIElement[]): Promise<boolean> { + return new Promise((resolve) => { + let refs: ReferenceList[] = []; + let dialogText: string = ''; + switch (deletedObjectType) { + case "page": + refs = this.referenceManager.getPageElementsReferences( + this.unit.pages[this.selectionService.selectedPageIndex] + ); + const pageNavButtonRefs = this.referenceManager.getButtonReferencesForPage( + this.selectionService.selectedPageIndex + ); + refs = refs.concat(pageNavButtonRefs); + dialogText = `Seite ${this.selectionService.selectedPageIndex + 1} löschen?`; + break; + case "section": + refs = this.referenceManager.getSectionElementsReferences([object as Section]); + dialogText = `Abschnitt ${this.selectionService.selectedSectionIndex + 1} löschen?`; + break; + case "elements": + refs = this.referenceManager.getElementsReferences(object as UIElement[]); + dialogText = 'Folgende Elemente werden gelöscht:'; + } + + this.dialogService.showDeleteConfirmDialog( + dialogText, + deletedObjectType == 'elements' ? object as UIElement[] : undefined, + refs) + .subscribe((result: boolean) => { + if (result) { + if (refs.length > 0) ReferenceManager.deleteReferences(refs); // TODO rollback? + resolve(true); + } else { + if (refs.length > 0) this.messageService.showReferencePanel(refs); + resolve(false); + } + }); + }); + } + + applyTemplate(templateName: string) { + // TODO + } } -- GitLab