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

[editor] Rework drag and drop of dynamic sections

To make it work that the section itself can handle drop events, but
also have the canvas to handle drops when outside of the section, all
the allowed dropLists have to be connected. Because the lists are not
properly nested (see below), this needs to be done manually by IDs.
This list is given to the necessary dropLists to make it possible to
drop items not only into them but also any other connected dropLists.

Dynamic sections have droplists for the grid cells next to the actual
elements. Elements can not be children of the grid cells because they
can span over multiple cells.

Dynamic sections don't have a general drop area, like static sections.
They have grid placeholder elements which are droplists. Therefore they
have no parent dropList to add to the list but themselves.
Static elements only have the parent, which is added to the list.

Resizing in dynamic sections is handled by the section/element-overlays
themselves.

Ticket #38
parent eebe3f36
No related branches found
No related tags found
No related merge requests found
......@@ -9,12 +9,25 @@
[style.padding.px]="page.margin"
[style.background-color]="page.backgroundColor">
<div cdkDropListGroup>
<div app-canvas-section class="section drop-list"
*ngFor="let section of page.sections; let i = index"
[section]="section"
cdkDropList cdkDropListSortingDisabled
(cdkDropListDropped)="elementDropped($event)" [cdkDropListData]="section">
</div>
<ng-container *ngFor="let section of page.sections; let i = index">
<!-- TODO split section into static and dynamic-->
<div *ngIf="!section.dynamicPositioning"
app-canvas-section class="section drop-list" id="section-{{i}}"
[section]="section"
cdkDropList cdkDropListSortingDisabled
[cdkDropListData]="{ sectionIndex: i }"
[dropListList]="dropListList"
(cdkDropListDropped)="elementDropped($event)"
[cdkDropListConnectedTo]="dropListList">
</div>
<div *ngIf="section.dynamicPositioning"
app-canvas-section class="section drop-list"
[section]="section" [sectionIndex]="i"
(transferElement)="moveElementBetweenSections($event.element,
$event.previousSectionIndex,
$event.newSectionIndex)">
</div>
</ng-container>
</div>
</div>
......
import { Component, Input } from '@angular/core';
import { CdkDragDrop, moveItemInArray, transferArrayItem } from '@angular/cdk/drag-drop';
import {
Component, Input, OnDestroy, OnInit
} from '@angular/core';
import { CdkDragDrop, moveItemInArray } from '@angular/cdk/drag-drop';
import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
import { MatSlideToggleChange } from '@angular/material/slide-toggle';
import { UnitPage, UnitPageSection } from '../../../../../../../common/unit';
import { UnitPage, UnitPageSection, UnitUIElement } from '../../../../../../../common/unit';
import { UnitService } from '../../../../unit.service';
import { SelectionService } from '../../../../selection.service';
......@@ -16,20 +20,64 @@ import { SelectionService } from '../../../../selection.service';
'::ng-deep .add-section-button span.mat-button-wrapper mat-icon {vertical-align: unset}'
]
})
export class PageCanvasComponent {
export class PageCanvasComponent implements OnInit, OnDestroy {
@Input() page!: UnitPage;
sectionEditMode: boolean = false;
dropListList: string[] = [];
private ngUnsubscribe = new Subject<void>();
constructor(private selectionService: SelectionService, public unitService: UnitService) { }
elementDropped(event: CdkDragDrop<UnitPageSection>): void {
const sourceItemModel = event.item.data;
ngOnInit(): void {
this.unitService.unit
.pipe(takeUntil(this.ngUnsubscribe))
.subscribe(() => this.generateDropListList());
}
/*
To make it work that the section itself can handle drop events, but also have the canvas to handle drops
when outside of the section, all the allowed dropLists have to be connected. Because the lists are not properly
nested (see below), this needs to be done manually by IDs.
This list is given to the necessary dropLists to make it possible to drop items not only into them
but also any other connected dropLists.
Dynamic sections have droplists for the grid cells next to the actual elements. Elements can not
be children of the grid cells because they can span over multiple cells.
Dynamic sections don't have a general drop area, like static sections. They have grid placeholder elements
which are droplists. Therefore they have no parent dropList to add to the list but themselves.
Static elements only have the parent, which is added to the list.
Resizing in dynamic sections is handled by the section/element-overlays themselves.
*/
generateDropListList(): void {
this.dropListList = [];
this.page.sections.forEach((section: UnitPageSection, index: number) => {
if (!section.dynamicPositioning) {
this.dropListList.push(`section-${index}`);
} else {
section.gridColumnSizes.split(' ').forEach((columnSize: string, columnIndex: number) => {
section.gridRowSizes.split(' ').forEach((rowSize: string, rowIndex: number) => {
this.dropListList.push(`list-${index}-${columnIndex + 1}-${rowIndex + 1}`); // grid starts counting at 1
});
});
}
});
}
moveElementBetweenSections(element: UnitUIElement, previousSectionIndex: number, newSectionIndex: number): void {
this.unitService.transferElement([element],
this.page.sections[previousSectionIndex],
this.page.sections[newSectionIndex]);
}
elementDropped(event: CdkDragDrop<DropListData>): void {
const sourceItemModel = (event.item.data as DragItemData).element;
if (event.previousContainer !== event.container) {
transferArrayItem(event.previousContainer.data.elements,
event.container.data.elements,
event.previousIndex,
event.currentIndex);
this.moveElementBetweenSections(event.item.data.element,
event.previousContainer.data.sectionIndex,
event.container.data.sectionIndex);
} else {
let newXPosition = sourceItemModel.xPosition + event.distance.x;
if (newXPosition < 0) {
......@@ -72,4 +120,19 @@ export class PageCanvasComponent {
moveItemInArray(this.page.sections, event.previousIndex, event.currentIndex);
this.unitService.setPageSections(this.page, this.page.sections);
}
ngOnDestroy(): void {
this.ngUnsubscribe.next();
this.ngUnsubscribe.complete();
}
}
export interface DragItemData {
dragType: string;
element: UnitUIElement;
}
export interface DropListData {
sectionIndex: number;
gridCoordinates?: number[];
}
import { Component, Input } from '@angular/core';
import {
Component, Input, Output, EventEmitter
} from '@angular/core';
import { CdkDragDrop } from '@angular/cdk/drag-drop/drag-events';
import { UnitPageSection } from '../../../../../../../common/unit';
import { UnitPageSection, UnitUIElement } from '../../../../../../../common/unit';
import { UnitService } from '../../../../unit.service';
import { SelectionService } from '../../../../selection.service';
import { DragItemData, DropListData } from './page-canvas.component';
@Component({
selector: '[app-canvas-section]',
......@@ -27,6 +30,8 @@ import { SelectionService } from '../../../../selection.service';
[style.height.%]="100"
cdkDropListGroup>
<!-- Dynamic sections have the droplists for the grid cells next to the actual elements. Elements can not
be children of the grid cells because they can span over multiple cells. -->
<ng-container *ngFor="let column of this.section.gridColumnSizes.split(' '); let x = index">
<ng-container *ngFor="let row of this.section.gridRowSizes.split(' '); let y = index">
<div class="grid-placeholder"
......@@ -34,7 +39,9 @@ import { SelectionService } from '../../../../selection.service';
[style.grid-column-end]="x + 1"
[style.grid-row-start]="y + 1"
[style.grid-row-end]="y + 1"
cdkDropList [cdkDropListData]="[x + 1, y + 1]" (cdkDropListDropped)="drop($event)">
cdkDropList [cdkDropListData]="{ sectionIndex: sectionIndex, gridCoordinates: [x + 1, y + 1] }"
(cdkDropListDropped)="drop($any($event))"
id="list-{{sectionIndex}}-{{x+1}}-{{y+1}}">
{{x + 1}} / {{y + 1}}
</div>
</ng-container>
......@@ -53,6 +60,8 @@ import { SelectionService } from '../../../../selection.service';
[style.grid-row-start]="element.gridRowStart"
[style.grid-row-end]="element.gridRowEnd"
cdkDropList cdkDropListSortingDisabled
[cdkDropListData]="{ sectionIndex: sectionIndex }"
[cdkDropListConnectedTo]="dropListList"
(resize)="resizeOverlay($event)"
[style.pointer-events]="dragging ? 'none' : 'auto'"
[style.position]="dragging ? 'absolute' : null"
......@@ -103,6 +112,12 @@ import { SelectionService } from '../../../../selection.service';
export class SectionComponent {
@Input() section!: UnitPageSection;
@Input() sectionEditMode: boolean = false;
@Input() sectionIndex!: number;
@Input() dropListList!: string[];
@Output() transferElement = new EventEmitter<{ element: UnitUIElement,
previousSectionIndex: number,
newSectionIndex: number }>();
selected = true;
dragging = false;
draggingElementWidth: number | undefined = 0;
......@@ -110,34 +125,46 @@ export class SectionComponent {
constructor(public selectionService: SelectionService, public unitService: UnitService) { }
drop(event: CdkDragDrop<number[]>): void {
if (event.item.data.dragType === 'move') {
drop(event: CdkDragDrop<DropListData>): void {
const dragItemData: DragItemData = event.item.data;
// Move element to other section - handled by parent (page-canvas).
if (event.previousContainer.data.sectionIndex !== event.container.data.sectionIndex) {
this.transferElement.emit({
element: event.item.data.element,
previousSectionIndex: event.previousContainer.data.sectionIndex,
newSectionIndex: event.container.data.sectionIndex
});
}
if (dragItemData.dragType === 'move') {
this.unitService.updateElementProperty(
this.selectionService.getSelectedElements(),
'gridColumnStart', event.container.data[0]
[event.item.data.element],
'gridColumnStart', event.container.data.gridCoordinates![0]
);
// Ensure the end value is at least the same as the start, otherwise the grid breaks
this.unitService.updateElementProperty(
this.selectionService.getSelectedElements(),
'gridColumnEnd', Math.max(event.item.data.element.gridColumnEnd, event.container.data[0])
[dragItemData.element],
'gridColumnEnd', Math.max(event.item.data.element.gridColumnEnd, event.container.data.gridCoordinates![0])
);
this.unitService.updateElementProperty(
this.selectionService.getSelectedElements(),
'gridRowStart', event.container.data[1]
[dragItemData.element],
'gridRowStart', event.container.data.gridCoordinates![1]
);
this.unitService.updateElementProperty(
this.selectionService.getSelectedElements(),
'gridRowEnd', Math.max(event.item.data.element.gridRowEnd, event.container.data[1])
[dragItemData.element],
'gridRowEnd', Math.max(event.item.data.element.gridRowEnd, event.container.data.gridCoordinates![1])
);
} else { // resize
} else if (event.item.data.dragType === 'resize') { // resize
this.unitService.updateElementProperty(
this.selectionService.getSelectedElements(),
'gridColumnEnd', event.container.data[0] + 1
[dragItemData.element],
'gridColumnEnd', event.container.data.gridCoordinates![0] + 1
);
this.unitService.updateElementProperty(
this.selectionService.getSelectedElements(),
'gridRowEnd', event.container.data[1] + 1
[dragItemData.element],
'gridRowEnd', event.container.data.gridCoordinates![1] + 1
);
} else {
throw new Error('Unknown drop event');
}
}
......
......@@ -7,7 +7,7 @@ import { CanvasElementOverlay } from './canvas-element-overlay';
template: `
<!-- Needs extra div because styling can interfere with drag and drop-->
<div class="draggable-element" [class.draggable-element-selected]="selected"
cdkDrag [cdkDragData]="this.element"
cdkDrag [cdkDragData]="{dragType: 'move', element: element}"
(click)="selectElement($event.shiftKey)" (cdkDragStarted)="selectElement()"
(dblclick)="openEditDialog()">
<div [style.position]="'absolute'"
......
......@@ -151,6 +151,14 @@ export class UnitService {
this.veronaApiService.sendVoeDefinitionChangedNotification();
}
/* Move element between sections */
transferElement(elements: UnitUIElement[], previousSection: UnitPageSection, newSection: UnitPageSection): void {
previousSection.elements = previousSection.elements.filter(element => !elements.includes(element));
elements.forEach(element => newSection.elements.push(element));
this._unit.next(this._unit.value);
this.veronaApiService.sendVoeDefinitionChangedNotification();
}
duplicateElementsInSection(elements: UnitUIElement[], section: UnitPageSection): void {
elements.forEach((element: UnitUIElement) => {
const newElement: UnitUIElement = { ...element };
......@@ -224,6 +232,7 @@ export class UnitService {
section[property] = value;
}
this.elementPropertyUpdated.next();
this._unit.next(this._unit.value);
this.veronaApiService.sendVoeDefinitionChangedNotification();
}
......
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