UNPKG

@obliczeniowo/elementary

Version:
466 lines (459 loc) 25.7 kB
import * as i0 from '@angular/core'; import { input, EventEmitter, effect, HostListener, Output, Component, ViewChildren, HostBinding, Input, NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; import { NG_VALUE_ACCESSOR, NG_VALIDATORS } from '@angular/forms'; import { GroupClass } from '@obliczeniowo/elementary/circle-categories-tree'; import { Point2D } from '@obliczeniowo/elementary/classes'; import * as i1 from '@obliczeniowo/elementary/connectors'; import { DirectionalConnectorComponent, ConnectorsModule } from '@obliczeniowo/elementary/connectors'; import { ElementaryMath } from '@obliczeniowo/elementary/math'; import * as i2 from '@obliczeniowo/elementary/menu'; import { MenuModule } from '@obliczeniowo/elementary/menu'; class CategoryItemComponent { // ray = input.required(); // angle = input.required(); // textLength = input(100); // item = input.required(); // connectors = input([]); // menu = input(); // clicked = new EventEmitter(); // over = new EventEmitter(); // leave = new EventEmitter(); /** */ remove = new EventEmitter(); reverse; filtered; menuItems = [ { id: 1, text: 'Delete', command: 'delete', } ]; subscription; mouseOver() { this.filtered?.forEach(connector => { connector.highlight = true; connector.changes.detectChanges(); }); this.over.emit({ item: this.item(), pos: this.position(), angle: this.angle() }); const menu = this.menu(); if (menu && this.filtered?.length) { menu.items = this.menuItems; } } mouseLeave() { this.filtered?.forEach(connector => { connector.highlight = false; connector.changes.detectChanges(); }); this.leave.emit(); const menu = this.menu(); if (menu && !menu?.opened) { menu.items = []; } } constructor() { effect(() => { this.ray(); this.angle(); if (!this.connectors) { return; } this.filtered = this.connectors().filter(connector => connector.startName === this.item().name || connector.endName === this.item().name); if (this.filtered.length) { const position = this.position(); this.filtered.forEach(connector => { if (connector.endName === this.item().name) { connector.end = position; connector.endAngle = ElementaryMath.degreesToRadians(this.angle()); } else { connector.start = position; connector.startAngle = ElementaryMath.degreesToRadians(this.angle()) + Math.PI; } if (connector.endAngle !== undefined && connector.startAngle !== undefined) { let dAngle = ElementaryMath.fmod(Math.abs(connector.startAngle - connector.endAngle), Math.PI * 2); dAngle = (dAngle < Math.PI ? dAngle : 2 * Math.PI - dAngle); connector.k = (1 - dAngle / (Math.PI)) * 6.5 + 1.5; connector.changes.detectChanges(); } }); } }); effect(() => { this.reverse = this.angle() > 90 && this.angle() < 270; }); effect(() => { if (this.subscription && !this.subscription.closed) { this.subscription.unsubscribe(); } const menu = this.menu(); if (menu) { this.subscription = menu.command.subscribe(command => { if (menu?.items !== this.menuItems) { return; } switch (command) { case 'delete': this.remove.emit(this.item()); break; } menu.items = []; }); } }); } ngOnDestroy() { if (this.subscription && !this.subscription.closed) { this.subscription.unsubscribe(); } } position() { return { x: this.ray() + Math.cos(ElementaryMath.degreesToRadians(this.angle())) * (this.r - 10), y: this.ray() + Math.sin(ElementaryMath.degreesToRadians(this.angle())) * (this.r - 10) }; } get r() { return this.ray() - this.textLength(); } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.4", ngImport: i0, type: CategoryItemComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "20.0.4", type: CategoryItemComponent, isStandalone: false, selector: "g[obl-category-item]", inputs: { ray: { classPropertyName: "ray", publicName: "ray", isSignal: true, isRequired: true, transformFunction: null }, angle: { classPropertyName: "angle", publicName: "angle", isSignal: true, isRequired: true, transformFunction: null }, textLength: { classPropertyName: "textLength", publicName: "textLength", isSignal: true, isRequired: false, transformFunction: null }, item: { classPropertyName: "item", publicName: "item", isSignal: true, isRequired: true, transformFunction: null }, connectors: { classPropertyName: "connectors", publicName: "connectors", isSignal: true, isRequired: false, transformFunction: null }, menu: { classPropertyName: "menu", publicName: "menu", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { clicked: "clicked", over: "over", leave: "leave", remove: "remove" }, host: { listeners: { "mouseover": "mouseOver()", "mouseleave": "mouseLeave()" } }, ngImport: i0, template: "<svg:g\n [attr.transform]=\"\n 'translate(' +\n ray() +\n ',' +\n ray() +\n ') rotate(' +\n angle() +\n ') translate(' +\n r +\n ', ' +\n (reverse ? -2 : 5) +\n ')' +\n (reverse ? ' rotate(180)' : '')\n \"\n>\n <svg:circle\n [attr.cx]=\"reverse ? 10 : -10\"\n [attr.cy]=\"reverse ? -2 : -5\"\n r=\"2\"\n ></svg:circle>\n <svg:text [attr.text-anchor]=\"reverse ? 'end' : 'start'\">\n {{ item().name }}\n </svg:text>\n</svg:g>\n", styles: ["text{font-size:10px;fill:var(--default-text-color)}circle{fill:var(--default-text-color)}g:hover{cursor:pointer}g:hover text{opacity:.7}\n"] }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.4", ngImport: i0, type: CategoryItemComponent, decorators: [{ type: Component, args: [{ selector: 'g[obl-category-item]', standalone: false, template: "<svg:g\n [attr.transform]=\"\n 'translate(' +\n ray() +\n ',' +\n ray() +\n ') rotate(' +\n angle() +\n ') translate(' +\n r +\n ', ' +\n (reverse ? -2 : 5) +\n ')' +\n (reverse ? ' rotate(180)' : '')\n \"\n>\n <svg:circle\n [attr.cx]=\"reverse ? 10 : -10\"\n [attr.cy]=\"reverse ? -2 : -5\"\n r=\"2\"\n ></svg:circle>\n <svg:text [attr.text-anchor]=\"reverse ? 'end' : 'start'\">\n {{ item().name }}\n </svg:text>\n</svg:g>\n", styles: ["text{font-size:10px;fill:var(--default-text-color)}circle{fill:var(--default-text-color)}g:hover{cursor:pointer}g:hover text{opacity:.7}\n"] }] }], ctorParameters: () => [], propDecorators: { clicked: [{ type: Output }], over: [{ type: Output }], leave: [{ type: Output }], remove: [{ type: Output }], mouseOver: [{ type: HostListener, args: ['mouseover'] }], mouseLeave: [{ type: HostListener, args: ['mouseleave'] }] } }); class CategoryRelationGroupComponent { // Group class as required group = input.required(); // Ray of circle ray = input(400); // max text length textLength = input(100); // connectors = input([]); /** */ menu = input(); /** */ remove = new EventEmitter(); // emit when user click on item leaf or leaf (category) name clicked = new EventEmitter(); // over = new EventEmitter(); // leave = new EventEmitter(); get r() { return this.ray() - this.textLength(); } endAngle(index) { return ElementaryMath.degreesToRadians(this.group().dAngle * (index + this.group().offset)); } startAngle() { return ElementaryMath.degreesToRadians(this.group().dAngle * (this.group().items.length / 2 + this.group().offset)); } end(index) { const angle = this.endAngle(index); return { x: this.ray() + (this.r - 10) * Math.cos(angle), y: this.ray() + (this.r - 10) * Math.sin(angle) }; } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.4", ngImport: i0, type: CategoryRelationGroupComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.0.4", type: CategoryRelationGroupComponent, isStandalone: false, selector: "g[obl-category-relation-group]", inputs: { group: { classPropertyName: "group", publicName: "group", isSignal: true, isRequired: true, transformFunction: null }, ray: { classPropertyName: "ray", publicName: "ray", isSignal: true, isRequired: false, transformFunction: null }, textLength: { classPropertyName: "textLength", publicName: "textLength", isSignal: true, isRequired: false, transformFunction: null }, connectors: { classPropertyName: "connectors", publicName: "connectors", isSignal: true, isRequired: false, transformFunction: null }, menu: { classPropertyName: "menu", publicName: "menu", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { remove: "remove", clicked: "clicked", over: "over", leave: "leave" }, ngImport: i0, template: "@for (item of group().items; track item) {\n <svg:g\n (click)=\"clicked.emit(item)\"\n >\n <svg:g\n obl-category-item\n [angle]=\"group().dAngle * ($index + group().offset) + group().dCategoryOffset\"\n [item]=\"item\"\n [ray]=\"ray()\"\n [connectors]=\"connectors()\"\n [menu]=\"menu()\"\n (clicked)=\"clicked.emit($event)\"\n (over)=\"over.emit($event)\"\n (leave)=\"leave.emit($event)\"\n (remove)=\"remove.emit($event)\"\n ></svg:g>\n </svg:g>\n}\n", styles: ["text{font-size:10px;fill:var(--default-text-color)}g:hover{cursor:pointer}g:hover text{opacity:.7}\n"], dependencies: [{ kind: "component", type: CategoryItemComponent, selector: "g[obl-category-item]", inputs: ["ray", "angle", "textLength", "item", "connectors", "menu"], outputs: ["clicked", "over", "leave", "remove"] }] }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.4", ngImport: i0, type: CategoryRelationGroupComponent, decorators: [{ type: Component, args: [{ selector: 'g[obl-category-relation-group]', standalone: false, template: "@for (item of group().items; track item) {\n <svg:g\n (click)=\"clicked.emit(item)\"\n >\n <svg:g\n obl-category-item\n [angle]=\"group().dAngle * ($index + group().offset) + group().dCategoryOffset\"\n [item]=\"item\"\n [ray]=\"ray()\"\n [connectors]=\"connectors()\"\n [menu]=\"menu()\"\n (clicked)=\"clicked.emit($event)\"\n (over)=\"over.emit($event)\"\n (leave)=\"leave.emit($event)\"\n (remove)=\"remove.emit($event)\"\n ></svg:g>\n </svg:g>\n}\n", styles: ["text{font-size:10px;fill:var(--default-text-color)}g:hover{cursor:pointer}g:hover text{opacity:.7}\n"] }] }], propDecorators: { remove: [{ type: Output }], clicked: [{ type: Output }], over: [{ type: Output }], leave: [{ type: Output }] } }); class CircleCategoriesTreeRelationsComponent { element; connectors = []; // Grouping model table groups = []; // disabled; // menu = input(); // emit when user click on specific item itemClicked = new EventEmitter(); // connectorClicked = new EventEmitter(); // connectorsChange = new EventEmitter(); total = 0; // converted/prepared groups to display groupsClasses = []; // newConnector; set relations(relation) { setTimeout(() => this.relationsChanged = relation.toArray()); } ; relationsChanged = []; mouse; angle; menuItems = [ { id: 1, text: 'Delete', command: 'delete', } ]; constructor(element) { this.element = element; } ngOnChanges(changes) { if (changes.groups && this.groups) { this.prepareGroups(); } } move(event) { if (this.newConnector) { const rect = this.element.nativeElement.getBoundingClientRect(); const k = 800 / rect.width; const mouse = new Point2D((event.x - rect.left) * k, (event.y - rect.top) * k); if (mouse.subtract(new Point2D(400, 400)).sickLength() > 300 * 300) { return; } this.mouse = { x: (event.x - rect.left) * k, y: (event.y - rect.top) * k }; if (this.mouse.x !== 400 && this.mouse.y !== 400) { this.angle = Math.atan2(this.mouse.y - 400, this.mouse.x - 400); } } } mouseOver() { const menu = this.menu(); if (menu) { menu.items = this.menuItems; menu.target = this.element.nativeElement; } } clicked(event) { if (this.newConnector && this.mouse) { this.newConnector = undefined; this.angle = undefined; this.mouse = undefined; } } remove(item) { this.connectors = this.connectors.filter(connector => connector.left !== item.name && connector.right !== item.name); this.connectorsChange.emit(this.connectors); } onItemClicked(item) { if (this.newConnector && !this.disabled) { if (item.name === this.newConnector.left || this.connectors.find(connector => connector.left === this.newConnector?.left && connector.right === item.name || connector.right === this.newConnector?.left && connector.left === item.name)) { return; } this.newConnector.right = item.name; this.connectors.push(this.newConnector); this.connectorsChange.emit(this.connectors); this.newConnector = undefined; this.update(); } else { this.mouse = undefined; this.angle = undefined; this.newConnector = { left: item.name, right: undefined }; } this.itemClicked.emit({ item, connectors: this.connectors.filter(connector => connector.left === item.name || connector.right === item.name) }); } update() { if (this.onChange) { this.onChange({ groups: this.groups, connectors: this.connectors }); } else { this.writeValue({ groups: this.groups, connectors: this.connectors }); } } onConnectorClicked(connector) { if (!this.disabled) { this.connectors = this.connectors.filter(con => con.left !== connector.left || con.right !== connector.right); this.update(); } this.connectorClicked.emit({ left: this.groupsClasses .map(group => group.items) .reduce((p, c) => (p = [...p, ...c], p), []) .find(item => item.name === connector.left), right: this.groupsClasses .map(group => group.items) .reduce((p, c) => (p = [...p, ...c], p), []) .find(item => item.name === connector.right) }); } removeConnector(connector) { this.connectors = this.connectors.filter(con => con !== connector); this.connectorsChange.emit(this.connectors); } over(data) { if (this.newConnector) { this.newConnector = { ...this.newConnector, right: data.item.name }; this.mouse = data.pos; this.angle = ElementaryMath.degreesToRadians(data.angle); } } prepareGroups() { this.total = this.groups.map(group => group.items.length).reduce((p, c) => (p += c, p), 0); const dCategoryAngle = 1.5; const dAngle = (360 - dCategoryAngle * this.groups.length) / (this.total || 1); let offset = 0; this.groupsClasses = this.groups.map((group, index) => { const current = offset; offset += group.items.length; return new GroupClass(group, dAngle, current, dCategoryAngle * index); }); } onChange; onTouched = () => { }; writeValue(value) { Object.assign(this, value); if (this.groups) { this.prepareGroups(); } } registerOnChange(onChange) { this.onChange = onChange; } registerOnTouched(onTouched) { this.onTouched = onTouched; } setDisabledState(isDisabled) { this.disabled = isDisabled; } validate(control) { return null; } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.4", ngImport: i0, type: CircleCategoriesTreeRelationsComponent, deps: [{ token: i0.ElementRef }], target: i0.ɵɵFactoryTarget.Component }); static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.0.4", type: CircleCategoriesTreeRelationsComponent, isStandalone: false, selector: "obl-circle-categories-tree-relations", inputs: { connectors: { classPropertyName: "connectors", publicName: "connectors", isSignal: false, isRequired: false, transformFunction: null }, groups: { classPropertyName: "groups", publicName: "groups", isSignal: false, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: false, isRequired: false, transformFunction: null }, menu: { classPropertyName: "menu", publicName: "menu", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { itemClicked: "itemClicked", connectorClicked: "connectorClicked", connectorsChange: "connectorsChange" }, host: { listeners: { "mousemove": "move($event)", "mouseover": "mouseOver()", "click": "clicked($event)" }, properties: { "class.disabled": "this.disabled" } }, providers: [ { provide: NG_VALUE_ACCESSOR, multi: true, useExisting: CircleCategoriesTreeRelationsComponent }, { provide: NG_VALIDATORS, multi: true, useExisting: CircleCategoriesTreeRelationsComponent } ], viewQueries: [{ propertyName: "relations", predicate: DirectionalConnectorComponent, descendants: true }], usesOnChanges: true, ngImport: i0, template: "<svg viewBox=\"0 0 800 800\">\n @for (connector of connectors; track $index) {\n <g\n obl-directional-connector\n [startName]=\"connector.left\"\n [endName]=\"connector.right\"\n [menu]=\"menuRef\"\n (click)=\"onConnectorClicked(connector)\"\n (remove)=\"removeConnector(connector)\"\n ></g>\n }\n\n @for (group of groupsClasses; track $index) {\n <g\n obl-category-relation-group\n [group]=\"group\"\n [connectors]=\"relationsChanged\"\n [menu]=\"menuRef\"\n (clicked)=\"onItemClicked($event)\"\n (over)=\"over($event)\"\n (remove)=\"remove($event)\"\n ></g>\n }\n\n @if (newConnector) {\n <g\n obl-directional-connector\n class=\"created\"\n [startName]=\"newConnector.left\"\n [endName]=\"newConnector.right\"\n [end]=\"mouse\"\n [endAngle]=\"angle\"\n [color]=\"'#5fbcd3'\"\n ></g>\n }\n</svg>\n\n<obl-menu #menuRef [target]=\"element.nativeElement\"></obl-menu>", styles: [":host{display:flex}\n"], dependencies: [{ kind: "component", type: i1.DirectionalConnectorComponent, selector: "g[obl-directional-connector]", inputs: ["start", "end", "startAngle", "endAngle", "offset", "k", "color", "startName", "endName", "menu", "highlight"], outputs: ["remove"] }, { kind: "component", type: i2.MenuComponent, selector: "obl-menu", inputs: ["opened", "target", "items", "translations"], outputs: ["clicked"] }, { kind: "component", type: CategoryRelationGroupComponent, selector: "g[obl-category-relation-group]", inputs: ["group", "ray", "textLength", "connectors", "menu"], outputs: ["remove", "clicked", "over", "leave"] }] }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.4", ngImport: i0, type: CircleCategoriesTreeRelationsComponent, decorators: [{ type: Component, args: [{ selector: 'obl-circle-categories-tree-relations', providers: [ { provide: NG_VALUE_ACCESSOR, multi: true, useExisting: CircleCategoriesTreeRelationsComponent }, { provide: NG_VALIDATORS, multi: true, useExisting: CircleCategoriesTreeRelationsComponent } ], standalone: false, template: "<svg viewBox=\"0 0 800 800\">\n @for (connector of connectors; track $index) {\n <g\n obl-directional-connector\n [startName]=\"connector.left\"\n [endName]=\"connector.right\"\n [menu]=\"menuRef\"\n (click)=\"onConnectorClicked(connector)\"\n (remove)=\"removeConnector(connector)\"\n ></g>\n }\n\n @for (group of groupsClasses; track $index) {\n <g\n obl-category-relation-group\n [group]=\"group\"\n [connectors]=\"relationsChanged\"\n [menu]=\"menuRef\"\n (clicked)=\"onItemClicked($event)\"\n (over)=\"over($event)\"\n (remove)=\"remove($event)\"\n ></g>\n }\n\n @if (newConnector) {\n <g\n obl-directional-connector\n class=\"created\"\n [startName]=\"newConnector.left\"\n [endName]=\"newConnector.right\"\n [end]=\"mouse\"\n [endAngle]=\"angle\"\n [color]=\"'#5fbcd3'\"\n ></g>\n }\n</svg>\n\n<obl-menu #menuRef [target]=\"element.nativeElement\"></obl-menu>", styles: [":host{display:flex}\n"] }] }], ctorParameters: () => [{ type: i0.ElementRef }], propDecorators: { connectors: [{ type: Input }], groups: [{ type: Input }], disabled: [{ type: HostBinding, args: ['class.disabled'] }, { type: Input }], itemClicked: [{ type: Output }], connectorClicked: [{ type: Output }], connectorsChange: [{ type: Output }], relations: [{ type: ViewChildren, args: [DirectionalConnectorComponent] }], move: [{ type: HostListener, args: ['mousemove', ['$event']] }], mouseOver: [{ type: HostListener, args: ['mouseover'] }], clicked: [{ type: HostListener, args: ['click', ['$event']] }] } }); class CircleTreeCategoriesRelationsModule { static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.4", ngImport: i0, type: CircleTreeCategoriesRelationsModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "20.0.4", ngImport: i0, type: CircleTreeCategoriesRelationsModule, declarations: [CircleCategoriesTreeRelationsComponent, CategoryRelationGroupComponent, CategoryItemComponent], imports: [CommonModule, ConnectorsModule, MenuModule], exports: [CircleCategoriesTreeRelationsComponent] }); static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "20.0.4", ngImport: i0, type: CircleTreeCategoriesRelationsModule, imports: [CommonModule, ConnectorsModule, MenuModule] }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.4", ngImport: i0, type: CircleTreeCategoriesRelationsModule, decorators: [{ type: NgModule, args: [{ declarations: [ CircleCategoriesTreeRelationsComponent, CategoryRelationGroupComponent, CategoryItemComponent ], imports: [ CommonModule, ConnectorsModule, MenuModule ], exports: [ CircleCategoriesTreeRelationsComponent ] }] }] }); /** * Generated bundle index. Do not edit. */ export { CircleCategoriesTreeRelationsComponent, CircleTreeCategoriesRelationsModule }; //# sourceMappingURL=obliczeniowo-elementary-circle-tree-categories-relations.mjs.map