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
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 { DropListOptionEditDialogComponent } from './components/dialogs/drop-list-option-edit-dialog.component';
import { MatListModule } from '@angular/material/list';
@NgModule({
declarations: [
......@@ -93,7 +94,8 @@ import { DropListOptionEditDialogComponent } from './components/dialogs/drop-lis
provide: TranslateLoader,
useClass: EditorTranslateLoader
}
})
}),
MatListModule
],
providers: []
})
......
......@@ -14,10 +14,12 @@
[allowDelete]="page.sections.length > 1"
(moveSection)="unitService.moveSection(section, page, $event)"
(duplicateSection)="unitService.duplicateSection(section, page, i)"
(selectElementComponent)="selectElementComponent($event)"
(mouseover)="hoveredSection = i" (mouseleave)="hoveredSection = -1"> <!-- keep menu open-->
</app-section-menu>
<!-- TODO split section into static and dynamic-->
<app-section-static *ngIf="!section.dynamicPositioning"
#sectionComponent
class="section drop-list" id="section-{{i}}"
[section]="section"
[isSelected]="selectionService.selectedPageSectionIndex === i"
......@@ -29,6 +31,7 @@
(click)="selectionService.selectedPageSectionIndex = i">
</app-section-static>
<app-section-dynamic *ngIf="section.dynamicPositioning"
#sectionComponent
class="section drop-list"
[section]="section" [sectionIndex]="i"
[isSelected]="selectionService.selectedPageSectionIndex === i"
......
import {
Component, Input, OnDestroy, OnInit
Component, Input, OnDestroy, OnInit, QueryList, ViewChildren
} from '@angular/core';
import { CdkDragDrop } from '@angular/cdk/drag-drop';
import { Subject } from 'rxjs';
......@@ -9,6 +9,9 @@ import { SelectionService } from '../../../../services/selection.service';
import { Page } from '../../../../../../../common/models/page';
import { PositionedElement, UIElement } from '../../../../../../../common/models/uI-element';
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({
selector: 'app-page-canvas',
......@@ -28,6 +31,8 @@ export class CanvasComponent implements OnInit, OnDestroy {
hoveredSection: number = -1;
private ngUnsubscribe = new Subject<void>();
@ViewChildren('sectionComponent') childSectionComponents!: QueryList<SectionStaticComponent | SectionDynamicComponent>;
constructor(public selectionService: SelectionService, public unitService: UnitService) { }
ngOnInit(): void {
......@@ -113,6 +118,26 @@ export class CanvasComponent implements OnInit, OnDestroy {
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 {
this.ngUnsubscribe.next();
this.ngUnsubscribe.complete();
......
......@@ -24,6 +24,8 @@ export abstract class CanvasElementOverlay implements OnInit, OnDestroy {
protected childComponent!: ComponentRef<ElementComponent | CompoundElementComponent>;
private ngUnsubscribe = new Subject<void>();
temporaryHighlight: boolean = false;
constructor(public selectionService: SelectionService,
protected unitService: UnitService,
private componentFactoryResolver: ComponentFactoryResolver,
......@@ -54,6 +56,10 @@ export abstract class CanvasElementOverlay implements OnInit, OnDestroy {
}
setSelected(newValue: boolean): void {
this.temporaryHighlight = true;
setTimeout(() => {
this.temporaryHighlight = false;
}, 2000);
this.isSelected = newValue;
// This avoids: "NG0100: Expression has changed after it was checked"
// The selection service may change the "selected" variable after onInit has run.
......
......@@ -10,7 +10,8 @@ import { UIElement } from '../../../../../../../../common/models/uI-element';
template: `
<!-- 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. -->
<div #draggableElement class="draggable-element" [class.draggable-element-selected]="isSelected"
<div #draggableElement class="draggable-element"
[class.temporaryHighlight]="temporaryHighlight"
[style.display]="dragging ? 'none' : ''"
tabindex="-1"
cdkDrag [cdkDragData]="{dragType: 'move', element: element}"
......@@ -45,7 +46,8 @@ import { UIElement } from '../../../../../../../../common/models/uI-element';
'.draggable-element:active {cursor: grabbing}',
'.resizeHandle {position: absolute; right: 3px; bottom: 3px; z-index: 1; height: 25px}',
'.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 {
......
......@@ -9,7 +9,8 @@ import { UIElement } from '../../../../../../../../common/models/uI-element';
template: `
<!-- 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). -->
<div class="draggable-element" [class.draggable-element-selected]="isSelected"
<div class="draggable-element"
[class.temporaryHighlight]="temporaryHighlight"
cdkDrag [cdkDragData]="{dragType: 'move', element: element}"
(click)="selectElement($event.shiftKey); $event.stopPropagation()"
(cdkDragStarted)="!isSelected && selectElement()"
......@@ -45,7 +46,8 @@ import { UIElement } from '../../../../../../../../common/models/uI-element';
'.draggable-element {position: absolute}',
'.draggable-element:active {cursor: grabbing}',
'.resizeHandle {position: absolute; cursor: nwse-resize}',
'.resize-droplist {position: absolute}'
'.resize-droplist {position: absolute}',
'.temporaryHighlight {z-index: 100}'
]
})
export class StaticCanvasOverlayComponent extends CanvasElementOverlay {
......
import {
Component, Input, Output, EventEmitter
Component, Input, Output, EventEmitter, ViewChildren, QueryList
} from '@angular/core';
import { CdkDragDrop } from '@angular/cdk/drag-drop/drag-events';
import { DragItemData, DropListData } from './canvas.component';
import { UnitService } from '../../../../services/unit.service';
import { Section } from '../../../../../../../common/models/section';
import { UIElementType } from '../../../../../../../common/models/uI-element';
import { CanvasElementOverlay } from './overlays/canvas-element-overlay';
@Component({
selector: 'app-section-dynamic',
......@@ -38,6 +39,7 @@ import { UIElementType } from '../../../../../../../common/models/uI-element';
</ng-container>
<app-dynamic-canvas-overlay *ngFor="let element of section.elements"
#elementComponent
[element]="$any(element)"
[style.min-width.px]="element.width"
[style.min-height.px]="element.height"
......@@ -69,6 +71,8 @@ export class SectionDynamicComponent {
@Input() isSelected!: boolean;
@Output() transferElement = new EventEmitter<{ previousSectionIndex: number, newSectionIndex: number }>();
@ViewChildren('elementComponent') childElementComponents!: QueryList<CanvasElementOverlay>;
dragging = false;
constructor(public unitService: UnitService) { }
......
......@@ -8,10 +8,22 @@ import { UnitService } from '../../../../services/unit.service';
import { DialogService } from '../../../../services/dialog.service';
import { SelectionService } from '../../../../services/selection.service';
import { Section } from '../../../../../../../common/models/section';
import { UIElement } from '../../../../../../../common/models/uI-element';
@Component({
selector: 'app-section-menu',
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">
<mat-icon>height</mat-icon>
</button>
......@@ -141,6 +153,7 @@ export class SectionMenuComponent implements OnInit, OnDestroy {
@Input() allowDelete!: boolean;
@Output() moveSection = new EventEmitter<'up' | 'down'>();
@Output() duplicateSection = new EventEmitter();
@Output() selectElementComponent = new EventEmitter<UIElement>();
@ViewChild('colorPicker') colorPicker!: ElementRef;
columnSizes: { value: string, unit: string }[] = [];
......@@ -159,6 +172,10 @@ export class SectionMenuComponent implements OnInit, OnDestroy {
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))
......
import {
Component, ElementRef, Input, ViewChild
Component, ElementRef, Input, QueryList, ViewChild, ViewChildren
} from '@angular/core';
import { UnitService } from '../../../../services/unit.service';
import { Section } from '../../../../../../../common/models/section';
import { UIElementType } from '../../../../../../../common/models/uI-element';
import { SectionDynamicComponent } from './section-dynamic.component';
import { CanvasElementOverlay } from './overlays/canvas-element-overlay';
@Component({
selector: 'app-section-static',
......@@ -14,7 +16,7 @@ import { UIElementType } from '../../../../../../../common/models/uI-element';
[style.height.px]="section.height"
[style.background-color]="section.backgroundColor"
(dragover)="$event.preventDefault()" (drop)="newElementDropped($event)">
<app-static-canvas-overlay
<app-static-canvas-overlay #elementComponent
*ngFor="let element of section.elements"
[element]="$any(element)">
</app-static-canvas-overlay>
......@@ -28,6 +30,7 @@ export class SectionStaticComponent {
@Input() section!: Section;
@Input() isSelected!: boolean;
@ViewChild('sectionElement') sectionElement!: ElementRef;
@ViewChildren('elementComponent') childElementComponents!: QueryList<CanvasElementOverlay>;
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