diff --git a/projects/editor/src/app/components/unit-view/page-view/canvas/page-canvas.component.html b/projects/editor/src/app/components/unit-view/page-view/canvas/page-canvas.component.html index 1b6634a53e411d94bdeaa0e2b7501396d4c0273e..706cb10c053a435a882149e11414f47fd0c76e0e 100644 --- a/projects/editor/src/app/components/unit-view/page-view/canvas/page-canvas.component.html +++ b/projects/editor/src/app/components/unit-view/page-view/canvas/page-canvas.component.html @@ -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> diff --git a/projects/editor/src/app/components/unit-view/page-view/canvas/page-canvas.component.ts b/projects/editor/src/app/components/unit-view/page-view/canvas/page-canvas.component.ts index e7f48d51b5088d155f09604184aa29323e9a8d25..0b31ddd2a8560bd499bbdebb3a9316a4e786ddb5 100644 --- a/projects/editor/src/app/components/unit-view/page-view/canvas/page-canvas.component.ts +++ b/projects/editor/src/app/components/unit-view/page-view/canvas/page-canvas.component.ts @@ -1,7 +1,11 @@ -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[]; } diff --git a/projects/editor/src/app/components/unit-view/page-view/canvas/section.component.ts b/projects/editor/src/app/components/unit-view/page-view/canvas/section.component.ts index 3be5f06d19dd6d2a644761be021d547f14aedd99..70d7122ae8233468716ddae638c86dcb161788d8 100644 --- a/projects/editor/src/app/components/unit-view/page-view/canvas/section.component.ts +++ b/projects/editor/src/app/components/unit-view/page-view/canvas/section.component.ts @@ -1,8 +1,11 @@ -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'); } } diff --git a/projects/editor/src/app/components/unit-view/page-view/canvas/static-canvas-overlay.component.ts b/projects/editor/src/app/components/unit-view/page-view/canvas/static-canvas-overlay.component.ts index b70c6dea16dbb49584d9f8a3db240942be74207b..aa85692f64e7fa09ac9643730dc5a116b7c159c7 100644 --- a/projects/editor/src/app/components/unit-view/page-view/canvas/static-canvas-overlay.component.ts +++ b/projects/editor/src/app/components/unit-view/page-view/canvas/static-canvas-overlay.component.ts @@ -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'" diff --git a/projects/editor/src/app/unit.service.ts b/projects/editor/src/app/unit.service.ts index 8ff3d2611bbb944f8ca64b89829f3718e6d49bfd..042b58853435045443dc0559ea41ae95045b3d93 100644 --- a/projects/editor/src/app/unit.service.ts +++ b/projects/editor/src/app/unit.service.ts @@ -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(); }