Skip to content
Snippets Groups Projects
Commit af1b9ad7 authored by rhenck's avatar rhenck
Browse files

[editor] Add section element list

This allows to select elements without clicking them on the canvas, as 
elements may be obstructed or not visible for some reason.

The selection logic is a bit complicated because usually only the 
overlay components are usd for selection which are also the place to 
apply any highlighting on. Here we have to find the component element 
reference from the section. Therefore the double looping 
'getElementComponent' method.

Also when an elements is selected it briefly gets z-index 100 so it is 
moved to the front and visible and manipulatable.
parent 41df2460
No related branches found
No related tags found
No related merge requests found
Showing
with 73 additions and 9 deletions
...@@ -44,6 +44,7 @@ import { LikertRowEditDialogComponent } from './components/dialogs/likert-row-ed ...@@ -44,6 +44,7 @@ import { LikertRowEditDialogComponent } from './components/dialogs/likert-row-ed
import { RichTextEditDialogComponent } from './components/dialogs/rich-text-edit-dialog.component'; import { RichTextEditDialogComponent } from './components/dialogs/rich-text-edit-dialog.component';
import { ElementModelPropertiesComponent } from './components/unit-view/page-view/properties-panel/element-model-properties.component'; import { ElementModelPropertiesComponent } from './components/unit-view/page-view/properties-panel/element-model-properties.component';
import { DropListOptionEditDialogComponent } from './components/dialogs/drop-list-option-edit-dialog.component'; import { DropListOptionEditDialogComponent } from './components/dialogs/drop-list-option-edit-dialog.component';
import { MatListModule } from '@angular/material/list';
@NgModule({ @NgModule({
declarations: [ declarations: [
...@@ -93,7 +94,8 @@ import { DropListOptionEditDialogComponent } from './components/dialogs/drop-lis ...@@ -93,7 +94,8 @@ import { DropListOptionEditDialogComponent } from './components/dialogs/drop-lis
provide: TranslateLoader, provide: TranslateLoader,
useClass: EditorTranslateLoader useClass: EditorTranslateLoader
} }
}) }),
MatListModule
], ],
providers: [] providers: []
}) })
......
...@@ -14,10 +14,12 @@ ...@@ -14,10 +14,12 @@
[allowDelete]="page.sections.length > 1" [allowDelete]="page.sections.length > 1"
(moveSection)="unitService.moveSection(section, page, $event)" (moveSection)="unitService.moveSection(section, page, $event)"
(duplicateSection)="unitService.duplicateSection(section, page, i)" (duplicateSection)="unitService.duplicateSection(section, page, i)"
(selectElementComponent)="selectElementComponent($event)"
(mouseover)="hoveredSection = i" (mouseleave)="hoveredSection = -1"> <!-- keep menu open--> (mouseover)="hoveredSection = i" (mouseleave)="hoveredSection = -1"> <!-- keep menu open-->
</app-section-menu> </app-section-menu>
<!-- TODO split section into static and dynamic--> <!-- TODO split section into static and dynamic-->
<app-section-static *ngIf="!section.dynamicPositioning" <app-section-static *ngIf="!section.dynamicPositioning"
#sectionComponent
class="section drop-list" id="section-{{i}}" class="section drop-list" id="section-{{i}}"
[section]="section" [section]="section"
[isSelected]="selectionService.selectedPageSectionIndex === i" [isSelected]="selectionService.selectedPageSectionIndex === i"
...@@ -29,6 +31,7 @@ ...@@ -29,6 +31,7 @@
(click)="selectionService.selectedPageSectionIndex = i"> (click)="selectionService.selectedPageSectionIndex = i">
</app-section-static> </app-section-static>
<app-section-dynamic *ngIf="section.dynamicPositioning" <app-section-dynamic *ngIf="section.dynamicPositioning"
#sectionComponent
class="section drop-list" class="section drop-list"
[section]="section" [sectionIndex]="i" [section]="section" [sectionIndex]="i"
[isSelected]="selectionService.selectedPageSectionIndex === i" [isSelected]="selectionService.selectedPageSectionIndex === i"
......
import { import {
Component, Input, OnDestroy, OnInit Component, Input, OnDestroy, OnInit, QueryList, ViewChildren
} from '@angular/core'; } from '@angular/core';
import { CdkDragDrop } from '@angular/cdk/drag-drop'; import { CdkDragDrop } from '@angular/cdk/drag-drop';
import { Subject } from 'rxjs'; import { Subject } from 'rxjs';
...@@ -9,6 +9,9 @@ import { SelectionService } from '../../../../services/selection.service'; ...@@ -9,6 +9,9 @@ import { SelectionService } from '../../../../services/selection.service';
import { Page } from '../../../../../../../common/models/page'; import { Page } from '../../../../../../../common/models/page';
import { PositionedElement, UIElement } from '../../../../../../../common/models/uI-element'; import { PositionedElement, UIElement } from '../../../../../../../common/models/uI-element';
import { Section } from '../../../../../../../common/models/section'; import { Section } from '../../../../../../../common/models/section';
import { CanvasElementOverlay } from './overlays/canvas-element-overlay';
import { SectionStaticComponent } from './section-static.component';
import { SectionDynamicComponent } from './section-dynamic.component';
@Component({ @Component({
selector: 'app-page-canvas', selector: 'app-page-canvas',
...@@ -28,6 +31,8 @@ export class CanvasComponent implements OnInit, OnDestroy { ...@@ -28,6 +31,8 @@ export class CanvasComponent implements OnInit, OnDestroy {
hoveredSection: number = -1; hoveredSection: number = -1;
private ngUnsubscribe = new Subject<void>(); private ngUnsubscribe = new Subject<void>();
@ViewChildren('sectionComponent') childSectionComponents!: QueryList<SectionStaticComponent | SectionDynamicComponent>;
constructor(public selectionService: SelectionService, public unitService: UnitService) { } constructor(public selectionService: SelectionService, public unitService: UnitService) { }
ngOnInit(): void { ngOnInit(): void {
...@@ -113,6 +118,26 @@ export class CanvasComponent implements OnInit, OnDestroy { ...@@ -113,6 +118,26 @@ export class CanvasComponent implements OnInit, OnDestroy {
this.selectionService.selectedPageSectionIndex = this.page.sections.length - 1; this.selectionService.selectedPageSectionIndex = this.page.sections.length - 1;
} }
selectElementComponent(element: UIElement): void {
const elementComponent = this.getElementComponent(element);
if (elementComponent) {
this.selectionService.selectElement({ componentElement: elementComponent, multiSelect: false });
} else {
throw Error('Element not found. This is a bug!');
}
}
private getElementComponent(element: UIElement): CanvasElementOverlay | null {
for (const sectionComponent of this.childSectionComponents.toArray()) {
for (const elementComponent of sectionComponent.childElementComponents.toArray()) {
if (elementComponent.element.id === element.id) {
return elementComponent;
}
}
}
return null;
}
ngOnDestroy(): void { ngOnDestroy(): void {
this.ngUnsubscribe.next(); this.ngUnsubscribe.next();
this.ngUnsubscribe.complete(); this.ngUnsubscribe.complete();
......
...@@ -24,6 +24,8 @@ export abstract class CanvasElementOverlay implements OnInit, OnDestroy { ...@@ -24,6 +24,8 @@ export abstract class CanvasElementOverlay implements OnInit, OnDestroy {
protected childComponent!: ComponentRef<ElementComponent | CompoundElementComponent>; protected childComponent!: ComponentRef<ElementComponent | CompoundElementComponent>;
private ngUnsubscribe = new Subject<void>(); private ngUnsubscribe = new Subject<void>();
temporaryHighlight: boolean = false;
constructor(public selectionService: SelectionService, constructor(public selectionService: SelectionService,
protected unitService: UnitService, protected unitService: UnitService,
private componentFactoryResolver: ComponentFactoryResolver, private componentFactoryResolver: ComponentFactoryResolver,
...@@ -54,6 +56,10 @@ export abstract class CanvasElementOverlay implements OnInit, OnDestroy { ...@@ -54,6 +56,10 @@ export abstract class CanvasElementOverlay implements OnInit, OnDestroy {
} }
setSelected(newValue: boolean): void { setSelected(newValue: boolean): void {
this.temporaryHighlight = true;
setTimeout(() => {
this.temporaryHighlight = false;
}, 2000);
this.isSelected = newValue; this.isSelected = newValue;
// This avoids: "NG0100: Expression has changed after it was checked" // This avoids: "NG0100: Expression has changed after it was checked"
// The selection service may change the "selected" variable after onInit has run. // The selection service may change the "selected" variable after onInit has run.
......
...@@ -10,7 +10,8 @@ import { UIElement } from '../../../../../../../../common/models/uI-element'; ...@@ -10,7 +10,8 @@ import { UIElement } from '../../../../../../../../common/models/uI-element';
template: ` template: `
<!-- TabIndex is needed to make the div selectable and catch keyboard events (delete). --> <!-- TabIndex is needed to make the div selectable and catch keyboard events (delete). -->
<!-- DragStart and DragEnd are part of a cursor hack to style the body. See global styling file. --> <!-- DragStart and DragEnd are part of a cursor hack to style the body. See global styling file. -->
<div #draggableElement class="draggable-element" [class.draggable-element-selected]="isSelected" <div #draggableElement class="draggable-element"
[class.temporaryHighlight]="temporaryHighlight"
[style.display]="dragging ? 'none' : ''" [style.display]="dragging ? 'none' : ''"
tabindex="-1" tabindex="-1"
cdkDrag [cdkDragData]="{dragType: 'move', element: element}" cdkDrag [cdkDragData]="{dragType: 'move', element: element}"
...@@ -45,7 +46,8 @@ import { UIElement } from '../../../../../../../../common/models/uI-element'; ...@@ -45,7 +46,8 @@ import { UIElement } from '../../../../../../../../common/models/uI-element';
'.draggable-element:active {cursor: grabbing}', '.draggable-element:active {cursor: grabbing}',
'.resizeHandle {position: absolute; right: 3px; bottom: 3px; z-index: 1; height: 25px}', '.resizeHandle {position: absolute; right: 3px; bottom: 3px; z-index: 1; height: 25px}',
'.resizeHandle {cursor: nwse-resize}', '.resizeHandle {cursor: nwse-resize}',
'.cdk-drag {position: absolute; bottom: 0; right: 0}' '.cdk-drag {position: absolute; bottom: 0; right: 0}',
'.temporaryHighlight {z-index: 100}'
] ]
}) })
export class DynamicCanvasOverlayComponent extends CanvasElementOverlay { export class DynamicCanvasOverlayComponent extends CanvasElementOverlay {
......
...@@ -9,7 +9,8 @@ import { UIElement } from '../../../../../../../../common/models/uI-element'; ...@@ -9,7 +9,8 @@ import { UIElement } from '../../../../../../../../common/models/uI-element';
template: ` template: `
<!-- Is also a droplist to catch the resize drop and not let it bubble up to the canvas drop handler. --> <!-- Is also a droplist to catch the resize drop and not let it bubble up to the canvas drop handler. -->
<!-- TabIndex is needed to make the div selectable and catch keyboard events (delete). --> <!-- TabIndex is needed to make the div selectable and catch keyboard events (delete). -->
<div class="draggable-element" [class.draggable-element-selected]="isSelected" <div class="draggable-element"
[class.temporaryHighlight]="temporaryHighlight"
cdkDrag [cdkDragData]="{dragType: 'move', element: element}" cdkDrag [cdkDragData]="{dragType: 'move', element: element}"
(click)="selectElement($event.shiftKey); $event.stopPropagation()" (click)="selectElement($event.shiftKey); $event.stopPropagation()"
(cdkDragStarted)="!isSelected && selectElement()" (cdkDragStarted)="!isSelected && selectElement()"
...@@ -45,7 +46,8 @@ import { UIElement } from '../../../../../../../../common/models/uI-element'; ...@@ -45,7 +46,8 @@ import { UIElement } from '../../../../../../../../common/models/uI-element';
'.draggable-element {position: absolute}', '.draggable-element {position: absolute}',
'.draggable-element:active {cursor: grabbing}', '.draggable-element:active {cursor: grabbing}',
'.resizeHandle {position: absolute; cursor: nwse-resize}', '.resizeHandle {position: absolute; cursor: nwse-resize}',
'.resize-droplist {position: absolute}' '.resize-droplist {position: absolute}',
'.temporaryHighlight {z-index: 100}'
] ]
}) })
export class StaticCanvasOverlayComponent extends CanvasElementOverlay { export class StaticCanvasOverlayComponent extends CanvasElementOverlay {
......
import { import {
Component, Input, Output, EventEmitter Component, Input, Output, EventEmitter, ViewChildren, QueryList
} from '@angular/core'; } from '@angular/core';
import { CdkDragDrop } from '@angular/cdk/drag-drop/drag-events'; import { CdkDragDrop } from '@angular/cdk/drag-drop/drag-events';
import { DragItemData, DropListData } from './canvas.component'; import { DragItemData, DropListData } from './canvas.component';
import { UnitService } from '../../../../services/unit.service'; import { UnitService } from '../../../../services/unit.service';
import { Section } from '../../../../../../../common/models/section'; import { Section } from '../../../../../../../common/models/section';
import { UIElementType } from '../../../../../../../common/models/uI-element'; import { UIElementType } from '../../../../../../../common/models/uI-element';
import { CanvasElementOverlay } from './overlays/canvas-element-overlay';
@Component({ @Component({
selector: 'app-section-dynamic', selector: 'app-section-dynamic',
...@@ -38,6 +39,7 @@ import { UIElementType } from '../../../../../../../common/models/uI-element'; ...@@ -38,6 +39,7 @@ import { UIElementType } from '../../../../../../../common/models/uI-element';
</ng-container> </ng-container>
<app-dynamic-canvas-overlay *ngFor="let element of section.elements" <app-dynamic-canvas-overlay *ngFor="let element of section.elements"
#elementComponent
[element]="$any(element)" [element]="$any(element)"
[style.min-width.px]="element.width" [style.min-width.px]="element.width"
[style.min-height.px]="element.height" [style.min-height.px]="element.height"
...@@ -69,6 +71,8 @@ export class SectionDynamicComponent { ...@@ -69,6 +71,8 @@ export class SectionDynamicComponent {
@Input() isSelected!: boolean; @Input() isSelected!: boolean;
@Output() transferElement = new EventEmitter<{ previousSectionIndex: number, newSectionIndex: number }>(); @Output() transferElement = new EventEmitter<{ previousSectionIndex: number, newSectionIndex: number }>();
@ViewChildren('elementComponent') childElementComponents!: QueryList<CanvasElementOverlay>;
dragging = false; dragging = false;
constructor(public unitService: UnitService) { } constructor(public unitService: UnitService) { }
......
...@@ -8,10 +8,22 @@ import { UnitService } from '../../../../services/unit.service'; ...@@ -8,10 +8,22 @@ import { UnitService } from '../../../../services/unit.service';
import { DialogService } from '../../../../services/dialog.service'; import { DialogService } from '../../../../services/dialog.service';
import { SelectionService } from '../../../../services/selection.service'; import { SelectionService } from '../../../../services/selection.service';
import { Section } from '../../../../../../../common/models/section'; import { Section } from '../../../../../../../common/models/section';
import { UIElement } from '../../../../../../../common/models/uI-element';
@Component({ @Component({
selector: 'app-section-menu', selector: 'app-section-menu',
template: ` template: `
<button mat-mini-fab [matMenuTriggerFor]="elementListMenu">
<mat-icon>list</mat-icon>
</button>
<mat-menu #elementListMenu="matMenu" class="layoutMenu" xPosition="before">
<mat-action-list>
<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 [matMenuTriggerFor]="heightMenu"> <button mat-mini-fab [matMenuTriggerFor]="heightMenu">
<mat-icon>height</mat-icon> <mat-icon>height</mat-icon>
</button> </button>
...@@ -141,6 +153,7 @@ export class SectionMenuComponent implements OnInit, OnDestroy { ...@@ -141,6 +153,7 @@ export class SectionMenuComponent implements OnInit, OnDestroy {
@Input() allowDelete!: boolean; @Input() allowDelete!: boolean;
@Output() moveSection = new EventEmitter<'up' | 'down'>(); @Output() moveSection = new EventEmitter<'up' | 'down'>();
@Output() duplicateSection = new EventEmitter(); @Output() duplicateSection = new EventEmitter();
@Output() selectElementComponent = new EventEmitter<UIElement>();
@ViewChild('colorPicker') colorPicker!: ElementRef; @ViewChild('colorPicker') colorPicker!: ElementRef;
columnSizes: { value: string, unit: string }[] = []; columnSizes: { value: string, unit: string }[] = [];
...@@ -159,6 +172,10 @@ export class SectionMenuComponent implements OnInit, OnDestroy { ...@@ -159,6 +172,10 @@ export class SectionMenuComponent implements OnInit, OnDestroy {
this.unitService.updateSectionProperty(this.section, property, value); this.unitService.updateSectionProperty(this.section, property, value);
} }
selectElement(element: UIElement): void {
this.selectElementComponent.emit(element);
}
deleteSection(): void { deleteSection(): void {
this.dialogService.showConfirmDialog('Abschnitt löschen?') this.dialogService.showConfirmDialog('Abschnitt löschen?')
.pipe(takeUntil(this.ngUnsubscribe)) .pipe(takeUntil(this.ngUnsubscribe))
......
import { import {
Component, ElementRef, Input, ViewChild Component, ElementRef, Input, QueryList, ViewChild, ViewChildren
} from '@angular/core'; } from '@angular/core';
import { UnitService } from '../../../../services/unit.service'; import { UnitService } from '../../../../services/unit.service';
import { Section } from '../../../../../../../common/models/section'; import { Section } from '../../../../../../../common/models/section';
import { UIElementType } from '../../../../../../../common/models/uI-element'; import { UIElementType } from '../../../../../../../common/models/uI-element';
import { SectionDynamicComponent } from './section-dynamic.component';
import { CanvasElementOverlay } from './overlays/canvas-element-overlay';
@Component({ @Component({
selector: 'app-section-static', selector: 'app-section-static',
...@@ -14,7 +16,7 @@ import { UIElementType } from '../../../../../../../common/models/uI-element'; ...@@ -14,7 +16,7 @@ import { UIElementType } from '../../../../../../../common/models/uI-element';
[style.height.px]="section.height" [style.height.px]="section.height"
[style.background-color]="section.backgroundColor" [style.background-color]="section.backgroundColor"
(dragover)="$event.preventDefault()" (drop)="newElementDropped($event)"> (dragover)="$event.preventDefault()" (drop)="newElementDropped($event)">
<app-static-canvas-overlay <app-static-canvas-overlay #elementComponent
*ngFor="let element of section.elements" *ngFor="let element of section.elements"
[element]="$any(element)"> [element]="$any(element)">
</app-static-canvas-overlay> </app-static-canvas-overlay>
...@@ -28,6 +30,7 @@ export class SectionStaticComponent { ...@@ -28,6 +30,7 @@ export class SectionStaticComponent {
@Input() section!: Section; @Input() section!: Section;
@Input() isSelected!: boolean; @Input() isSelected!: boolean;
@ViewChild('sectionElement') sectionElement!: ElementRef; @ViewChild('sectionElement') sectionElement!: ElementRef;
@ViewChildren('elementComponent') childElementComponents!: QueryList<CanvasElementOverlay>;
constructor(public unitService: UnitService) { } constructor(public unitService: UnitService) { }
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment