diff --git a/projects/editor/src/app/components/canvas/canvas.component.ts b/projects/editor/src/app/components/canvas/canvas.component.ts index 0a5e5e840ce64abd3780fe76dc4be8a572f75de3..a2f687ebb68671ddbdde6121563d212a56a60ff3 100644 --- a/projects/editor/src/app/components/canvas/canvas.component.ts +++ b/projects/editor/src/app/components/canvas/canvas.component.ts @@ -11,6 +11,7 @@ import { CanvasElementOverlay } from './overlays/canvas-element-overlay'; import { SectionStaticComponent } from './section-static.component'; import { SectionDynamicComponent } from './section-dynamic.component'; import { SectionService } from 'editor/src/app/services/unit-services/section.service'; +import { ElementService } from 'editor/src/app/services/unit-services/element.service'; @Component({ selector: 'aspect-page-canvas', @@ -45,6 +46,7 @@ export class CanvasComponent { constructor(public selectionService: SelectionService, public unitService: UnitService, + public elementService: ElementService, public sectionService: SectionService) { } moveElementsBetweenSections(elements: UIElement[], previousSectionIndex: number, newSectionIndex: number): void { @@ -69,7 +71,7 @@ export class CanvasComponent { if (this.page.hasMaxWidth && newXPosition > this.page.maxWidth - element.dimensions.width) { newXPosition = this.page.maxWidth - element.dimensions.width; } - this.unitService.updateElementsPositionProperty([element], 'xPosition', newXPosition); + this.elementService.updateElementsPositionProperty([element], 'xPosition', newXPosition); let newYPosition = element.position.yPosition + event.distance.y; if (newYPosition < 0) { @@ -78,7 +80,7 @@ export class CanvasComponent { if (newYPosition > this.getPageHeight() - element.dimensions.height) { newYPosition = this.getPageHeight() - element.dimensions.height; } - this.unitService.updateElementsPositionProperty([element], 'yPosition', newYPosition); + this.elementService.updateElementsPositionProperty([element], 'yPosition', newYPosition); }); } } diff --git a/projects/editor/src/app/components/canvas/dynamic-section-helper-grid.component.ts b/projects/editor/src/app/components/canvas/dynamic-section-helper-grid.component.ts index 41df84a1d4a0cbe99a249832b99c80907a2cb80a..eee34a0a2f1ed2f04a6981ec0be9d5151dd83501 100644 --- a/projects/editor/src/app/components/canvas/dynamic-section-helper-grid.component.ts +++ b/projects/editor/src/app/components/canvas/dynamic-section-helper-grid.component.ts @@ -107,23 +107,23 @@ export class DynamicSectionHelperGridComponent implements OnInit, OnChanges { }); } if (dragItemData.dragType === 'move') { - this.unitService.updateElementsPositionProperty( + this.elementService.updateElementsPositionProperty( [event.item.data.element], 'gridColumn', event.container.data.gridCoordinates[0] ); - this.unitService.updateElementsPositionProperty( + this.elementService.updateElementsPositionProperty( [dragItemData.element], 'gridRow', event.container.data.gridCoordinates[1] ); } else if (event.item.data.dragType === 'resize') { - this.unitService.updateElementsPositionProperty( + this.elementService.updateElementsPositionProperty( [dragItemData.element], 'gridColumnEnd', event.container.data.gridCoordinates[0] + 1 ); - this.unitService.updateElementsPositionProperty( + this.elementService.updateElementsPositionProperty( [dragItemData.element], 'gridRowEnd', event.container.data.gridCoordinates[1] + 1 diff --git a/projects/editor/src/app/components/properties-panel/position-properties-tab/element-position-properties.component.ts b/projects/editor/src/app/components/properties-panel/position-properties-tab/element-position-properties.component.ts index ed47e355b98cc5f09dca2cce41d8ea0bb7ae75a2..5ed124368b73c3c730d4b5e96ff7edf08636a6e6 100644 --- a/projects/editor/src/app/components/properties-panel/position-properties-tab/element-position-properties.component.ts +++ b/projects/editor/src/app/components/properties-panel/position-properties-tab/element-position-properties.component.ts @@ -15,7 +15,7 @@ import { ElementService } from 'editor/src/app/services/unit-services/element.se *ngIf="positionProperties" [positionProperties]="positionProperties" [isZIndexDisabled]="isZIndexDisabled" - (updateModel)="unitService.updateSelectedElementsPositionProperty($event.property, $event.value)"> + (updateModel)="elementService.updateSelectedElementsPositionProperty($event.property, $event.value)"> </aspect-position-field-set> <aspect-dimension-field-set *ngIf="dimensions" @@ -51,7 +51,7 @@ export class ElementPositionPropertiesComponent { constructor(public unitService: UnitService, public selectionService: SelectionService, - private elementService: ElementService) { } + public elementService: ElementService) { } alignElements(direction: 'left' | 'right' | 'top' | 'bottom'): void { this.elementService.alignElements(this.selectionService.getSelectedElements() as PositionedUIElement[], direction); diff --git a/projects/editor/src/app/components/properties-panel/position-properties-tab/input-groups/dimension-field-set.component.ts b/projects/editor/src/app/components/properties-panel/position-properties-tab/input-groups/dimension-field-set.component.ts index 2964d6a4b8a82aec911a72f49313958178262e44..69889b6965cef53e411770675b183df6952efc57 100644 --- a/projects/editor/src/app/components/properties-panel/position-properties-tab/input-groups/dimension-field-set.component.ts +++ b/projects/editor/src/app/components/properties-panel/position-properties-tab/input-groups/dimension-field-set.component.ts @@ -2,6 +2,7 @@ import { Component, Input } from '@angular/core'; import { UnitService } from 'editor/src/app/services/unit-services/unit.service'; import { SelectionService } from 'editor/src/app/services/selection.service'; import { DimensionProperties, PositionProperties } from 'common/models/elements/property-group-interfaces'; +import { ElementService } from 'editor/src/app/services/unit-services/element.service'; @Component({ selector: 'aspect-dimension-field-set', @@ -123,15 +124,17 @@ export class DimensionFieldSetComponent { @Input() positionProperties: PositionProperties | undefined; @Input() dimensions!: DimensionProperties; - constructor(public unitService: UnitService, public selectionService: SelectionService) { } + constructor(public unitService: UnitService, + public elementService: ElementService, + public selectionService: SelectionService) { } updateDimensionProperty(property: string, value: any): void { - this.unitService.updateElementsDimensionsProperty(this.selectionService.getSelectedElements(), property, value); + this.elementService.updateElementsDimensionsProperty(this.selectionService.getSelectedElements(), property, value); } toggleProperty(property: string, checked:boolean): void { if (!checked) { - this.unitService.updateElementsDimensionsProperty(this.selectionService.getSelectedElements(), property, null); + this.elementService.updateElementsDimensionsProperty(this.selectionService.getSelectedElements(), property, null); } } } diff --git a/projects/editor/src/app/services/unit-services/element.service.ts b/projects/editor/src/app/services/unit-services/element.service.ts index c7e25ef9ced89a75bcba0223c2d0a6643bf61589..41a9554b42ffc32ba2f57eef3033c0068b5e2a0a 100644 --- a/projects/editor/src/app/services/unit-services/element.service.ts +++ b/projects/editor/src/app/services/unit-services/element.service.ts @@ -3,6 +3,7 @@ import { UnitService } from 'editor/src/app/services/unit-services/unit.service' import { SelectionService } from 'editor/src/app/services/selection.service'; import { IDService } from 'editor/src/app/services/id.service'; import { + CompoundElement, InputElement, PlayerElement, PositionedUIElement, UIElement, @@ -28,6 +29,7 @@ import { MessageService } from 'common/services/message.service'; import { TextElement } from 'common/models/elements/text/text'; import { ClozeDocument, ClozeElement } from 'common/models/elements/compound-elements/cloze/cloze'; import { DomSanitizer } from '@angular/platform-browser'; +import { DragNDropValueObject } from 'common/models/elements/label-interfaces'; @Injectable({ providedIn: 'root' @@ -153,7 +155,7 @@ export class ElementService { } private handleTextElementChange(element: TextElement, value: string): void { - const deletedAnchorIDs = UnitService.getRemovedTextAnchorIDs(element, value); + const deletedAnchorIDs = ElementService.getRemovedTextAnchorIDs(element, value); const refs = this.unitService.referenceManager.getTextAnchorReferences(deletedAnchorIDs); if (refs.length > 0) { this.dialogService.showDeleteReferenceDialog(refs) @@ -171,7 +173,7 @@ export class ElementService { } private handleClozeDocumentChange(element: ClozeElement, newValue: ClozeDocument): void { - const deletedElements = UnitService.getRemovedClozeElements(element, newValue); + const deletedElements = ElementService.getRemovedClozeElements(element, newValue); const refs = this.unitService.referenceManager.getElementsReferences(deletedElements); if (refs.length > 0) { this.dialogService.showDeleteReferenceDialog(refs) @@ -322,4 +324,99 @@ export class ElementService { this.unitService.elementPropertyUpdated.next(); this.unitService.updateUnitDefinition(); } + + /* - Also changes position of the element to not cover copied element. + - Also changes and registers all copied IDs. */ + duplicateElement(element: UIElement, adjustPosition: boolean = false): UIElement { + const newElement = element.getDuplicate(); + + if (newElement.position && adjustPosition) { + newElement.position.xPosition += 10; + newElement.position.yPosition += 10; + newElement.position.gridRow = null; + newElement.position.gridColumn = null; + } + + newElement.id = this.idService.getAndRegisterNewID(newElement.type); + if (newElement instanceof CompoundElement) { + newElement.getChildElements().forEach((child: UIElement) => { + child.id = this.idService.getAndRegisterNewID(child.type); + if (child.type === 'drop-list') { + (child.value as DragNDropValueObject[]).forEach(valueObject => { + valueObject.id = this.idService.getAndRegisterNewID('value'); + }); + } + }); + } + + // Special care with DropLists as they are no CompoundElement yet still have children with IDs + if (newElement.type === 'drop-list') { + (newElement.value as DragNDropValueObject[]).forEach(valueObject => { + valueObject.id = this.idService.getAndRegisterNewID('value'); + }); + } + return newElement; + } + + static getRemovedTextAnchorIDs(element: TextElement, newValue: string): string[] { + return TextElement.getAnchorIDs(element.text) + .filter(el => !TextElement.getAnchorIDs(newValue).includes(el)); + } + + static getRemovedClozeElements(cloze: ClozeElement, newClozeDoc: ClozeDocument): UIElement[] { + const newElements = ClozeElement.getDocumentChildElements(newClozeDoc); + return cloze.getChildElements() + .filter(element => !newElements.includes(element)); + } + + updateSelectedElementsPositionProperty(property: string, value: UIElementValue): void { + this.updateElementsPositionProperty(this.selectionService.getSelectedElements(), property, value); + } + + updateElementsPositionProperty(elements: UIElement[], property: string, value: UIElementValue): void { + elements.forEach(element => { + element.setPositionProperty(property, value); + }); + this.reorderElements(); + this.unitService.elementPropertyUpdated.next(); + this.unitService.updateUnitDefinition(); + } + + updateElementsDimensionsProperty(elements: UIElement[], property: string, value: number | null): void { + console.log('updateElementsDimensionsProperty', property, value); + elements.forEach(element => { + element.setDimensionsProperty(property, value); + }); + this.unitService.elementPropertyUpdated.next(); + this.unitService.updateUnitDefinition(); + } + + /* Reorder elements by their position properties, so the tab order is correct */ + reorderElements() { + const sectionElementList = this.unit.pages[this.selectionService.selectedPageIndex] + .sections[this.selectionService.selectedPageSectionIndex].elements; + const isDynamicPositioning = this.unit.pages[this.selectionService.selectedPageIndex] + .sections[this.selectionService.selectedPageSectionIndex].dynamicPositioning; + const sortDynamicPositioning = (a: PositionedUIElement, b: PositionedUIElement) => { + const rowSort = + (a.position.gridRow !== null ? a.position.gridRow : Infinity) - + (b.position.gridRow !== null ? b.position.gridRow : Infinity); + if (rowSort === 0) { + return a.position.gridColumn! - b.position.gridColumn!; + } + return rowSort; + }; + const sortStaticPositioning = (a: PositionedUIElement, b: PositionedUIElement) => { + const ySort = a.position.yPosition! - b.position.yPosition!; + if (ySort === 0) { + return a.position.xPosition! - b.position.xPosition!; + } + return ySort; + }; + if (isDynamicPositioning) { + sectionElementList.sort(sortDynamicPositioning); + } else { + sectionElementList.sort(sortStaticPositioning); + } + } } diff --git a/projects/editor/src/app/services/unit-services/section.service.ts b/projects/editor/src/app/services/unit-services/section.service.ts index b559cf6315e9fc06ba310ba103236a682bef7038..c2cc6a70b2a8e636b444130efb57c527d3c99b10 100644 --- a/projects/editor/src/app/services/unit-services/section.service.ts +++ b/projects/editor/src/app/services/unit-services/section.service.ts @@ -8,6 +8,7 @@ import { DropListElement } from 'common/models/elements/input-elements/drop-list import { ArrayUtils } from 'common/util/array'; import { IDService } from 'editor/src/app/services/id.service'; import { VisibilityRule } from 'common/models/visibility-rule'; +import { ElementService } from 'editor/src/app/services/unit-services/element.service'; @Injectable({ providedIn: 'root' @@ -16,6 +17,7 @@ export class SectionService { unit = this.unitService.unit; constructor(private unitService: UnitService, + private elementService: ElementService, private selectionService: SelectionService, private idService: IDService) { } @@ -58,7 +60,7 @@ export class SectionService { duplicateSection(section: Section, page: Page, sectionIndex: number): void { const newSection: Section = new Section({ ...section, - elements: section.elements.map(element => this.unitService.duplicateElement(element) as PositionedUIElement) + elements: section.elements.map(element => this.elementService.duplicateElement(element) as PositionedUIElement) }); page.sections.splice(sectionIndex + 1, 0, newSection); this.unitService.updateUnitDefinition(); @@ -92,7 +94,7 @@ export class SectionService { const selectedSection = this.unit.pages[this.selectionService.selectedPageIndex].sections[this.selectionService.selectedPageSectionIndex]; this.selectionService.getSelectedElements().forEach((element: UIElement) => { - selectedSection.elements.push(this.unitService.duplicateElement(element, true) as PositionedUIElement); + selectedSection.elements.push(this.elementService.duplicateElement(element, true) as PositionedUIElement); }); this.unitService.updateUnitDefinition(); } diff --git a/projects/editor/src/app/services/unit-services/unit.service.ts b/projects/editor/src/app/services/unit-services/unit.service.ts index ceacbf332618ceb09c0e4caa9c94a614b13b71d3..7ee604b7c7418e24c3dad7e826357fb491c7ac3c 100644 --- a/projects/editor/src/app/services/unit-services/unit.service.ts +++ b/projects/editor/src/app/services/unit-services/unit.service.ts @@ -1,31 +1,14 @@ import { Injectable } from '@angular/core'; -import { DomSanitizer } from '@angular/platform-browser'; -import { firstValueFrom, Subject } from 'rxjs'; -import { takeUntil } from 'rxjs/operators'; +import { Subject } from 'rxjs'; import { FileService } from 'common/services/file.service'; import { MessageService } from 'common/services/message.service'; import { Unit, UnitProperties } from 'common/models/unit'; -import { - PlayerProperties, - PositionProperties, - PropertyGroupGenerators -} from 'common/models/elements/property-group-interfaces'; import { DragNDropValueObject } from 'common/models/elements/label-interfaces'; import { - CompoundElement, InputElement, PlayerElement, PositionedUIElement, - UIElement, UIElementProperties, UIElementType, UIElementValue + CompoundElement, UIElement } from 'common/models/elements/element'; -import { ClozeDocument, ClozeElement } from 'common/models/elements/compound-elements/cloze/cloze'; -import { TextElement } from 'common/models/elements/text/text'; import { DropListElement } from 'common/models/elements/input-elements/drop-list'; -import { Section } from 'common/models/section'; -import { ElementFactory } from 'common/util/element.factory'; -import { GeometryProperties } from 'common/models/elements/geometry/geometry'; -import { AudioProperties } from 'common/models/elements/media-elements/audio'; -import { VideoProperties } from 'common/models/elements/media-elements/video'; -import { ImageProperties } from 'common/models/elements/media-elements/image'; import { StateVariable } from 'common/models/state-variable'; -import { VisibilityRule } from 'common/models/visibility-rule'; import { VersionManager } from 'common/services/version-manager'; import { ReferenceManager } from 'editor/src/app/services/reference-manager'; import { DialogService } from '../dialog.service'; @@ -44,7 +27,6 @@ export class UnitService { geometryElementPropertyUpdated: Subject<string> = new Subject<string>(); mathTableElementPropertyUpdated: Subject<string> = new Subject<string>(); referenceManager: ReferenceManager; - private ngUnsubscribe = new Subject<void>(); constructor(private selectionService: SelectionService, private veronaApiService: VeronaAPIService, @@ -131,101 +113,6 @@ export class UnitService { }); } - /* - Also changes position of the element to not cover copied element. - - Also changes and registers all copied IDs. */ - duplicateElement(element: UIElement, adjustPosition: boolean = false): UIElement { - const newElement = element.getDuplicate(); - - if (newElement.position && adjustPosition) { - newElement.position.xPosition += 10; - newElement.position.yPosition += 10; - newElement.position.gridRow = null; - newElement.position.gridColumn = null; - } - - newElement.id = this.idService.getAndRegisterNewID(newElement.type); - if (newElement instanceof CompoundElement) { - newElement.getChildElements().forEach((child: UIElement) => { - child.id = this.idService.getAndRegisterNewID(child.type); - if (child.type === 'drop-list') { - (child.value as DragNDropValueObject[]).forEach(valueObject => { - valueObject.id = this.idService.getAndRegisterNewID('value'); - }); - } - }); - } - - // Special care with DropLists as they are no CompoundElement yet still have children with IDs - if (newElement.type === 'drop-list') { - (newElement.value as DragNDropValueObject[]).forEach(valueObject => { - valueObject.id = this.idService.getAndRegisterNewID('value'); - }); - } - return newElement; - } - - static getRemovedTextAnchorIDs(element: TextElement, newValue: string): string[] { - return TextElement.getAnchorIDs(element.text) - .filter(el => !TextElement.getAnchorIDs(newValue).includes(el)); - } - - static getRemovedClozeElements(cloze: ClozeElement, newClozeDoc: ClozeDocument): UIElement[] { - const newElements = ClozeElement.getDocumentChildElements(newClozeDoc); - return cloze.getChildElements() - .filter(element => !newElements.includes(element)); - } - - updateSelectedElementsPositionProperty(property: string, value: UIElementValue): void { - this.updateElementsPositionProperty(this.selectionService.getSelectedElements(), property, value); - } - - updateElementsPositionProperty(elements: UIElement[], property: string, value: UIElementValue): void { - elements.forEach(element => { - element.setPositionProperty(property, value); - }); - this.reorderElements(); - this.elementPropertyUpdated.next(); - this.updateUnitDefinition(); - } - - updateElementsDimensionsProperty(elements: UIElement[], property: string, value: number | null): void { - console.log('updateElementsDimensionsProperty', property, value); - elements.forEach(element => { - element.setDimensionsProperty(property, value); - }); - this.elementPropertyUpdated.next(); - this.updateUnitDefinition(); - } - - /* Reorder elements by their position properties, so the tab order is correct */ - reorderElements() { - const sectionElementList = this.unit.pages[this.selectionService.selectedPageIndex] - .sections[this.selectionService.selectedPageSectionIndex].elements; - const isDynamicPositioning = this.unit.pages[this.selectionService.selectedPageIndex] - .sections[this.selectionService.selectedPageSectionIndex].dynamicPositioning; - const sortDynamicPositioning = (a: PositionedUIElement, b: PositionedUIElement) => { - const rowSort = - (a.position.gridRow !== null ? a.position.gridRow : Infinity) - - (b.position.gridRow !== null ? b.position.gridRow : Infinity); - if (rowSort === 0) { - return a.position.gridColumn! - b.position.gridColumn!; - } - return rowSort; - }; - const sortStaticPositioning = (a: PositionedUIElement, b: PositionedUIElement) => { - const ySort = a.position.yPosition! - b.position.yPosition!; - if (ySort === 0) { - return a.position.xPosition! - b.position.xPosition!; - } - return ySort; - }; - if (isDynamicPositioning) { - sectionElementList.sort(sortDynamicPositioning); - } else { - sectionElementList.sort(sortStaticPositioning); - } - } - saveUnit(): void { FileService.saveUnitToFile(JSON.stringify(this.unit)); }