Skip to content
Snippets Groups Projects
section-menu.component.ts 14.8 KiB
Newer Older
  Component, OnDestroy, Input, Output, EventEmitter, ViewChild, ElementRef
import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
import { Clipboard } from '@angular/cdk/clipboard';
import { MessageService } from 'common/services/message.service';
import { UIElement } from 'common/models/elements/element';
import { Section } from 'common/models/section';
rhenck's avatar
rhenck committed
import { DropListElement } from 'common/models/elements/input-elements/drop-list';
import { IDService } from 'editor/src/app/services/id.service';
import { VisibilityRule } from 'common/models/visibility-rule';
rhenck's avatar
rhenck committed
import { UnitService } from '../../services/unit.service';
import { DialogService } from '../../services/dialog.service';
import { SelectionService } from '../../services/selection.service';
  selector: 'aspect-section-menu',
    <button mat-mini-fab [matMenuTriggerFor]="elementListMenu"
            [matTooltip]="'Elementliste'"
            [matTooltipPosition]="'left'">
      <mat-icon>list</mat-icon>
    </button>
    <mat-menu #elementListMenu="matMenu" class="layoutMenu" xPosition="before">
      <mat-action-list>
        <ng-container *ngIf="section.elements.length === 0">
          Keine Elemente im Abschnitt
        </ng-container>
        <mat-list-item *ngFor="let element of section.elements"
                       (click)="selectElement(element)">
          {{element.id}}
        </mat-list-item>
      </mat-action-list>
    </mat-menu>
    <button mat-mini-fab [matTooltip]="'Hintergrundfarbe'" [matTooltipPosition]="'left'"
            (click)="openColorPicker()">
      <mat-icon>palette</mat-icon>
    </button>
    <input #colorPicker type="color" [style.display]="'none'"
           [value]="$any(section.backgroundColor)"
           (change)="updateModel('backgroundColor', $any($event.target).value)">

    <button mat-mini-fab
            (click)="showVisibilityRulesDialog()"
            [matTooltip]="'Sichtbarkeit'" [matTooltipPosition]="'left'">
      <mat-icon>disabled_visible</mat-icon>
    </button>
    <!--    <mat-menu #activeAfterIDMenu="matMenu"-->
    <!--              class="activeAfterID-menu" xPosition="before">-->

    <!--      <aspect-rules [elementIds]="elementIds"-->
    <!--                    [rules]="section.rules"-->
    <!--      (click)="$event.stopPropagation()">-->
    <!--      </aspect-rules>-->


    <!--      <mat-form-field appearance="outline">-->
    <!--        <mat-label>{{'section-menu.activeAfterID' | translate }}</mat-label>-->
    <!--        <input matInput-->
    <!--               [value]="$any(section.activeAfterID)"-->
    <!--               (click)="$any($event).stopPropagation()"-->
    <!--               (change)="updateModel('activeAfterID', $any($event.target).value)">-->
    <!--      </mat-form-field>-->
    <!--      <mat-form-field appearance="outline">-->
    <!--        <mat-label>{{'section-menu.activeAfterIdDelay' | translate }}</mat-label>-->
    <!--        <input matInput type="number" step="1000" min="0"-->
    <!--               [disabled]="!section.activeAfterID"-->
    <!--               [value]="$any(section.activeAfterIdDelay)"-->
    <!--               (click)="$any($event).stopPropagation()"-->
    <!--               (change)="updateModel('activeAfterIdDelay', $any($event.target).value)">-->
    <!--      </mat-form-field>-->


    <!--      <button mat-icon-button-->
    <!--              matSuffix-->
    <!--              color="primary"-->
    <!--              (click)="addRule($event)">-->
    <!--        <mat-icon>add</mat-icon>-->
    <!--      </button>-->
    <!--      <ng-container *ngFor="let rule of section.rules; let i = index">-->
    <!--        <aspect-rule [elementIds]="elementIds"-->
    <!--                     [rule]="rule"-->
    <!--                     (ruleChange)="updateRule(i, $event)"-->
    <!--                     (click)="$event.stopPropagation()">-->
    <!--        </aspect-rule>-->
    <!--        <button mat-icon-button-->
    <!--                matSuffix-->
    <!--                color="primary"-->
    <!--                (click)="deleteRule(i)">-->
    <!--          <mat-icon>delete</mat-icon>-->
    <!--        </button>-->
    <!--      </ng-container>-->


    <!--    </mat-menu>-->
    <button mat-mini-fab [matMenuTriggerFor]="layoutMenu"
            [matTooltip]="'Layout'" [matTooltipPosition]="'left'">
      <mat-icon>space_dashboard</mat-icon>
    </button>
    <mat-menu #layoutMenu="matMenu" class="layoutMenu" xPosition="before">
      <div (click)="$event.stopPropagation()">
        <mat-checkbox class="menuItem" [checked]="section.dynamicPositioning"
                      (click)="$any($event).stopPropagation()"
                      (change)="updateModel('dynamicPositioning', $event.checked)">
          {{'section-menu.dynamic-positioning' | translate }}
        </mat-checkbox>
        <ng-container *ngIf="!section.dynamicPositioning">
          <mat-form-field class="section-height-input" appearance="outline">
            <mat-label>{{'section-menu.height' | translate }}</mat-label>
            <input matInput type="number"
                   [value]="$any(section.height)"
rhenck's avatar
rhenck committed
                   (click)="$any($event).stopPropagation()"
                   (change)="updateModel('height', $any($event.target).value)">
          </mat-form-field>
        </ng-container>
        <div *ngIf="section.dynamicPositioning">
          <fieldset>
            <legend>{{'section-menu.columns' | translate }}</legend>

            <mat-checkbox class="menuItem" [checked]="section.autoColumnSize"
                          (click)="$any($event).stopPropagation()"
                          (change)="updateModel('autoColumnSize', $event.checked)">
              {{'section-menu.autoColumnSize' | translate }}
            </mat-checkbox>
            <ng-container *ngIf="!section.autoColumnSize">
              <mat-form-field appearance="outline">
                <mat-label>{{'section-menu.columnCount' | translate }}</mat-label>
                <input matInput type="number"
                       [value]="$any(section.gridColumnSizes.length)"
                       (click)="$any($event).stopPropagation()"
                       (change)="modifySizeArray('gridColumnSizes', $any($event).target.value)">
              </mat-form-field>
              <ng-container *ngFor="let size of section.gridColumnSizes; let i = index">
                <aspect-size-input-panel [label]="('section-menu.width' | translate) + ' ' + (i + 1)"
                                         [value]="size.value"
                                         [unit]="size.unit"
                                         [allowedUnits]="['px', 'fr']"
                                         (valueUpdated)="changeGridSize('gridColumnSizes', i, $event)">
                </aspect-size-input-panel>
              </ng-container>
            </ng-container>
          </fieldset>

          <fieldset>
            <legend>{{'section-menu.rows' | translate }}</legend>
            <mat-checkbox class="menuItem" [checked]="section.autoRowSize"
                          (click)="$any($event).stopPropagation()"
                          (change)="updateModel('autoRowSize', $event.checked)">
              {{'section-menu.autoRowSize' | translate }}
            </mat-checkbox>
            <ng-container *ngIf="!section.autoRowSize">
              <mat-form-field appearance="outline">
                <mat-label>{{'section-menu.height' | translate }}</mat-label>
                <input matInput type="number"
                       [value]="$any(section.height)"
                       (click)="$any($event).stopPropagation()"
                       (change)="updateModel('height', $any($event.target).value)">
              </mat-form-field>
              <mat-form-field appearance="outline">
                <mat-label>{{'section-menu.rowCount' | translate }}</mat-label>
                <input matInput type="number"
                       [value]="$any(section.gridRowSizes.length)"
                       (click)="$any($event).stopPropagation()"
                       (change)="modifySizeArray('gridRowSizes', $any($event).target.value)">
              </mat-form-field>
              <ng-container *ngFor="let size of section.gridRowSizes ; let i = index">
                <aspect-size-input-panel [label]="('section-menu.height' | translate) + ' ' + (i + 1)"
                                         [value]="size.value"
                                         [unit]="size.unit"
                                         [allowedUnits]="['px', 'fr']"
                                         (valueUpdated)="changeGridSize('gridRowSizes', i, $event)">
                </aspect-size-input-panel>
              </ng-container>
            </ng-container>
          </fieldset>
      </div>
    </mat-menu>

    <button mat-mini-fab [matTooltip]="'Abschnitt kopieren'" [matTooltipPosition]="'left'"
            (click)="copySectionToClipboard()">
      <mat-icon>content_copy</mat-icon>
    </button>
    <button mat-mini-fab
            [matTooltip]="'Abschnitt einfügen'" [matTooltipPosition]="'left'"
            (click)="showSectionInsertDialog()">
      <mat-icon>content_paste</mat-icon>
    </button>
    <button *ngIf="allowMoveUp" mat-mini-fab
            [matTooltip]="'Nach oben verschieben'" [matTooltipPosition]="'left'"
            (click)="this.moveSection.emit('up')">
      <mat-icon>north</mat-icon>
    </button>
    <button *ngIf="allowMoveDown" mat-mini-fab
            [matTooltip]="'Nach unten verschieben'" [matTooltipPosition]="'left'"
            (click)="this.moveSection.emit('down')">
      <mat-icon>south</mat-icon>
    </button>
    <button mat-mini-fab [matTooltip]="'Duplizieren'" [matTooltipPosition]="'left'"
            (click)="duplicateSection.emit()">
      <mat-icon>control_point_duplicate</mat-icon>
    </button>
    <button *ngIf="allowDelete" mat-mini-fab
            [matTooltip]="'Löschen'" [matTooltipPosition]="'left'"
            (click)="deleteSection()">
      <mat-icon>clear</mat-icon>
  styles: [
    '::ng-deep .layoutMenu {padding: 0 15px; width: 250px;}',
    '::ng-deep .layoutMenu fieldset {margin: 10px 0; display: flex; flex-direction: column; align-items: flex-start;}',
    '::ng-deep .layoutMenu .section-height-input {margin-top: 10px;}',
    '.menuItem {margin-bottom: 5px;}',
    '::ng-deep .activeAfterID-menu .mat-mdc-form-field {width:90%; margin-left: 10px;}'
export class SectionMenuComponent implements OnDestroy {
  @Input() section!: Section;
  @Input() sectionIndex!: number;
  @Input() allowMoveUp!: boolean;
  @Input() allowMoveDown!: boolean;
  @Input() allowDelete!: boolean;
  @Output() moveSection = new EventEmitter<'up' | 'down'>();
  @Output() duplicateSection = new EventEmitter();
  @Output() selectElementComponent = new EventEmitter<UIElement>();

  @ViewChild('colorPicker') colorPicker!: ElementRef;
  private ngUnsubscribe = new Subject<void>();
  constructor(public unitService: UnitService,
              private selectionService: SelectionService,
              private dialogService: DialogService,
              private messageService: MessageService,
rhenck's avatar
rhenck committed
              private idService: IDService,
              private clipboard: Clipboard) { }
  updateModel(
    property: string, value: string | number | boolean | VisibilityRule[] | { value: number; unit: string }[]
  ): void {
    this.unitService.updateSectionProperty(this.section, property, value);
  }

  selectElement(element: UIElement): void {
    this.selectElementComponent.emit(element);
  }

  deleteSection(): void {
    this.dialogService.showConfirmDialog('Abschnitt löschen?')
      .pipe(takeUntil(this.ngUnsubscribe))
      .subscribe((result: boolean) => {
        if (result) {
          this.unitService.deleteSection(this.section);
          if (this.sectionIndex === this.selectionService.selectedPageSectionIndex &&
            this.selectionService.selectedPageSectionIndex > 0) {
            this.selectionService.selectedPageSectionIndex -= 1;
          }
  /* Add or remove elements to size array. Default value 1fr. */
  modifySizeArray(property: 'gridColumnSizes' | 'gridRowSizes', newLength: number): void {
    const sizeArray: { value: number; unit: string }[] = property === 'gridColumnSizes' ?
      this.section.gridColumnSizes : this.section.gridRowSizes;

    let newArray = [];
    if (newLength < sizeArray.length) {
      newArray = sizeArray.slice(0, newLength);
    } else {
      newArray.push(
        ...sizeArray,
        ...Array(newLength - sizeArray.length).fill({ value: 1, unit: 'fr' })
    this.updateModel(property, newArray);
  changeGridSize(property: string, index: number, newValue: { value: number; unit: string }): void {
    const sizeArray: { value: number; unit: string }[] = property === 'gridColumnSizes' ?
      this.section.gridColumnSizes : this.section.gridRowSizes;
    sizeArray[index] = newValue;
    this.updateModel(property, [...sizeArray]);
rhenck's avatar
rhenck committed
  openColorPicker(): void {
    this.colorPicker.nativeElement.click();
  }

  ngOnDestroy(): void {
    this.ngUnsubscribe.next();
    this.ngUnsubscribe.complete();
  }

  copySectionToClipboard() {
    this.clipboard.copy(JSON.stringify(this.section));
    this.messageService.showSuccess('Abschnitt in Zwischenablage kopiert');
  showSectionInsertDialog(): void {
    this.dialogService.showSectionInsertDialog(this.section)
      .pipe(takeUntil(this.ngUnsubscribe))
      .subscribe((newSection: Section) => {
        if (newSection) {
rhenck's avatar
rhenck committed
          newSection.getAllElements().filter(element => !this.idService.isIdAvailable(element.id)).forEach(element => {
            element.id = this.idService.getAndRegisterNewID(element.type);
            if (['drop-list', 'drop-list-simple'].includes((element as UIElement).type as string)) {
              (element as DropListElement).value
                .filter(valueElement => !this.idService.isIdAvailable(valueElement.id))
                .forEach(valueElement => {
                  valueElement.id = this.idService.getAndRegisterNewID('value');
                });
            }
          });
          this.unitService.replaceSection(this.selectionService.selectedPageIndex, this.sectionIndex, newSection);
        }
      });

  showVisibilityRulesDialog(): void {
    this.dialogService
      .showVisibilityRulesDialog(this.section.visibilityRules, this.getControlIds(), this.section.visibilityDelay)
      .subscribe(rulesWithDelay => {
        if (rulesWithDelay) {
          this.updateModel('visibilityRules', rulesWithDelay.visibilityRules);
          this.updateModel('visibilityDelay', rulesWithDelay.visibilityDelay);
        }
      });
  }

  private getControlIds(): string[] {
    return this.unitService.unit.getAllElements()
      .map(element => element.id)
      .concat(this.unitService.unit.stateVariables
        .map(element => element.id));
  }