@metadev/daga-angular
Version:
Diagramming engine for editing models on the Web. Made by Metadev.
1 lines • 176 kB
Source Map (JSON)
{"version":3,"file":"metadev-daga-angular.mjs","sources":["../../../../libs/daga-angular/src/lib/collapse-button/collapse-button.component.ts","../../../../libs/daga-angular/src/lib/collapse-button/collapse-button.component.html","../../../../libs/daga-angular/src/lib/services/canvas-provider.service.ts","../../../../libs/daga-angular/src/lib/services/daga-configuration.service.ts","../../../../libs/daga-angular/src/lib/diagram-buttons/diagram-buttons.component.ts","../../../../libs/daga-angular/src/lib/diagram-buttons/diagram-buttons.component.html","../../../../libs/daga-angular/src/lib/errors/errors.component.ts","../../../../libs/daga-angular/src/lib/errors/errors.component.html","../../../../libs/daga-angular/src/lib/palette/palette.component.ts","../../../../libs/daga-angular/src/lib/palette/palette.component.html","../../../../libs/daga-angular/src/lib/property-editor/property-settings/property-settings.component.ts","../../../../libs/daga-angular/src/lib/property-editor/property-settings/property-settings.component.html","../../../../libs/daga-angular/src/lib/property-editor/autocomplete/autocomplete.component.ts","../../../../libs/daga-angular/src/lib/property-editor/autocomplete/autocomplete.component.html","../../../../libs/daga-angular/src/lib/property-editor/option-list-editor/option-list-editor.component.ts","../../../../libs/daga-angular/src/lib/property-editor/option-list-editor/option-list-editor.component.html","../../../../libs/daga-angular/src/lib/property-editor/text-list-editor/text-list-editor.component.ts","../../../../libs/daga-angular/src/lib/property-editor/text-list-editor/text-list-editor.component.html","../../../../libs/daga-angular/src/lib/property-editor/text-map-editor/text-map-editor.component.ts","../../../../libs/daga-angular/src/lib/property-editor/text-map-editor/text-map-editor.component.html","../../../../libs/daga-angular/src/lib/property-editor/object-editor/object-editor.component.ts","../../../../libs/daga-angular/src/lib/property-editor/object-editor/object-editor.component.html","../../../../libs/daga-angular/src/lib/property-editor/property-editor.component.ts","../../../../libs/daga-angular/src/lib/property-editor/property-editor.component.html","../../../../libs/daga-angular/src/lib/diagram/diagram.component.ts","../../../../libs/daga-angular/src/lib/diagram/diagram.component.html","../../../../libs/daga-angular/src/lib/daga.module.ts","../../../../libs/daga-angular/src/metadev-daga-angular.ts"],"sourcesContent":["import { Component, ElementRef, Input } from '@angular/core';\r\nimport { Side } from '@metadev/daga';\r\nimport * as d3 from 'd3';\r\n\r\n/**\r\n * Button used to collapse components that implement it.\r\n * @private\r\n */\r\n@Component({\r\n selector: 'daga-collapse-button',\r\n templateUrl: './collapse-button.component.html'\r\n})\r\nexport class CollapseButtonComponent {\r\n @Input()\r\n collapsableSelector!: string | ElementRef | HTMLElement;\r\n @Input()\r\n collapsableAdditionalSelector!: string;\r\n @Input()\r\n collapsed = false;\r\n @Input()\r\n disabled = false;\r\n @Input()\r\n direction = Side.Bottom;\r\n @Input()\r\n rule = 'visibility';\r\n @Input()\r\n collapsedValue = 'collapse';\r\n @Input()\r\n visibleValue = 'visible';\r\n\r\n Side = Side;\r\n\r\n toggleCollapse(): void {\r\n if (!this.disabled) {\r\n this.collapsed = !this.collapsed;\r\n let selection;\r\n if (this.collapsableSelector instanceof ElementRef) {\r\n selection = d3.select(this.collapsableSelector.nativeElement);\r\n if (this.collapsableAdditionalSelector) {\r\n selection = selection.select(this.collapsableAdditionalSelector);\r\n }\r\n } else if (this.collapsableSelector instanceof HTMLElement) {\r\n selection = d3.select(this.collapsableSelector);\r\n if (this.collapsableAdditionalSelector) {\r\n selection = selection.select(this.collapsableAdditionalSelector);\r\n }\r\n } else {\r\n selection = d3.select(this.collapsableSelector);\r\n if (this.collapsableAdditionalSelector) {\r\n selection = selection.select(this.collapsableAdditionalSelector);\r\n }\r\n }\r\n selection.style(\r\n this.rule,\r\n this.collapsed ? this.collapsedValue : this.visibleValue\r\n );\r\n }\r\n }\r\n\r\n getClass(): string {\r\n switch (this.direction) {\r\n case Side.Right:\r\n if (this.disabled) {\r\n return 'daga-horizontal-none';\r\n } else if (this.collapsed) {\r\n return 'daga-horizontal-right';\r\n } else {\r\n return 'daga-horizontal-left';\r\n }\r\n case Side.Bottom:\r\n if (this.disabled) {\r\n return 'daga-vertical-none';\r\n } else if (this.collapsed) {\r\n return 'daga-vertical-down';\r\n } else {\r\n return 'daga-vertical-up';\r\n }\r\n case Side.Left:\r\n if (this.disabled) {\r\n return 'daga-horizontal-none';\r\n } else if (this.collapsed) {\r\n return 'daga-horizontal-left';\r\n } else {\r\n return 'daga-horizontal-right';\r\n }\r\n case Side.Top:\r\n if (this.disabled) {\r\n return 'daga-vertical-none';\r\n } else if (this.collapsed) {\r\n return 'daga-vertical-up';\r\n } else {\r\n return 'daga-vertical-down';\r\n }\r\n }\r\n }\r\n}\r\n","<button\r\n class=\"daga-collapse-button daga-{{ direction }}\"\r\n (click)=\"toggleCollapse()\"\r\n>\r\n <div [class]=\"getClass()\"></div>\r\n</button>\r\n","import { Injectable } from '@angular/core';\r\nimport {\r\n Canvas,\r\n DiagramCanvas,\r\n DiagramConfig,\r\n DiagramEditor\r\n} from '@metadev/daga';\r\nimport { ReplaySubject } from 'rxjs';\r\n\r\n/**\r\n * A provider for the {@link Canvas} associated with a {@link DiagramComponent} context.\r\n * @public\r\n */\r\n@Injectable()\r\nexport class CanvasProviderService {\r\n private _canvas!: Canvas;\r\n\r\n private canvasSubject$ = new ReplaySubject<Canvas>();\r\n\r\n /**\r\n * Subject used to track when the canvas has been updated.\r\n * @public\r\n */\r\n public canvas$ = this.canvasSubject$.asObservable();\r\n\r\n private viewInitializedSubject$ = new ReplaySubject<Canvas>();\r\n\r\n /**\r\n * Subject used to track when the view of the canvas has been updated after updating the canvas.\r\n * @public\r\n */\r\n public viewInitialized$ = this.viewInitializedSubject$.asObservable();\r\n\r\n /**\r\n * Initialize the diagram canvas object of this context using the given diagram configuration.\r\n * @private\r\n * @param parentComponent A diagram editor.\r\n * @param config A diagram configuration.\r\n */\r\n initCanvas(parentComponent: DiagramEditor, config: DiagramConfig) {\r\n this._canvas = new DiagramCanvas(parentComponent, config);\r\n this.canvasSubject$.next(this._canvas);\r\n }\r\n\r\n /**\r\n * Attach the canvas of this context to an HTML element to render it there.\r\n * @private\r\n * @param appendTo An HTML element.\r\n */\r\n initCanvasView(appendTo: HTMLElement) {\r\n this._canvas.initView(appendTo);\r\n this.viewInitializedSubject$.next(this._canvas);\r\n }\r\n\r\n /**\r\n * Get the current canvas of this context.\r\n * @public\r\n * @returns A canvas.\r\n */\r\n getCanvas(): Canvas {\r\n return this._canvas;\r\n }\r\n}\r\n","import { Injectable } from '@angular/core';\r\nimport { DiagramConfig } from '@metadev/daga';\r\nimport { ReplaySubject } from 'rxjs';\r\n\r\n/**\r\n * A provider for the {@link DiagramConfig} associated with a {@link DiagramComponent} context.\r\n * @public\r\n */\r\n@Injectable()\r\nexport class DagaConfigurationService {\r\n private _config!: DiagramConfig;\r\n\r\n private configSubject$ = new ReplaySubject<DiagramConfig>();\r\n\r\n /**\r\n * Subject used to track when the diagram configuration has been updated.\r\n * @public\r\n */\r\n public config$ = this.configSubject$.asObservable();\r\n\r\n /**\r\n * Set the diagram configuration of this context.\r\n * @private\r\n * @param config A diagram configuration.\r\n */\r\n init(config: DiagramConfig) {\r\n this._config = config;\r\n this.configSubject$.next(config);\r\n }\r\n\r\n /**\r\n * Get the current diagram configuration of this context.\r\n * @public\r\n * @returns A diagram configuration.\r\n */\r\n getConfig(): DiagramConfig {\r\n return this._config;\r\n }\r\n}\r\n","import {\r\n AfterViewInit,\r\n ChangeDetectionStrategy,\r\n Component,\r\n ElementRef,\r\n inject,\r\n Input,\r\n OnDestroy,\r\n OnInit,\r\n ViewChild\r\n} from '@angular/core';\r\nimport {\r\n ApplyLayoutAction,\r\n Canvas,\r\n Corner,\r\n DiagramActions,\r\n DiagramButtons,\r\n DiagramConfig,\r\n getLocationsOfNodes,\r\n layouts,\r\n Side\r\n} from '@metadev/daga';\r\nimport * as d3 from 'd3';\r\nimport { Subscription } from 'rxjs';\r\nimport { CanvasProviderService } from '../services/canvas-provider.service';\r\nimport { DagaConfigurationService } from '../services/daga-configuration.service';\r\n\r\n/**\r\n * Buttons used to trigger diagram functionalities by the user.\r\n * @private\r\n */\r\n@Component({\r\n selector: 'daga-diagram-buttons',\r\n templateUrl: './diagram-buttons.component.html',\r\n changeDetection: ChangeDetectionStrategy.OnPush\r\n})\r\nexport class DiagramButtonsComponent\r\n implements DiagramButtons, OnInit, AfterViewInit, OnDestroy\r\n{\r\n #canvasProvider = inject(CanvasProviderService);\r\n #configService = inject(DagaConfigurationService);\r\n\r\n get canvas(): Canvas {\r\n return this.#canvasProvider.getCanvas();\r\n }\r\n\r\n @ViewChild('collapsableButtons')\r\n collapsableButtons!: ElementRef<HTMLDivElement>;\r\n @Input()\r\n location!: Corner;\r\n @Input()\r\n direction!: Side;\r\n @Input()\r\n enableAction = true;\r\n @Input()\r\n enableFilter = false;\r\n @Input()\r\n enableLayout = false;\r\n @Input()\r\n enableSelection = true;\r\n @Input()\r\n enableZoom = true;\r\n\r\n filterOn = false;\r\n collapsed = true;\r\n animationOngoing = false;\r\n private sizeAttribute!: string;\r\n private transformFunction!: string;\r\n private transformOrigin!: string;\r\n private marginSide!: string;\r\n private collapsableButtonsSize!: string;\r\n\r\n private sub?: Subscription;\r\n\r\n DiagramActions = DiagramActions;\r\n\r\n ngOnInit() {\r\n this.sub = this.#configService.config$.subscribe((c) => {\r\n this.init(c);\r\n });\r\n }\r\n\r\n ngAfterViewInit() {\r\n this.recalculateSizeOfButtons();\r\n }\r\n\r\n ngOnDestroy(): void {\r\n this.sub?.unsubscribe();\r\n }\r\n\r\n init(config: DiagramConfig) {\r\n this.filterOn = false;\r\n this.collapsed = true;\r\n this.animationOngoing = false;\r\n this.location = config.components?.buttons?.location || Corner.BottomRight;\r\n this.direction = config.components?.buttons?.direction || Side.Top;\r\n this.enableAction = config.components?.buttons?.enableAction !== false;\r\n this.enableFilter = config.components?.buttons?.enableFilter === true;\r\n this.enableLayout = config.components?.buttons?.enableLayout === true;\r\n this.enableSelection =\r\n config.components?.buttons?.enableSelection !== false;\r\n this.enableZoom = config.components?.buttons?.enableZoom !== false;\r\n switch (this.direction) {\r\n case Side.Bottom:\r\n this.sizeAttribute = 'height';\r\n this.transformFunction = 'scaleY';\r\n this.transformOrigin = 'top';\r\n this.marginSide = 'bottom';\r\n break;\r\n case Side.Top:\r\n this.sizeAttribute = 'height';\r\n this.transformFunction = 'scaleY';\r\n this.transformOrigin = 'bottom';\r\n this.marginSide = 'top';\r\n break;\r\n case Side.Left:\r\n this.sizeAttribute = 'width';\r\n this.transformFunction = 'scaleX';\r\n this.transformOrigin = 'right';\r\n this.marginSide = 'left';\r\n break;\r\n case Side.Right:\r\n this.sizeAttribute = 'width';\r\n this.transformFunction = 'scaleX';\r\n this.transformOrigin = 'left';\r\n this.marginSide = 'right';\r\n break;\r\n }\r\n this.recalculateSizeOfButtons();\r\n }\r\n\r\n recalculateSizeOfButtons() {\r\n if (this.collapsableButtons) {\r\n const numberOfCollapsableButtons =\r\n (this.enableZoom &&\r\n this.canvas.canUserPerformAction(DiagramActions.Zoom)\r\n ? 1\r\n : 0) +\r\n (this.enableAction ? 2 : 0) +\r\n (this.enableSelection ? 5 : 0) +\r\n (this.enableLayout ? 1 : 0) +\r\n (this.enableFilter ? 1 : 0);\r\n this.collapsableButtonsSize = `${4 * numberOfCollapsableButtons}rem`;\r\n d3.select(this.collapsableButtons.nativeElement)\r\n .style(`margin-${this.marginSide}`, '-1rem')\r\n .style(this.sizeAttribute, '0rem')\r\n .style('transform', `${this.transformFunction}(0)`)\r\n .style('transform-origin', this.transformOrigin);\r\n }\r\n }\r\n\r\n // Collapse functions\r\n\r\n async toggleCollapse() {\r\n if (!this.animationOngoing) {\r\n const duration = 500;\r\n const collapsableButtons = d3.select(\r\n this.collapsableButtons.nativeElement\r\n );\r\n if (this.collapsed) {\r\n this.collapsed = false;\r\n collapsableButtons\r\n .transition()\r\n .duration(duration)\r\n .ease(d3.easeLinear)\r\n .style(this.sizeAttribute, this.collapsableButtonsSize)\r\n .style('transform', `${this.transformFunction}(1)`);\r\n setTimeout(() => {\r\n this.animationOngoing = false;\r\n }, duration);\r\n } else {\r\n this.collapsed = true;\r\n collapsableButtons\r\n .transition()\r\n .duration(duration)\r\n .ease(d3.easeLinear)\r\n .style(this.sizeAttribute, '0rem')\r\n .style('transform', `${this.transformFunction}(0)`);\r\n setTimeout(() => {\r\n this.animationOngoing = false;\r\n }, duration);\r\n }\r\n }\r\n }\r\n\r\n // Button functions\r\n\r\n zoomIn(): void {\r\n this.canvas.zoomBy(this.canvas.zoomFactor);\r\n }\r\n\r\n zoomOut(): void {\r\n this.canvas.zoomBy(1 / this.canvas.zoomFactor);\r\n }\r\n\r\n center(): void {\r\n this.canvas.center();\r\n }\r\n\r\n layout(): void {\r\n if (this.canvas.layoutFormat && this.canvas.layoutFormat in layouts) {\r\n const from = getLocationsOfNodes(this.canvas.model);\r\n layouts[this.canvas.layoutFormat].apply(this.canvas.model);\r\n const to = getLocationsOfNodes(this.canvas.model);\r\n const action = new ApplyLayoutAction(this.canvas, from, to);\r\n this.canvas.actionStack.add(action);\r\n }\r\n }\r\n\r\n filter(): void {\r\n this.filterOn = !this.filterOn;\r\n const priorityThresholds = this.canvas.getPriorityThresholdOptions();\r\n if (priorityThresholds && priorityThresholds.length >= 2) {\r\n this.canvas.setPriorityThreshold(\r\n priorityThresholds[this.filterOn ? 1 : 0]\r\n );\r\n }\r\n }\r\n\r\n undo(): void {\r\n this.canvas.actionStack.undo();\r\n }\r\n\r\n redo(): void {\r\n this.canvas.actionStack.redo();\r\n }\r\n\r\n copySelection(): void {\r\n this.canvas.userSelection.copyToClipboard();\r\n }\r\n\r\n cutSelection(): void {\r\n this.canvas.userSelection.cutToClipboard();\r\n }\r\n\r\n pasteSelection(): void {\r\n this.canvas.userSelection.pasteFromClipboard();\r\n }\r\n\r\n deleteSelection(): void {\r\n this.canvas.userSelection.removeFromModel();\r\n }\r\n\r\n startMultipleSelection(): void {\r\n this.canvas.multipleSelectionOn = true;\r\n }\r\n}\r\n","<div class=\"daga-diagram-buttons daga-{{ location }} daga-{{ direction }}\">\r\n @if (enableZoom && canvas.canUserPerformAction(DiagramActions.Zoom)) {\r\n <button\r\n class=\"daga-zoom-in\"\r\n (click)=\"zoomIn()\"\r\n >\r\n <span class=\"daga-tooltip\">Zoom in</span>\r\n </button>\r\n }\r\n @if (enableZoom && canvas.canUserPerformAction(DiagramActions.Zoom)) {\r\n <button\r\n class=\"daga-zoom-out\"\r\n (click)=\"zoomOut()\"\r\n >\r\n <span class=\"daga-tooltip\">Zoom out</span>\r\n </button>\r\n }\r\n <div #collapsableButtons class=\"daga-collapsable-buttons daga-collapsed\">\r\n @if (enableZoom && canvas.canUserPerformAction(DiagramActions.Zoom)) {\r\n <button\r\n class=\"daga-center\"\r\n (click)=\"center()\"\r\n >\r\n <span class=\"daga-tooltip\">Fit diagram to screen</span>\r\n </button>\r\n }\r\n @if (enableAction) {\r\n <button class=\"daga-undo\" (click)=\"undo()\">\r\n <span class=\"daga-tooltip\">Undo</span>\r\n </button>\r\n }\r\n @if (enableAction) {\r\n <button class=\"daga-redo\" (click)=\"redo()\">\r\n <span class=\"daga-tooltip\">Redo</span>\r\n </button>\r\n }\r\n @if (enableSelection) {\r\n <button class=\"daga-copy\" (click)=\"copySelection()\">\r\n <span class=\"daga-tooltip\">Copy</span>\r\n </button>\r\n }\r\n @if (enableSelection) {\r\n <button class=\"daga-cut\" (click)=\"cutSelection()\">\r\n <span class=\"daga-tooltip\">Cut</span>\r\n </button>\r\n }\r\n @if (enableSelection) {\r\n <button\r\n class=\"daga-multiple-selection\"\r\n [class]=\"canvas.multipleSelectionOn ? 'daga-on' : 'daga-off'\"\r\n (click)=\"startMultipleSelection()\"\r\n >\r\n <span class=\"daga-tooltip\">Multiple selection</span>\r\n </button>\r\n }\r\n @if (enableSelection) {\r\n <button\r\n class=\"daga-paste\"\r\n (click)=\"pasteSelection()\"\r\n >\r\n <span class=\"daga-tooltip\">Paste</span>\r\n </button>\r\n }\r\n @if (enableSelection) {\r\n <button\r\n class=\"daga-delete\"\r\n (click)=\"deleteSelection()\"\r\n >\r\n <span class=\"daga-tooltip\">Delete</span>\r\n </button>\r\n }\r\n @if (enableLayout) {\r\n <button class=\"daga-layout\" (click)=\"layout()\">\r\n <span class=\"daga-tooltip\">Apply layout</span>\r\n </button>\r\n }\r\n @if (enableFilter) {\r\n <button\r\n class=\"daga-filter\"\r\n [class]=\"filterOn ? 'daga-on' : 'daga-off'\"\r\n (click)=\"filter()\"\r\n >\r\n <span class=\"daga-tooltip\">Apply filter</span>\r\n </button>\r\n }\r\n </div>\r\n <button class=\"daga-more-options\" (click)=\"toggleCollapse()\">\r\n @if (!collapsed) {\r\n <span class=\"daga-tooltip\">Less options</span>\r\n }\r\n @if (collapsed) {\r\n <span class=\"daga-tooltip\">More options</span>\r\n }\r\n </button>\r\n</div>\r\n","import {\r\n AfterViewInit,\r\n ChangeDetectionStrategy,\r\n ChangeDetectorRef,\r\n Component,\r\n ElementRef,\r\n inject,\r\n OnDestroy,\r\n ViewChild\r\n} from '@angular/core';\r\nimport { Canvas, DiagramError, Side } from '@metadev/daga';\r\nimport * as d3 from 'd3';\r\nimport { map, merge, Subscription } from 'rxjs';\r\nimport { CollapseButtonComponent } from '../collapse-button/collapse-button.component';\r\nimport { CanvasProviderService } from '../services/canvas-provider.service';\r\n\r\n/**\r\n * Displays the errors detected by a diagram's validators.\r\n * @private\r\n * @see DiagramValidator\r\n */\r\n@Component({\r\n selector: 'daga-errors',\r\n templateUrl: './errors.component.html',\r\n imports: [CollapseButtonComponent],\r\n changeDetection: ChangeDetectionStrategy.OnPush\r\n})\r\nexport class ErrorsComponent implements AfterViewInit, OnDestroy {\r\n #canvasProvider = inject(CanvasProviderService);\r\n #cdf = inject(ChangeDetectorRef);\r\n\r\n get canvas(): Canvas {\r\n return this.#canvasProvider.getCanvas();\r\n }\r\n\r\n @ViewChild('errors') errorsContainer!: ElementRef<HTMLDivElement>;\r\n\r\n errors: DiagramError[] = [];\r\n\r\n private canvasSub?: Subscription;\r\n private validationSub?: Subscription;\r\n\r\n Side = Side;\r\n\r\n ngAfterViewInit(): void {\r\n this.canvasSub = this.#canvasProvider.canvas$.subscribe((c) => {\r\n this.updateCanvas(c);\r\n });\r\n }\r\n\r\n ngOnDestroy(): void {\r\n this.canvasSub?.unsubscribe();\r\n this.validationSub?.unsubscribe();\r\n }\r\n\r\n selectPanel(): d3.Selection<d3.BaseType, unknown, null, unknown> {\r\n return d3\r\n .select(this.errorsContainer.nativeElement)\r\n .select('.daga-error-panel');\r\n }\r\n\r\n private updateCanvas(canvas: Canvas) {\r\n this.validationSub?.unsubscribe();\r\n this.validationSub = merge(canvas.validatorChange$, canvas.diagramChange$)\r\n .pipe(map(() => this.validate()))\r\n .subscribe();\r\n }\r\n\r\n validate() {\r\n this.errors = [];\r\n for (const validator of this.canvas.validators) {\r\n const newErrors = validator.validate(this.canvas.model);\r\n this.errors = [...this.errors, ...newErrors];\r\n }\r\n // need to force detection changes here for some reason...\r\n this.#cdf.detectChanges();\r\n }\r\n\r\n showError(error: DiagramError): void {\r\n if (\r\n error.elementId &&\r\n (error.propertyNames === undefined || error.propertyNames.length === 0)\r\n ) {\r\n const element =\r\n this.canvas.model.nodes.get(error.elementId) ||\r\n this.canvas.model.connections.get(error.elementId);\r\n if (element) {\r\n this.canvas.userHighlight.add(element);\r\n }\r\n } else if (\r\n error.elementId &&\r\n error.propertyNames !== undefined &&\r\n error.propertyNames.length > 0\r\n ) {\r\n this.canvas.userSelection.openInPropertyEditor(\r\n this.canvas.model.nodes.get(error.elementId) ||\r\n this.canvas.model.connections.get(error.elementId)\r\n );\r\n const element =\r\n this.canvas.model.nodes.get(error.elementId) ||\r\n this.canvas.model.connections.get(error.elementId);\r\n if (element) {\r\n this.canvas.userHighlight.add(element);\r\n }\r\n this.canvas.parentComponent?.propertyEditor?.highlightProperty(\r\n ...error.propertyNames\r\n );\r\n } else if (\r\n !error.elementId &&\r\n error.propertyNames !== undefined &&\r\n error.propertyNames.length > 0\r\n ) {\r\n this.canvas.userSelection.openInPropertyEditor();\r\n this.canvas.parentComponent?.propertyEditor?.highlightProperty(\r\n ...error.propertyNames\r\n );\r\n }\r\n }\r\n}\r\n","<div #errorsContainer class=\"daga-errors\">\r\n @if (errors.length === 0) {\r\n <div\r\n class=\"daga-errors-summary daga-no-errors daga-prevent-user-select\"\r\n >\r\n <span>No errors found</span>\r\n </div>\r\n }\r\n @if (errors.length > 0) {\r\n <div\r\n class=\"daga-errors-summary daga-with-errors daga-prevent-user-select\"\r\n >\r\n <span>{{ errors.length }} errors found</span>\r\n <div class=\"daga-collapse-button-container\">\r\n <daga-collapse-button\r\n [collapsableSelector]=\"errorsContainer\"\r\n [collapsableAdditionalSelector]=\"'.daga-error-panel'\"\r\n [direction]=\"Side.Top\"\r\n [rule]=\"'display'\"\r\n [collapsedValue]=\"'none'\"\r\n [visibleValue]=\"'block'\"\r\n />\r\n </div>\r\n </div>\r\n }\r\n @if (errors.length > 0) {\r\n <div class=\"daga-error-panel\">\r\n <ol>\r\n @for (error of errors; track error; let i = $index) {\r\n <li\r\n (click)=\"showError(error)\"\r\n [innerHTML]=\"error.message\"\r\n ></li>\r\n }\r\n </ol>\r\n </div>\r\n }\r\n </div>\r\n","import {\r\n AfterViewInit,\r\n ChangeDetectionStrategy,\r\n Component,\r\n ElementRef,\r\n inject,\r\n Input,\r\n OnDestroy,\r\n OnInit,\r\n ViewChild\r\n} from '@angular/core';\r\nimport {\r\n AddNodeAction,\r\n Canvas,\r\n ClosedShape,\r\n ConnectionTemplateConfig,\r\n Corner,\r\n CursorStyle,\r\n DIAGRAM_FIELD_DEFAULTS,\r\n DiagramActions,\r\n DiagramConfig,\r\n DiagramConnectionType,\r\n DiagramNodeType,\r\n DragEvents,\r\n Events,\r\n filterByOnlyDescendants,\r\n generalClosedPath,\r\n getLeftMargin,\r\n getTopMargin,\r\n NodeTemplateConfig,\r\n Palette,\r\n PaletteSectionConfig,\r\n Point,\r\n setCursorStyle,\r\n Side\r\n} from '@metadev/daga';\r\nimport * as d3 from 'd3';\r\nimport { Subscription } from 'rxjs';\r\nimport { CollapseButtonComponent } from '../collapse-button/collapse-button.component';\r\nimport { CanvasProviderService } from '../services/canvas-provider.service';\r\nimport { DagaConfigurationService } from '../services/daga-configuration.service';\r\n\r\n/**\r\n * Distance in pixels by which labels must be shifted in order to be centered properly.\r\n */\r\nconst LABEL_VERTICAL_SHIFT_PX = 6;\r\n\r\n/**\r\n * Palette that the user can drag and drop nodes from.\r\n * @private\r\n * @see DiagramConfig\r\n * @see DiagramNode\r\n */\r\n@Component({\r\n selector: 'daga-palette',\r\n templateUrl: './palette.component.html',\r\n imports: [CollapseButtonComponent],\r\n changeDetection: ChangeDetectionStrategy.OnPush\r\n})\r\nexport class PaletteComponent\r\n implements Palette, OnInit, AfterViewInit, OnDestroy\r\n{\r\n #canvasProvider = inject(CanvasProviderService);\r\n #configService = inject(DagaConfigurationService);\r\n\r\n get canvas(): Canvas {\r\n return this.#canvasProvider.getCanvas();\r\n }\r\n\r\n @ViewChild('panel') panel!: ElementRef<HTMLDivElement>;\r\n\r\n @Input()\r\n palettes!: PaletteSectionConfig[];\r\n @Input()\r\n currentPalette!: PaletteSectionConfig;\r\n @Input()\r\n currentCategory = '';\r\n @Input()\r\n location!: Corner;\r\n @Input()\r\n direction!: Side;\r\n @Input()\r\n width!: string;\r\n @Input()\r\n height?: string;\r\n @Input()\r\n gap!: string;\r\n\r\n private priorityThreshold?: number;\r\n\r\n private sub?: Subscription;\r\n\r\n ngOnInit(): void {\r\n /*\r\n reload using the canvas observable instead of the config one\r\n because we're getting the type info from the model, not the configuration\r\n */\r\n this.sub = this.#canvasProvider.canvas$.subscribe(() => {\r\n this.init(this.#configService.getConfig());\r\n });\r\n }\r\n\r\n ngAfterViewInit(): void {\r\n this.refreshPalette();\r\n switch (this.direction) {\r\n case Side.Bottom:\r\n case Side.Top:\r\n this.selectPanel()\r\n .style('width', this.width)\r\n .select('.daga-palette-view')\r\n .style('flex-direction', 'row');\r\n if (this.height) {\r\n this.selectPanel()\r\n .select('.daga-panel-content')\r\n .style('height', this.height);\r\n }\r\n break;\r\n case Side.Left:\r\n case Side.Right:\r\n this.selectPanel()\r\n .style('height', this.width)\r\n .select('.daga-palette-view')\r\n .style('flex-direction', 'column');\r\n if (this.height) {\r\n this.selectPanel()\r\n .select('.daga-panel-content')\r\n .style('width', this.height);\r\n }\r\n break;\r\n }\r\n this.selectPanel()\r\n .select('.daga-palette-view')\r\n .style('flex-wrap', 'wrap')\r\n .style('justify-content', 'center')\r\n .style('gap', this.gap);\r\n }\r\n\r\n ngOnDestroy(): void {\r\n this.sub?.unsubscribe();\r\n }\r\n\r\n init(config: DiagramConfig): void {\r\n this.location = config.components?.palette?.location || Corner.TopLeft;\r\n this.direction = config.components?.palette?.direction || Side.Bottom;\r\n this.width = config.components?.palette?.width || '12rem';\r\n this.height = config.components?.palette?.height;\r\n this.gap = config.components?.palette?.gap || '1rem';\r\n this.palettes = config.components?.palette?.sections || [];\r\n this.currentPalette = this.palettes[0];\r\n this.refreshPalette();\r\n }\r\n\r\n refreshPalette(): void {\r\n this.switchPalette(this.currentPalette);\r\n }\r\n\r\n switchPalette(palette: PaletteSectionConfig): void {\r\n this.currentPalette = palette;\r\n\r\n if (this.panel !== undefined) {\r\n this.selectPalette().selectAll('*').remove();\r\n this.priorityThreshold = this.canvas.getPriorityThreshold();\r\n\r\n if (palette.categories) {\r\n this.appendCategories(palette.categories);\r\n }\r\n if (palette.templates) {\r\n for (const template of palette.templates) {\r\n this.appendTemplate(template);\r\n }\r\n }\r\n }\r\n }\r\n\r\n selectPanel(): d3.Selection<HTMLDivElement, unknown, null, undefined> {\r\n return d3.select(this.panel.nativeElement);\r\n }\r\n\r\n selectPalette(): d3.Selection<HTMLDivElement, unknown, null, undefined> {\r\n return this.selectPanel().select('.daga-palette-view');\r\n }\r\n\r\n private appendCategories(categories: {\r\n [key: string]: (NodeTemplateConfig | ConnectionTemplateConfig)[];\r\n }): void {\r\n const thisComponent = this.selectPalette()\r\n .append('select')\r\n .style('width', '100%')\r\n .style('height', '2rem')\r\n .style('padding', '0.5rem')\r\n .style('border-radius', '0.25rem')\r\n .style('background-color', '#f7f8fc')\r\n .style('border', `1px solid #e6e6e6`);\r\n thisComponent.append('option').attr('value', '').text('(None selected)');\r\n for (const categoryKey in categories) {\r\n thisComponent\r\n .append('option')\r\n .attr('value', categoryKey)\r\n .text(categoryKey);\r\n }\r\n thisComponent.on(Events.Change, () => {\r\n if (this.currentCategory) {\r\n this.selectPalette()\r\n .selectAll(`.daga-template-container.daga-in-category`)\r\n .remove();\r\n }\r\n\r\n const selectedKey = thisComponent.property('value');\r\n this.currentCategory = selectedKey;\r\n const templatesInCategory = categories[selectedKey] || [];\r\n for (const template of templatesInCategory) {\r\n this.appendTemplate(template, 'daga-in-category');\r\n }\r\n });\r\n if (this.currentCategory) {\r\n // if we had a category already selected, re-select it\r\n thisComponent.property('value', this.currentCategory);\r\n thisComponent.dispatch(Events.Change);\r\n }\r\n }\r\n\r\n private appendTemplate(\r\n template: NodeTemplateConfig | ConnectionTemplateConfig,\r\n classes?: string\r\n ): void {\r\n if (template.templateType === 'node') {\r\n const nodeType = this.canvas.model.nodes.types.get(template.type);\r\n if (nodeType) {\r\n this.appendNodeTemplate(nodeType, template, classes);\r\n } else {\r\n console.error(`Could not find a node type called '${template.type}'`);\r\n }\r\n } else if (template.templateType === 'connection') {\r\n {\r\n const connectionType = this.canvas.model.connections.types.get(\r\n template.type\r\n );\r\n if (connectionType) {\r\n this.appendConnectionTemplate(connectionType, template, classes);\r\n } else {\r\n console.error(\r\n `Could not find a connection type called '${template.type}'`\r\n );\r\n }\r\n }\r\n }\r\n }\r\n\r\n private appendNodeTemplate(\r\n type: DiagramNodeType,\r\n templateConfig: NodeTemplateConfig,\r\n classes?: string\r\n ): void {\r\n if (\r\n this.priorityThreshold !== undefined &&\r\n type.priority < this.priorityThreshold\r\n ) {\r\n return;\r\n }\r\n const borderThickness =\r\n type.defaultLook.lookType === 'shaped-look'\r\n ? type.defaultLook.borderThickness || 1\r\n : 0;\r\n const templateHeight = type.defaultHeight + borderThickness;\r\n const templateWidth = type.defaultWidth + borderThickness;\r\n const stretchedTemplateHeight = templateConfig.height || templateHeight;\r\n const stretchedTemplateWidth = templateConfig.width || templateWidth;\r\n\r\n let thisComponentClone!: d3.Selection<Element, unknown, null, undefined>;\r\n\r\n const thisComponent = this.selectPalette()\r\n .append('div')\r\n .attr(\r\n 'class',\r\n `daga-template-container ${classes !== undefined ? classes : ''}`\r\n )\r\n .style('width', `${stretchedTemplateWidth}px`)\r\n .style('height', `${stretchedTemplateHeight}px`)\r\n .on(Events.MouseEnter, () => {\r\n setCursorStyle(CursorStyle.Grab);\r\n })\r\n .on(Events.MouseLeave, () => {\r\n setCursorStyle();\r\n })\r\n .call(\r\n d3\r\n .drag<HTMLDivElement, unknown, HTMLElement>()\r\n .on(DragEvents.Drag, (event: DragEvent) => {\r\n if (this.canvas.canUserPerformAction(DiagramActions.AddNode)) {\r\n const pointerCoords =\r\n this.canvas.getPointerLocationRelativeToRoot(event);\r\n if (\r\n pointerCoords.length < 2 ||\r\n isNaN(pointerCoords[0]) ||\r\n isNaN(pointerCoords[1])\r\n ) {\r\n return;\r\n }\r\n thisComponentClone\r\n .style(\r\n 'left',\r\n `${pointerCoords[0] - stretchedTemplateWidth / 2}px`\r\n )\r\n .style(\r\n 'top',\r\n `${pointerCoords[1] - stretchedTemplateHeight / 2}px`\r\n );\r\n }\r\n })\r\n .on(DragEvents.Start, (event: DragEvent) => {\r\n if (this.canvas.canUserPerformAction(DiagramActions.AddNode)) {\r\n setCursorStyle(CursorStyle.Grabbing);\r\n const pointerCoords =\r\n this.canvas.getPointerLocationRelativeToRoot(event);\r\n if (\r\n pointerCoords.length < 2 ||\r\n isNaN(pointerCoords[0]) ||\r\n isNaN(pointerCoords[1])\r\n ) {\r\n return;\r\n }\r\n const thisComponentNodeCloned = thisComponent\r\n .node()\r\n ?.cloneNode(true);\r\n (this.canvas.selectRoot().node() as HTMLDivElement).appendChild(\r\n thisComponentNodeCloned as Node\r\n );\r\n thisComponentClone = d3.select(\r\n thisComponentNodeCloned as Element\r\n );\r\n thisComponentClone\r\n .style('position', 'absolute')\r\n .style(\r\n 'left',\r\n `${pointerCoords[0] - stretchedTemplateWidth / 2}px`\r\n )\r\n .style(\r\n 'top',\r\n `${pointerCoords[1] - stretchedTemplateHeight / 2}px`\r\n )\r\n .style('z-index', 1);\r\n // when trying to place a unique node in a diagram that already has a node of that type, set cursor style to not allowed\r\n if (\r\n type.isUnique &&\r\n this.canvas.model.nodes.find(\r\n (n) => !n.removed && n.type.id === type.id\r\n ) !== undefined\r\n ) {\r\n setCursorStyle(CursorStyle.NotAllowed);\r\n }\r\n }\r\n })\r\n .on(DragEvents.End, (event: DragEvent) => {\r\n if (this.canvas.canUserPerformAction(DiagramActions.AddNode)) {\r\n // take node back to its original position\r\n setCursorStyle(CursorStyle.Auto);\r\n thisComponentClone?.remove();\r\n // try to place node\r\n if (\r\n type.isUnique &&\r\n this.canvas.model.nodes.find(\r\n (n) => !n.removed && n.type.id === type.id\r\n ) !== undefined\r\n ) {\r\n // can't place, it's unique and that node is already in the model\r\n return;\r\n }\r\n const pointerCoordsRelativeToScreen =\r\n this.canvas.getPointerLocationRelativeToScreen(event);\r\n if (\r\n pointerCoordsRelativeToScreen.length < 2 ||\r\n isNaN(pointerCoordsRelativeToScreen[0]) ||\r\n isNaN(pointerCoordsRelativeToScreen[1])\r\n ) {\r\n // can't place, position is incorrect\r\n return;\r\n }\r\n\r\n const element = document.elementFromPoint(\r\n pointerCoordsRelativeToScreen[0],\r\n pointerCoordsRelativeToScreen[1]\r\n );\r\n if (\r\n element &&\r\n !this.canvas.selectCanvasView().node()?.contains(element)\r\n ) {\r\n // can't place, node hasn't been dropped on the canvas\r\n return;\r\n }\r\n const pointerCoords =\r\n this.canvas.getPointerLocationRelativeToCanvas(event);\r\n if (\r\n pointerCoords.length < 2 ||\r\n isNaN(pointerCoords[0]) ||\r\n isNaN(pointerCoords[1])\r\n ) {\r\n // can't place, position is incorrect\r\n return;\r\n }\r\n let newNodeCoords: Point = [\r\n pointerCoords[0] - type.defaultWidth / 2,\r\n pointerCoords[1] - type.defaultHeight / 2\r\n ];\r\n if (this.canvas.snapToGrid) {\r\n newNodeCoords = this.canvas.getClosestGridPoint([\r\n newNodeCoords[0] - type.snapToGridOffset[0],\r\n newNodeCoords[1] - type.snapToGridOffset[1]\r\n ]);\r\n newNodeCoords[0] += type.snapToGridOffset[0];\r\n newNodeCoords[1] += type.snapToGridOffset[1];\r\n }\r\n\r\n // check whether we dropped this node on a potential parent\r\n const nodesAtLocation = this.canvas.model.nodes.getAtCoordinates(\r\n pointerCoords[0],\r\n pointerCoords[1]\r\n );\r\n // filter by which nodes can have this type as a child\r\n const nodesAtLocationWhichCanHaveNodeAsAChild =\r\n nodesAtLocation.filter((n) =>\r\n n.type.childrenTypes.includes(type.id)\r\n );\r\n // filter by which nodes don't have descendants in this collection\r\n const filteredNodesAtLocation = filterByOnlyDescendants(\r\n nodesAtLocationWhichCanHaveNodeAsAChild\r\n );\r\n const droppedOn =\r\n filteredNodesAtLocation[filteredNodesAtLocation.length - 1];\r\n\r\n if (!type.canBeParentless && droppedOn === undefined) {\r\n // can't place, node must have a parent and no suitable parent has been found\r\n return;\r\n }\r\n\r\n const ancestor = droppedOn?.getLastAncestor();\r\n\r\n const addNodeAction = new AddNodeAction(\r\n this.canvas,\r\n type,\r\n newNodeCoords,\r\n droppedOn?.id,\r\n ancestor?.id,\r\n ancestor?.getGeometry(),\r\n undefined,\r\n templateConfig.label,\r\n templateConfig.values\r\n );\r\n\r\n addNodeAction.do();\r\n addNodeAction.toAncestorGeometry = ancestor?.getGeometry();\r\n\r\n this.canvas.actionStack.add(addNodeAction);\r\n // reset cursor\r\n setCursorStyle();\r\n }\r\n })\r\n )\r\n .append('svg')\r\n .attr('class', `palette-node ${type.id}`)\r\n .attr('viewBox', `0 0 ${templateWidth} ${templateHeight}`)\r\n .attr('preserveAspectRatio', 'none')\r\n .style('position', 'relative')\r\n .style('left', 0)\r\n .style('top', 0)\r\n .style('width', `${stretchedTemplateWidth}px`)\r\n .style('height', `${stretchedTemplateHeight}px`);\r\n const nodeLook = templateConfig.look || type.defaultLook;\r\n\r\n switch (nodeLook.lookType) {\r\n case 'shaped-look':\r\n thisComponent\r\n .append('path')\r\n .attr(\r\n 'd',\r\n generalClosedPath(\r\n nodeLook.shape || ClosedShape.Rectangle,\r\n (nodeLook.borderThickness || 1) / 2,\r\n (nodeLook.borderThickness || 1) / 2,\r\n type.defaultWidth,\r\n type.defaultHeight\r\n )\r\n )\r\n .attr('fill', nodeLook.fillColor || '#FFFFFF')\r\n .attr('stroke', nodeLook.borderColor || '#000000')\r\n .attr('stroke-width', `${nodeLook.borderThickness}px`);\r\n break;\r\n case 'image-look':\r\n thisComponent\r\n .append('image')\r\n .attr('x', 0)\r\n .attr('y', 0)\r\n .attr('width', type.defaultWidth)\r\n .attr('height', type.defaultHeight)\r\n .attr('href', nodeLook.backgroundImage)\r\n .attr('preserveAspectRatio', 'none');\r\n break;\r\n case 'stretchable-image-look':\r\n thisComponent\r\n .append('image')\r\n .attr('x', 0)\r\n .attr('y', 0)\r\n .attr('width', nodeLook.leftMargin)\r\n .attr('height', nodeLook.topMargin)\r\n .attr('href', nodeLook.backgroundImageTopLeft)\r\n .attr('preserveAspectRatio', 'none');\r\n thisComponent\r\n .append('image')\r\n .attr('x', nodeLook.leftMargin)\r\n .attr('y', 0)\r\n .attr(\r\n 'width',\r\n type.defaultWidth - nodeLook.rightMargin - nodeLook.leftMargin\r\n )\r\n .attr('height', nodeLook.topMargin)\r\n .attr('href', nodeLook.backgroundImageTop)\r\n .attr('preserveAspectRatio', 'none');\r\n thisComponent\r\n .append('image')\r\n .attr('x', type.defaultWidth - nodeLook.rightMargin)\r\n .attr('y', 0)\r\n .attr('width', nodeLook.rightMargin)\r\n .attr('height', nodeLook.topMargin)\r\n .attr('href', nodeLook.backgroundImageTopRight)\r\n .attr('preserveAspectRatio', 'none');\r\n thisComponent\r\n .append('image')\r\n .attr('x', 0)\r\n .attr('y', nodeLook.topMargin)\r\n .attr('width', nodeLook.leftMargin)\r\n .attr(\r\n 'height',\r\n type.defaultHeight - nodeLook.bottomMargin - nodeLook.topMargin\r\n )\r\n .attr('href', nodeLook.backgroundImageLeft)\r\n .attr('preserveAspectRatio', 'none');\r\n thisComponent\r\n .append('image')\r\n .attr('x', nodeLook.leftMargin)\r\n .attr('y', nodeLook.topMargin)\r\n .attr(\r\n 'width',\r\n type.defaultWidth - nodeLook.rightMargin - nodeLook.leftMargin\r\n )\r\n .attr(\r\n 'height',\r\n type.defaultHeight - nodeLook.bottomMargin - nodeLook.topMargin\r\n )\r\n .attr('href', nodeLook.backgroundImageCenter)\r\n .attr('preserveAspectRatio', 'none');\r\n thisComponent\r\n .append('image')\r\n .attr('x', type.defaultWidth - nodeLook.rightMargin)\r\n .attr('y', nodeLook.topMargin)\r\n .attr('width', nodeLook.rightMargin)\r\n .attr(\r\n 'height',\r\n type.defaultHeight - nodeLook.bottomMargin - nodeLook.topMargin\r\n )\r\n .attr('href', nodeLook.backgroundImageRight)\r\n .attr('preserveAspectRatio', 'none');\r\n thisComponent\r\n .append('image')\r\n .attr('x', 0)\r\n .attr('y', type.defaultHeight - nodeLook.bottomMargin)\r\n .attr('width', nodeLook.leftMargin)\r\n .attr('height', nodeLook.bottomMargin)\r\n .attr('href', nodeLook.backgroundImageBottomLeft)\r\n .attr('preserveAspectRatio', 'none');\r\n thisComponent\r\n .append('image')\r\n .attr('x', nodeLook.leftMargin)\r\n .attr('y', type.defaultHeight - nodeLook.bottomMargin)\r\n .attr(\r\n 'width',\r\n type.defaultWidth - nodeLook.rightMargin - nodeLook.leftMargin\r\n )\r\n .attr('height', nodeLook.bottomMargin)\r\n .attr('href', nodeLook.backgroundImageBottom)\r\n .attr('preserveAspectRatio', 'none');\r\n thisComponent\r\n .append('image')\r\n .attr('x', type.defaultWidth - nodeLook.rightMargin)\r\n .attr('y', type.defaultHeight - nodeLook.bottomMargin)\r\n .attr('width', nodeLook.rightMargin)\r\n .attr('height', nodeLook.bottomMargin)\r\n .attr('href', nodeLook.backgroundImageBottomRight)\r\n .attr('preserveAspectRatio', 'none');\r\n }\r\n\r\n if (templateConfig.look === undefined) {\r\n if (type.decorators) {\r\n for (const decoratorConfig of type.decorators) {\r\n thisComponent\r\n .append('foreignObject')\r\n .attr('width', `${decoratorConfig.width}px`)\r\n .attr('height', `${decoratorConfig.height}px`)\r\n .attr(\r\n 'transform',\r\n `translate(${decoratorConfig.coords[0]},${decoratorConfig.coords[1]})`\r\n )\r\n .html(decoratorConfig.html);\r\n }\r\n }\r\n\r\n if (templateConfig.label) {\r\n const labelConfig = {\r\n ...DIAGRAM_FIELD_DEFAULTS,\r\n ...type.label,\r\n ...templateConfig.labelLook\r\n };\r\n thisComponent\r\n .append('text')\r\n .attr(\r\n 'transform',\r\n `translate(${(getLeftMargin(labelConfig) + type.defaultWidth + borderThickness / 2) / 2},${(getTopMargin(labelConfig) + type.defaultHeight + borderThickness / 2) / 2})`\r\n )\r\n .attr('x', 0)\r\n .attr('y', 0)\r\n .attr('font-size', `${labelConfig.fontSize}px`)\r\n .attr('text-anchor', 'middle')\r\n .attr('font-family', labelConfig.fontFamily)\r\n .attr('font-weight', 400)\r\n .attr('fill', labelConfig.color)\r\n .attr('stroke', 'none')\r\n .style('font-kerning', 'none')\r\n .style('user-select', 'none')\r\n .text(templateConfig.label);\r\n }\r\n }\r\n }\r\n\r\n private appendConnectionTemplate(\r\n type: DiagramConnectionType,\r\n templateConfig: ConnectionTemplateConfig,\r\n classes?: string\r\n ): void {\r\n const thisComponent = this.selectPalette()\r\n .append('div')\r\n .attr(\r\n 'class',\r\n `daga-template-container ${classes !== undefined ? classes : ''}`\r\n )\r\n .style('width', `${templateConfig.width}px`)\r\n .style('height', `${templateConfig.height}px`)\r\n .style('cursor', 'pointer')\r\n .append('svg')\r\n .attr('class', `palette-button ${type.id}`)\r\n .style('position', 'relative')\r\n .style('left', 0)\r\n .style('top', 0)\r\n .style('width', '100%')\r\n .style('height', '100%')\r\n .on('click', () => {\r\n this.canvas.connectionType = type;\r\n });\r\n\r\n if (this.canvas.connectionType !== type) {\r\n // the connection type of this template is not the currently selected one\r\n if (templateConfig.icon !== '') {\r\n thisComponent\r\n .append('image')\r\n .attr('x', 0)\r\n .attr('y', 0)\r\n .attr('width', templateConfig.width)\r\n .attr('height', templateConfig.height)\r\n .attr('href', templateConfig.icon);\r\n }\r\n } else {\r\n // the connection type of this template is the currently selected one\r\n if (templateConfig.selectedIcon !== '') {\r\n thisComponent\r\n .append('image')\r\n .attr('x', 0)\r\n .attr('y', 0)\r\n .attr('width', templateConfig.width)\r\n .attr('height', templateConfig.height)\r\n .attr('href', templateConfig.selectedIcon);\r\n } else if (templateConfig.icon !== '') {\r\n // if there's no selectedIcon, use the regular icon instead\r\n thisComponent\r\n .append('image')\r\n .attr('x', 0)\r\n .attr('y', 0)\r\n .attr('width', templateConfig.width)\r\n .attr('height', templateConfig.height)\r\n .attr('href', templateConfig.icon);\r\n }\r\n }\r\n\r\n if (templateConfig.label !== '') {\r\n thisComponent\r\n .append('text')\r\n .attr(\r\n 'transform',\r\n `translate(${templateConfig.width / 2},${\r\n templateConfig.height / 2 + LABEL_VERTICAL_SHIFT_PX\r\n })`\r\n )\r\n .attr('x', 0)\r\n .attr('y', 0)\r\n .attr('font-size', '20px')\r\n .attr('text-anchor', 'middle')\r\n .attr('font-family', \"'Wonder Unit Sans', sans-serif\")\r\n .attr('font-weight', 400)\r\n .attr('fill', '#000000')\r\n .attr('stroke', 'none')\r\n .style('font-kerning', 'none')\r\n .style('user-select', 'none')\r\n .text(templateConfig.label);\r\n }\r\n }\r\n}\r\n","<div #panel class=\"daga-panel daga-{{ location }} daga-