@myrmidon/paged-data-browsers 5.0.3 → 5.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -12,9 +12,13 @@ import * as i5 from '@angular/material/tooltip';
|
|
|
12
12
|
import { MatTooltipModule } from '@angular/material/tooltip';
|
|
13
13
|
import { ColorToContrastPipe, StringToColorPipe } from '@myrmidon/ngx-tools';
|
|
14
14
|
import { BehaviorSubject, of, tap, forkJoin } from 'rxjs';
|
|
15
|
+
import { map } from 'rxjs/operators';
|
|
15
16
|
|
|
16
17
|
class CompactPagerComponent {
|
|
17
18
|
constructor() {
|
|
19
|
+
/**
|
|
20
|
+
* The current paging information.
|
|
21
|
+
*/
|
|
18
22
|
this.paging = input({ pageNumber: 0, pageCount: 0, total: 0 }, ...(ngDevMode ? [{ debugName: "paging" }] : []));
|
|
19
23
|
/**
|
|
20
24
|
* Emits the new paging information when the user changes the page.
|
|
@@ -48,13 +52,13 @@ class CompactPagerComponent {
|
|
|
48
52
|
});
|
|
49
53
|
}
|
|
50
54
|
}
|
|
51
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "
|
|
52
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "
|
|
55
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.0", ngImport: i0, type: CompactPagerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
56
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.0", type: CompactPagerComponent, isStandalone: true, selector: "pdb-compact-pager", inputs: { paging: { classPropertyName: "paging", publicName: "paging", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { pagingChange: "pagingChange" }, ngImport: i0, template: "@if (paging().pageCount) {\n <div class=\"form-row\">\n <span id=\"pages\">{{ paging().pageNumber }}/{{ paging().pageCount }}</span>\n <button\n type=\"button\"\n mat-icon-button\n (click)=\"onFirst()\"\n [disabled]=\"paging().pageNumber < 2\"\n >\n <mat-icon>first_page</mat-icon>\n </button>\n <button\n type=\"button\"\n mat-icon-button\n (click)=\"onPrevious()\"\n [disabled]=\"paging().pageNumber < 2\"\n >\n <mat-icon>navigate_before</mat-icon>\n </button>\n <button\n type=\"button\"\n mat-icon-button\n (click)=\"onNext()\"\n [disabled]=\"paging().pageNumber === paging().pageCount\"\n >\n <mat-icon>navigate_next</mat-icon>\n </button>\n <button\n type=\"button\"\n mat-icon-button\n (click)=\"onLast()\"\n [disabled]=\"paging().pageNumber === paging().pageCount\"\n >\n <mat-icon>last_page</mat-icon>\n </button>\n <span id=\"total\">{{ paging().total }} </span>\n </div>\n}\n", styles: ["#pages,#total{color:silver}.form-row{display:flex;gap:2px;align-items:center;flex-wrap:wrap}.form-row *{flex:0 0 auto}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i1.MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i2.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }] }); }
|
|
53
57
|
}
|
|
54
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
58
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.0", ngImport: i0, type: CompactPagerComponent, decorators: [{
|
|
55
59
|
type: Component,
|
|
56
60
|
args: [{ selector: 'pdb-compact-pager', imports: [CommonModule, MatButtonModule, MatIconModule], template: "@if (paging().pageCount) {\n <div class=\"form-row\">\n <span id=\"pages\">{{ paging().pageNumber }}/{{ paging().pageCount }}</span>\n <button\n type=\"button\"\n mat-icon-button\n (click)=\"onFirst()\"\n [disabled]=\"paging().pageNumber < 2\"\n >\n <mat-icon>first_page</mat-icon>\n </button>\n <button\n type=\"button\"\n mat-icon-button\n (click)=\"onPrevious()\"\n [disabled]=\"paging().pageNumber < 2\"\n >\n <mat-icon>navigate_before</mat-icon>\n </button>\n <button\n type=\"button\"\n mat-icon-button\n (click)=\"onNext()\"\n [disabled]=\"paging().pageNumber === paging().pageCount\"\n >\n <mat-icon>navigate_next</mat-icon>\n </button>\n <button\n type=\"button\"\n mat-icon-button\n (click)=\"onLast()\"\n [disabled]=\"paging().pageNumber === paging().pageCount\"\n >\n <mat-icon>last_page</mat-icon>\n </button>\n <span id=\"total\">{{ paging().total }} </span>\n </div>\n}\n", styles: ["#pages,#total{color:silver}.form-row{display:flex;gap:2px;align-items:center;flex-wrap:wrap}.form-row *{flex:0 0 auto}\n"] }]
|
|
57
|
-
}] });
|
|
61
|
+
}], propDecorators: { paging: [{ type: i0.Input, args: [{ isSignal: true, alias: "paging", required: false }] }], pagingChange: [{ type: i0.Output, args: ["pagingChange"] }] } });
|
|
58
62
|
|
|
59
63
|
class RangeViewComponent {
|
|
60
64
|
constructor() {
|
|
@@ -87,13 +91,13 @@ class RangeViewComponent {
|
|
|
87
91
|
];
|
|
88
92
|
}, ...(ngDevMode ? [{ debugName: "scaledRange" }] : []));
|
|
89
93
|
}
|
|
90
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "
|
|
91
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "
|
|
94
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.0", ngImport: i0, type: RangeViewComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
95
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.0.0", type: RangeViewComponent, isStandalone: true, selector: "pdb-range-view", inputs: { domain: { classPropertyName: "domain", publicName: "domain", isSignal: true, isRequired: false, transformFunction: null }, range: { classPropertyName: "range", publicName: "range", isSignal: true, isRequired: false, transformFunction: null }, width: { classPropertyName: "width", publicName: "width", isSignal: true, isRequired: false, transformFunction: null }, height: { classPropertyName: "height", publicName: "height", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<svg [attr.width]=\"width()\" [attr.height]=\"height()\">\n <rect id=\"rdomain\" [attr.width]=\"width()\" [attr.height]=\"height()\" />\n <rect\n id=\"rrange\"\n [attr.x]=\"scaledRange()[0]\"\n [attr.y]=\"0\"\n [attr.width]=\"scaledRange()[1] - scaledRange()[0]\"\n [attr.height]=\"height()\"\n />\n</svg>\n", styles: ["#rdomain{fill:#d3d3d3;stroke-width:3;stroke:#c1ba9b}#rrange{fill:#91aad3;stroke-width:3}\n"] }); }
|
|
92
96
|
}
|
|
93
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
97
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.0", ngImport: i0, type: RangeViewComponent, decorators: [{
|
|
94
98
|
type: Component,
|
|
95
99
|
args: [{ selector: 'pdb-range-view', template: "<svg [attr.width]=\"width()\" [attr.height]=\"height()\">\n <rect id=\"rdomain\" [attr.width]=\"width()\" [attr.height]=\"height()\" />\n <rect\n id=\"rrange\"\n [attr.x]=\"scaledRange()[0]\"\n [attr.y]=\"0\"\n [attr.width]=\"scaledRange()[1] - scaledRange()[0]\"\n [attr.height]=\"height()\"\n />\n</svg>\n", styles: ["#rdomain{fill:#d3d3d3;stroke-width:3;stroke:#c1ba9b}#rrange{fill:#91aad3;stroke-width:3}\n"] }]
|
|
96
|
-
}], ctorParameters: () => [] });
|
|
100
|
+
}], ctorParameters: () => [], propDecorators: { domain: [{ type: i0.Input, args: [{ isSignal: true, alias: "domain", required: false }] }], range: [{ type: i0.Input, args: [{ isSignal: true, alias: "range", required: false }] }], width: [{ type: i0.Input, args: [{ isSignal: true, alias: "width", required: false }] }], height: [{ type: i0.Input, args: [{ isSignal: true, alias: "height", required: false }] }] } });
|
|
97
101
|
|
|
98
102
|
/**
|
|
99
103
|
* Browser tree node component view. This wraps some HTML content providing
|
|
@@ -178,14 +182,14 @@ class BrowserTreeNodeComponent {
|
|
|
178
182
|
this.editNodeFilterRequest.emit(this.node());
|
|
179
183
|
}
|
|
180
184
|
}
|
|
181
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "
|
|
182
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "
|
|
185
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.0", ngImport: i0, type: BrowserTreeNodeComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
186
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.0", type: BrowserTreeNodeComponent, isStandalone: true, selector: "pdb-browser-tree-node", inputs: { node: { classPropertyName: "node", publicName: "node", isSignal: true, isRequired: false, transformFunction: null }, paging: { classPropertyName: "paging", publicName: "paging", isSignal: true, isRequired: false, transformFunction: null }, debug: { classPropertyName: "debug", publicName: "debug", isSignal: true, isRequired: false, transformFunction: null }, hideLabel: { classPropertyName: "hideLabel", publicName: "hideLabel", isSignal: true, isRequired: false, transformFunction: null }, hideLoc: { classPropertyName: "hideLoc", publicName: "hideLoc", isSignal: true, isRequired: false, transformFunction: null }, hidePaging: { classPropertyName: "hidePaging", publicName: "hidePaging", isSignal: true, isRequired: false, transformFunction: null }, hideFilter: { classPropertyName: "hideFilter", publicName: "hideFilter", isSignal: true, isRequired: false, transformFunction: null }, indentSize: { classPropertyName: "indentSize", publicName: "indentSize", isSignal: true, isRequired: false, transformFunction: null }, rangeWidth: { classPropertyName: "rangeWidth", publicName: "rangeWidth", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { toggleExpandedRequest: "toggleExpandedRequest", changePageRequest: "changePageRequest", editNodeFilterRequest: "editNodeFilterRequest" }, ngImport: i0, template: "@if (node()) {\r\n<div id=\"node\" [style.margin-left.px]=\"(node()!.y - 1) * indentSize()\">\r\n <!-- pager -->\r\n @if (node()!.expanded && paging() && paging()!.pageCount > 1) {\r\n <div id=\"pager\" [style.display]=\"hidePaging() ? 'inherit' : 'block'\">\r\n <pdb-compact-pager\r\n [paging]=\"paging()!\"\r\n (pagingChange)=\"onPagingChange(node()!, $event)\"\r\n />\r\n <pdb-range-view\r\n [width]=\"rangeWidth()\"\r\n [domain]=\"[0, paging()!.pageCount]\"\r\n [range]=\"[paging()!.pageNumber - 1, paging()!.pageNumber]\"\r\n />\r\n </div>\r\n }\r\n <!-- node -->\r\n <div class=\"form-row\">\r\n <!-- expand/collapse button -->\r\n <button\r\n type=\"button\"\r\n mat-icon-button\r\n [matTooltip]=\"node()?.expanded ? 'Collapse' : 'Expand'\"\r\n i18n-matTooltip\r\n [disabled]=\"node()!.hasChildren === false\"\r\n (click)=\"onToggleExpanded()\"\r\n >\r\n <mat-icon class=\"mat-primary\" i18n>\r\n @if (node()!.hasChildren === true) { @if (node()!.expanded) {\r\n chevron_left } @else { chevron_right } } @else { stop }\r\n </mat-icon>\r\n </button>\r\n\r\n <!-- tag -->\r\n @if (!hideLabel()) {\r\n <span\r\n class=\"tag\"\r\n [ngStyle]=\"{\r\n 'background-color': (node()!.tag | stringToColor),\r\n color: node()!.tag | stringToColor | colorToContrast\r\n }\"\r\n >{{ node()!.tag }}</span\r\n >\r\n\r\n <!-- loc and label -->\r\n @if (!hideLoc()) {\r\n <span class=\"loc\">{{ node()!.y }}.{{ node()!.x }}</span> - }\r\n {{ node()!.label }}\r\n }\r\n\r\n <!-- PROJECTED NODE -->\r\n <ng-content></ng-content>\r\n\r\n <!-- debug -->\r\n @if (debug()) {\r\n <span class=\"debug\"\r\n >#{{ node()!.id }}\r\n <span\r\n >| {{ node()!.paging.pageNumber }}/{{ node()!.paging.pageCount }} ({{\r\n node()!.paging.total\r\n }})</span\r\n ></span\r\n >\r\n }\r\n\r\n <!-- filter -->\r\n @if (!hideFilter()){ @if (!node()?.filter && node()?.y) {\r\n <div class=\"muted\">\r\n <button\r\n type=\"button\"\r\n mat-icon-button\r\n matTooltip=\"Add filter\"\r\n i18n-matTooltip\r\n (click)=\"onEditFilter()\"\r\n >\r\n <mat-icon>filter_list</mat-icon>\r\n </button>\r\n </div>\r\n } @if (node()?.filter && node()?.y) {\r\n <div class=\"muted\">\r\n <button type=\"button\" mat-icon-button (click)=\"onEditFilter()\">\r\n <mat-icon [matBadge]=\"node()?.filter ? 'F' : ''\">filter_alt</mat-icon>\r\n </button>\r\n </div>\r\n } }\r\n </div>\r\n</div>\r\n}\r\n", styles: [":root{--browser-tree-node-margin-bottom: 4px;--browser-tree-node-padding: 4px 6px;--browser-tree-node-border: 1px solid #98a8d4;--browser-tree-node-border-radius: 6px;--browser-tree-node-hover-bg-color: #d6dee9}#node{margin-bottom:var(--browser-tree-node-margin-bottom);padding:var(--browser-tree-node-padding);border:var(--browser-tree-node-border);border-radius:var(--browser-tree-node-border-radius)}#node:hover{background-color:var(--browser-tree-node-hover-bg-color)}.form-row{display:flex;gap:8px;align-items:center}.form-row *{flex:0 0 auto}.form-row span{flex:0 1 auto;white-space:normal}#node #pager{display:none}#node:hover #pager{display:block}span.loc{font-size:.85em;color:#666;vertical-align:middle}span.tag{border:1px solid #aaa;border-radius:4px;padding:0 4px}fieldset{border:1px solid silver;border-radius:6px;padding:4px 6px;margin:4px 0}legend{color:silver}.muted{opacity:.3}.muted:hover{opacity:1}.debug{font-size:.85em;color:#9c3d3e}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "ngmodule", type: MatBadgeModule }, { kind: "directive", type: i2$1.MatBadge, selector: "[matBadge]", inputs: ["matBadgeColor", "matBadgeOverlap", "matBadgeDisabled", "matBadgePosition", "matBadge", "matBadgeDescription", "matBadgeSize", "matBadgeHidden"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i1.MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i2.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i5.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "component", type:
|
|
183
187
|
// local
|
|
184
188
|
CompactPagerComponent, selector: "pdb-compact-pager", inputs: ["paging"], outputs: ["pagingChange"] }, { kind: "component", type: RangeViewComponent, selector: "pdb-range-view", inputs: ["domain", "range", "width", "height"] }, { kind: "pipe", type:
|
|
185
189
|
// ngx-tools
|
|
186
190
|
ColorToContrastPipe, name: "colorToContrast" }, { kind: "pipe", type: StringToColorPipe, name: "stringToColor" }] }); }
|
|
187
191
|
}
|
|
188
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
192
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.0", ngImport: i0, type: BrowserTreeNodeComponent, decorators: [{
|
|
189
193
|
type: Component,
|
|
190
194
|
args: [{ selector: 'pdb-browser-tree-node', imports: [
|
|
191
195
|
CommonModule,
|
|
@@ -200,7 +204,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.1", ngImpor
|
|
|
200
204
|
CompactPagerComponent,
|
|
201
205
|
RangeViewComponent,
|
|
202
206
|
], template: "@if (node()) {\r\n<div id=\"node\" [style.margin-left.px]=\"(node()!.y - 1) * indentSize()\">\r\n <!-- pager -->\r\n @if (node()!.expanded && paging() && paging()!.pageCount > 1) {\r\n <div id=\"pager\" [style.display]=\"hidePaging() ? 'inherit' : 'block'\">\r\n <pdb-compact-pager\r\n [paging]=\"paging()!\"\r\n (pagingChange)=\"onPagingChange(node()!, $event)\"\r\n />\r\n <pdb-range-view\r\n [width]=\"rangeWidth()\"\r\n [domain]=\"[0, paging()!.pageCount]\"\r\n [range]=\"[paging()!.pageNumber - 1, paging()!.pageNumber]\"\r\n />\r\n </div>\r\n }\r\n <!-- node -->\r\n <div class=\"form-row\">\r\n <!-- expand/collapse button -->\r\n <button\r\n type=\"button\"\r\n mat-icon-button\r\n [matTooltip]=\"node()?.expanded ? 'Collapse' : 'Expand'\"\r\n i18n-matTooltip\r\n [disabled]=\"node()!.hasChildren === false\"\r\n (click)=\"onToggleExpanded()\"\r\n >\r\n <mat-icon class=\"mat-primary\" i18n>\r\n @if (node()!.hasChildren === true) { @if (node()!.expanded) {\r\n chevron_left } @else { chevron_right } } @else { stop }\r\n </mat-icon>\r\n </button>\r\n\r\n <!-- tag -->\r\n @if (!hideLabel()) {\r\n <span\r\n class=\"tag\"\r\n [ngStyle]=\"{\r\n 'background-color': (node()!.tag | stringToColor),\r\n color: node()!.tag | stringToColor | colorToContrast\r\n }\"\r\n >{{ node()!.tag }}</span\r\n >\r\n\r\n <!-- loc and label -->\r\n @if (!hideLoc()) {\r\n <span class=\"loc\">{{ node()!.y }}.{{ node()!.x }}</span> - }\r\n {{ node()!.label }}\r\n }\r\n\r\n <!-- PROJECTED NODE -->\r\n <ng-content></ng-content>\r\n\r\n <!-- debug -->\r\n @if (debug()) {\r\n <span class=\"debug\"\r\n >#{{ node()!.id }}\r\n <span\r\n >| {{ node()!.paging.pageNumber }}/{{ node()!.paging.pageCount }} ({{\r\n node()!.paging.total\r\n }})</span\r\n ></span\r\n >\r\n }\r\n\r\n <!-- filter -->\r\n @if (!hideFilter()){ @if (!node()?.filter && node()?.y) {\r\n <div class=\"muted\">\r\n <button\r\n type=\"button\"\r\n mat-icon-button\r\n matTooltip=\"Add filter\"\r\n i18n-matTooltip\r\n (click)=\"onEditFilter()\"\r\n >\r\n <mat-icon>filter_list</mat-icon>\r\n </button>\r\n </div>\r\n } @if (node()?.filter && node()?.y) {\r\n <div class=\"muted\">\r\n <button type=\"button\" mat-icon-button (click)=\"onEditFilter()\">\r\n <mat-icon [matBadge]=\"node()?.filter ? 'F' : ''\">filter_alt</mat-icon>\r\n </button>\r\n </div>\r\n } }\r\n </div>\r\n</div>\r\n}\r\n", styles: [":root{--browser-tree-node-margin-bottom: 4px;--browser-tree-node-padding: 4px 6px;--browser-tree-node-border: 1px solid #98a8d4;--browser-tree-node-border-radius: 6px;--browser-tree-node-hover-bg-color: #d6dee9}#node{margin-bottom:var(--browser-tree-node-margin-bottom);padding:var(--browser-tree-node-padding);border:var(--browser-tree-node-border);border-radius:var(--browser-tree-node-border-radius)}#node:hover{background-color:var(--browser-tree-node-hover-bg-color)}.form-row{display:flex;gap:8px;align-items:center}.form-row *{flex:0 0 auto}.form-row span{flex:0 1 auto;white-space:normal}#node #pager{display:none}#node:hover #pager{display:block}span.loc{font-size:.85em;color:#666;vertical-align:middle}span.tag{border:1px solid #aaa;border-radius:4px;padding:0 4px}fieldset{border:1px solid silver;border-radius:6px;padding:4px 6px;margin:4px 0}legend{color:silver}.muted{opacity:.3}.muted:hover{opacity:1}.debug{font-size:.85em;color:#9c3d3e}\n"] }]
|
|
203
|
-
}] });
|
|
207
|
+
}], propDecorators: { node: [{ type: i0.Input, args: [{ isSignal: true, alias: "node", required: false }] }], paging: [{ type: i0.Input, args: [{ isSignal: true, alias: "paging", required: false }] }], debug: [{ type: i0.Input, args: [{ isSignal: true, alias: "debug", required: false }] }], hideLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "hideLabel", required: false }] }], hideLoc: [{ type: i0.Input, args: [{ isSignal: true, alias: "hideLoc", required: false }] }], hidePaging: [{ type: i0.Input, args: [{ isSignal: true, alias: "hidePaging", required: false }] }], hideFilter: [{ type: i0.Input, args: [{ isSignal: true, alias: "hideFilter", required: false }] }], indentSize: [{ type: i0.Input, args: [{ isSignal: true, alias: "indentSize", required: false }] }], rangeWidth: [{ type: i0.Input, args: [{ isSignal: true, alias: "rangeWidth", required: false }] }], toggleExpandedRequest: [{ type: i0.Output, args: ["toggleExpandedRequest"] }], changePageRequest: [{ type: i0.Output, args: ["changePageRequest"] }], editNodeFilterRequest: [{ type: i0.Output, args: ["editNodeFilterRequest"] }] } });
|
|
204
208
|
|
|
205
209
|
/**
|
|
206
210
|
* A Least Recently Used cache that can be used to store any type of object.
|
|
@@ -499,11 +503,13 @@ class PagedListStore {
|
|
|
499
503
|
|
|
500
504
|
/**
|
|
501
505
|
* A store for the node browser component. This store is used to keep a
|
|
502
|
-
* list of nodes, and to load them from the
|
|
503
|
-
* node. Every tree node in the list
|
|
504
|
-
* page count and total items, plus
|
|
506
|
+
* list of nodes, and to load them from the injected PagedTreeStoreService<F>.
|
|
507
|
+
* It also keeps the root node(s) in memory. Every tree node in the list
|
|
508
|
+
* is extended with page number, page count and total items, plus
|
|
509
|
+
* expansion-related metadata.
|
|
505
510
|
* The store keeps a flat list of these tree nodes, allowing users to
|
|
506
|
-
* expand and collapse them.
|
|
511
|
+
* expand and collapse them. Each node has its children paged, and optionally
|
|
512
|
+
* filtered.
|
|
507
513
|
* F is the type of the filter object, E is the type of the paged tree nodes.
|
|
508
514
|
*/
|
|
509
515
|
class PagedTreeStore {
|
|
@@ -539,7 +545,7 @@ class PagedTreeStore {
|
|
|
539
545
|
this._hasMockRoot = options.hasMockRoot || false;
|
|
540
546
|
}
|
|
541
547
|
/**
|
|
542
|
-
* Gets the global filter,
|
|
548
|
+
* Gets the global filter, optionally overridden with values
|
|
543
549
|
* from the specified node's filter.
|
|
544
550
|
* @param node The optional node.
|
|
545
551
|
* @returns The filter.
|
|
@@ -567,7 +573,8 @@ class PagedTreeStore {
|
|
|
567
573
|
return this._nodes$.value;
|
|
568
574
|
}
|
|
569
575
|
/**
|
|
570
|
-
* Get the root node of the tree.
|
|
576
|
+
* Get the root node of the tree. Given that the store keeps a flat
|
|
577
|
+
* list, this is the first node in the list, if any.
|
|
571
578
|
* @returns The root node of the tree or undefined if empty.
|
|
572
579
|
*/
|
|
573
580
|
getRootNode() {
|
|
@@ -596,8 +603,11 @@ class PagedTreeStore {
|
|
|
596
603
|
* @returns Observable of the page of nodes.
|
|
597
604
|
*/
|
|
598
605
|
getPageFromCacheOrServer(filter, pageNumber) {
|
|
606
|
+
// try to get the page from cache
|
|
599
607
|
const key = this.buildCacheKey(pageNumber, filter);
|
|
600
608
|
const pageInCache = this._cache.get(key);
|
|
609
|
+
// if in cache, just return it; else, get it from the server and
|
|
610
|
+
// store it in cache
|
|
601
611
|
if (pageInCache) {
|
|
602
612
|
return of(pageInCache);
|
|
603
613
|
}
|
|
@@ -653,10 +663,11 @@ class PagedTreeStore {
|
|
|
653
663
|
this._service
|
|
654
664
|
.getNodes({
|
|
655
665
|
...filter,
|
|
656
|
-
parentId: undefined,
|
|
666
|
+
parentId: undefined, // root have no parent
|
|
657
667
|
}, 1, this._pageSize, this._hasMockRoot)
|
|
658
668
|
.subscribe({
|
|
659
669
|
next: (page) => {
|
|
670
|
+
// update the nodes
|
|
660
671
|
this._nodes$.next(this.createPageNodes(page));
|
|
661
672
|
// get the children of each node thus calculating their hasChildren property
|
|
662
673
|
const childrenObservables = this._nodes$.value.map((node) => this.getPageFromCacheOrServer({ ...filter, parentId: node.id }, 1));
|
|
@@ -702,13 +713,17 @@ class PagedTreeStore {
|
|
|
702
713
|
*/
|
|
703
714
|
expand(id) {
|
|
704
715
|
return new Promise((resolve, reject) => {
|
|
716
|
+
// get the node to expand
|
|
705
717
|
const node = this._nodes$.value.find((n) => n.id === id);
|
|
718
|
+
// if no node, or no children, or already expanded, do nothing
|
|
706
719
|
if (!node || node.hasChildren === false || node.expanded) {
|
|
707
720
|
resolve(false);
|
|
708
721
|
}
|
|
722
|
+
// get the first page of children for this node
|
|
709
723
|
this.getPageFromCacheOrServer({ ...this.getFilter(node), parentId: id }, 1).subscribe((page) => {
|
|
710
724
|
// no children, set hasChildren to false
|
|
711
725
|
if (!page.total) {
|
|
726
|
+
// no children, next time we won't try to expand
|
|
712
727
|
node.hasChildren = false;
|
|
713
728
|
resolve(false);
|
|
714
729
|
}
|
|
@@ -776,6 +791,11 @@ class PagedTreeStore {
|
|
|
776
791
|
}
|
|
777
792
|
return true;
|
|
778
793
|
}
|
|
794
|
+
/**
|
|
795
|
+
* Get the children of the node with the specified ID.
|
|
796
|
+
* @param id The ID of the node whose children you want to get.
|
|
797
|
+
* @returns An array of child nodes.
|
|
798
|
+
*/
|
|
779
799
|
getChildren(id) {
|
|
780
800
|
const node = this._nodes$.value.find((n) => n.id === id);
|
|
781
801
|
if (!node || node.hasChildren === false) {
|
|
@@ -856,7 +876,8 @@ class PagedTreeStore {
|
|
|
856
876
|
return Promise.resolve(true);
|
|
857
877
|
}
|
|
858
878
|
/**
|
|
859
|
-
* Change the page including the
|
|
879
|
+
* Change the page including the children of the parent node with
|
|
880
|
+
* the specified ID.
|
|
860
881
|
* @param parentId The ID of the parent node whose children are inside the page
|
|
861
882
|
* you want to change.
|
|
862
883
|
* @param pageNumber The new page number.
|
|
@@ -866,45 +887,64 @@ class PagedTreeStore {
|
|
|
866
887
|
return new Promise((resolve, reject) => {
|
|
867
888
|
// get the parent node
|
|
868
889
|
const parentNode = this._nodes$.value.find((n) => n.id === parentId);
|
|
890
|
+
// if parent not found, do nothing
|
|
869
891
|
if (!parentNode) {
|
|
870
892
|
resolve(false);
|
|
893
|
+
return;
|
|
894
|
+
}
|
|
895
|
+
// parent should be expanded
|
|
896
|
+
if (!parentNode.expanded) {
|
|
897
|
+
resolve(false);
|
|
898
|
+
return;
|
|
871
899
|
}
|
|
872
900
|
// get the page
|
|
873
|
-
this.getPageFromCacheOrServer({ ...this.getFilter(parentNode), parentId }, pageNumber).subscribe(
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
901
|
+
this.getPageFromCacheOrServer({ ...this.getFilter(parentNode), parentId }, pageNumber).subscribe({
|
|
902
|
+
next: (page) => {
|
|
903
|
+
// if page is empty, collapse the parent
|
|
904
|
+
if (!page.total) {
|
|
905
|
+
parentNode.hasChildren = false;
|
|
906
|
+
parentNode.expanded = false;
|
|
907
|
+
this._nodes$.next(this._nodes$.value);
|
|
908
|
+
resolve(false);
|
|
909
|
+
return;
|
|
910
|
+
}
|
|
879
911
|
this._dirty = true;
|
|
880
|
-
// remove all the nodes in the same page of node
|
|
881
|
-
// with all their descendants
|
|
882
912
|
const nodes = this._nodes$.value;
|
|
883
|
-
const
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
const oldPageNr = nodes[start].paging.pageNumber;
|
|
888
|
-
while (start > 0 &&
|
|
889
|
-
nodes[start - 1].parentId === parentId &&
|
|
890
|
-
nodes[start - 1].paging.pageNumber === oldPageNr) {
|
|
891
|
-
start--;
|
|
913
|
+
const parentIndex = nodes.indexOf(parentNode);
|
|
914
|
+
if (parentIndex === -1) {
|
|
915
|
+
reject(`Parent node ID ${parentId} not found in store`);
|
|
916
|
+
return;
|
|
892
917
|
}
|
|
893
|
-
|
|
894
|
-
//
|
|
918
|
+
const pageNodes = this.createPageNodes(page);
|
|
919
|
+
// find the range of children to replace
|
|
920
|
+
let start = parentIndex + 1;
|
|
895
921
|
let end = start;
|
|
896
|
-
|
|
897
|
-
while (end < nodes.length &&
|
|
922
|
+
// find all current children of this parent (at any page)
|
|
923
|
+
while (end < nodes.length &&
|
|
924
|
+
nodes[end].parentId === parentId &&
|
|
925
|
+
nodes[end].y === parentNode.y + 1) {
|
|
926
|
+
// skip descendants of children
|
|
927
|
+
const childY = nodes[end].y;
|
|
898
928
|
end++;
|
|
929
|
+
while (end < nodes.length && nodes[end].y > childY) {
|
|
930
|
+
end++;
|
|
931
|
+
}
|
|
899
932
|
}
|
|
900
|
-
// replace
|
|
901
|
-
nodes.splice(start, end - start);
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
933
|
+
// replace the children with the new page
|
|
934
|
+
nodes.splice(start, end - start, ...pageNodes);
|
|
935
|
+
// update parent paging info
|
|
936
|
+
parentNode.paging = {
|
|
937
|
+
pageNumber: page.pageNumber,
|
|
938
|
+
pageCount: page.pageCount,
|
|
939
|
+
total: page.total,
|
|
940
|
+
};
|
|
905
941
|
this._nodes$.next(nodes);
|
|
906
942
|
resolve(true);
|
|
907
|
-
}
|
|
943
|
+
},
|
|
944
|
+
error: (error) => {
|
|
945
|
+
console.error('Error changing page:', error);
|
|
946
|
+
reject(error);
|
|
947
|
+
},
|
|
908
948
|
});
|
|
909
949
|
});
|
|
910
950
|
}
|
|
@@ -953,24 +993,24 @@ class PagedTreeStore {
|
|
|
953
993
|
this.removeHilites();
|
|
954
994
|
return;
|
|
955
995
|
}
|
|
956
|
-
//
|
|
996
|
+
// first remove all existing hilites
|
|
957
997
|
this.removeHilites();
|
|
958
998
|
const searchLower = searchText.toLowerCase();
|
|
959
999
|
const matchingNodes = [];
|
|
960
|
-
//
|
|
961
|
-
//
|
|
1000
|
+
// find all matching nodes (including those not currently visible):
|
|
1001
|
+
// search through all possible nodes, not just visible ones
|
|
962
1002
|
await this.searchAllNodes(searchLower, matchingNodes);
|
|
963
|
-
//
|
|
1003
|
+
// highlight the matching nodes
|
|
964
1004
|
matchingNodes.forEach((node) => {
|
|
965
1005
|
node.hilite = true;
|
|
966
1006
|
});
|
|
967
|
-
//
|
|
1007
|
+
// ensure all matching nodes are visible by expanding their ancestor paths
|
|
968
1008
|
const expansionPromises = [];
|
|
969
1009
|
for (const node of matchingNodes) {
|
|
970
1010
|
expansionPromises.push(this.ensureNodeVisible(node.id));
|
|
971
1011
|
}
|
|
972
1012
|
await Promise.all(expansionPromises);
|
|
973
|
-
//
|
|
1013
|
+
// update the nodes observable
|
|
974
1014
|
this._nodes$.next(this._nodes$.value);
|
|
975
1015
|
}
|
|
976
1016
|
/**
|
|
@@ -980,7 +1020,7 @@ class PagedTreeStore {
|
|
|
980
1020
|
* @param matchingNodes Array to collect matching nodes.
|
|
981
1021
|
*/
|
|
982
1022
|
async searchAllNodes(searchLower, matchingNodes) {
|
|
983
|
-
//
|
|
1023
|
+
// start with root nodes
|
|
984
1024
|
const rootNodes = this._nodes$.value.filter((n) => n.parentId === undefined);
|
|
985
1025
|
for (const rootNode of rootNodes) {
|
|
986
1026
|
await this.searchNodeAndDescendants(rootNode, searchLower, matchingNodes);
|
|
@@ -993,17 +1033,17 @@ class PagedTreeStore {
|
|
|
993
1033
|
* @param matchingNodes Array to collect matching nodes.
|
|
994
1034
|
*/
|
|
995
1035
|
async searchNodeAndDescendants(node, searchLower, matchingNodes) {
|
|
996
|
-
//
|
|
1036
|
+
// check if current node matches
|
|
997
1037
|
if (node.label.toLowerCase().includes(searchLower)) {
|
|
998
1038
|
matchingNodes.push(node);
|
|
999
1039
|
}
|
|
1000
|
-
//
|
|
1040
|
+
// if node has children, we need to load them to search through them
|
|
1001
1041
|
if (node.hasChildren) {
|
|
1002
1042
|
// Expand the node if not already expanded to load its children
|
|
1003
1043
|
if (!node.expanded) {
|
|
1004
1044
|
await this.expand(node.id);
|
|
1005
1045
|
}
|
|
1006
|
-
//
|
|
1046
|
+
// get all direct children and search them recursively
|
|
1007
1047
|
const children = this.getChildren(node.id);
|
|
1008
1048
|
for (const child of children) {
|
|
1009
1049
|
await this.searchNodeAndDescendants(child, searchLower, matchingNodes);
|
|
@@ -1019,14 +1059,14 @@ class PagedTreeStore {
|
|
|
1019
1059
|
if (!node) {
|
|
1020
1060
|
return;
|
|
1021
1061
|
}
|
|
1022
|
-
//
|
|
1062
|
+
// find the path from root to this node
|
|
1023
1063
|
const ancestorPath = [];
|
|
1024
1064
|
let currentNode = node;
|
|
1025
1065
|
while (currentNode && currentNode.parentId !== undefined) {
|
|
1026
1066
|
ancestorPath.unshift(currentNode.parentId);
|
|
1027
1067
|
currentNode = this._nodes$.value.find((n) => n.id === currentNode.parentId);
|
|
1028
1068
|
}
|
|
1029
|
-
//
|
|
1069
|
+
// expand all ancestors in order
|
|
1030
1070
|
for (const ancestorId of ancestorPath) {
|
|
1031
1071
|
const ancestor = this._nodes$.value.find((n) => n.id === ancestorId);
|
|
1032
1072
|
if (ancestor && ancestor.hasChildren && !ancestor.expanded) {
|
|
@@ -1034,6 +1074,629 @@ class PagedTreeStore {
|
|
|
1034
1074
|
}
|
|
1035
1075
|
}
|
|
1036
1076
|
}
|
|
1077
|
+
get _isEditable() {
|
|
1078
|
+
// check if the service supports editing
|
|
1079
|
+
return 'saveChanges' in this._service;
|
|
1080
|
+
}
|
|
1081
|
+
get _editableService() {
|
|
1082
|
+
return this._isEditable
|
|
1083
|
+
? this._service
|
|
1084
|
+
: undefined;
|
|
1085
|
+
}
|
|
1086
|
+
/**
|
|
1087
|
+
* Add a child node to the specified parent.
|
|
1088
|
+
* @param parentId The ID of the parent node.
|
|
1089
|
+
* @param child The child node to add (without ID).
|
|
1090
|
+
* @param first If true, add as first child; otherwise add as last child.
|
|
1091
|
+
* @returns The added node with temporary ID, or undefined if service doesn't support editing.
|
|
1092
|
+
*/
|
|
1093
|
+
addChild(parentId, child, first = false) {
|
|
1094
|
+
return new Promise((resolve, reject) => {
|
|
1095
|
+
const editableService = this._editableService;
|
|
1096
|
+
if (!editableService) {
|
|
1097
|
+
resolve(undefined);
|
|
1098
|
+
return;
|
|
1099
|
+
}
|
|
1100
|
+
const parent = this._nodes$.value.find((n) => n.id === parentId);
|
|
1101
|
+
if (!parent) {
|
|
1102
|
+
reject(`Parent node with ID ${parentId} not found`);
|
|
1103
|
+
return;
|
|
1104
|
+
}
|
|
1105
|
+
// calculate position
|
|
1106
|
+
const siblings = this.getChildren(parentId);
|
|
1107
|
+
const position = first ? 1 : siblings.length + 1;
|
|
1108
|
+
const y = parent.y + 1;
|
|
1109
|
+
// adjust x values of existing siblings if inserting at beginning
|
|
1110
|
+
if (first && siblings.length > 0) {
|
|
1111
|
+
siblings.forEach((sibling) => {
|
|
1112
|
+
sibling.x++;
|
|
1113
|
+
editableService.updateNode(sibling.id, { x: sibling.x });
|
|
1114
|
+
});
|
|
1115
|
+
}
|
|
1116
|
+
const newChild = {
|
|
1117
|
+
...child,
|
|
1118
|
+
parentId,
|
|
1119
|
+
y,
|
|
1120
|
+
x: position,
|
|
1121
|
+
};
|
|
1122
|
+
const addedNode = editableService.addNode(newChild, parentId, position);
|
|
1123
|
+
// update parent hasChildren if it was false
|
|
1124
|
+
if (parent.hasChildren === false) {
|
|
1125
|
+
parent.hasChildren = true;
|
|
1126
|
+
editableService.updateNode(parentId, { hasChildren: true });
|
|
1127
|
+
}
|
|
1128
|
+
// if parent is expanded, add the node to the visible list
|
|
1129
|
+
if (parent.expanded) {
|
|
1130
|
+
const nodes = this._nodes$.value;
|
|
1131
|
+
const parentIndex = nodes.indexOf(parent);
|
|
1132
|
+
if (parentIndex !== -1) {
|
|
1133
|
+
const insertIndex = first
|
|
1134
|
+
? parentIndex + 1
|
|
1135
|
+
: parentIndex + siblings.length + 1;
|
|
1136
|
+
const pagedNode = {
|
|
1137
|
+
...addedNode,
|
|
1138
|
+
paging: parent.paging,
|
|
1139
|
+
expanded: false,
|
|
1140
|
+
};
|
|
1141
|
+
nodes.splice(insertIndex, 0, pagedNode);
|
|
1142
|
+
this._nodes$.next(nodes);
|
|
1143
|
+
}
|
|
1144
|
+
}
|
|
1145
|
+
this._dirty = true;
|
|
1146
|
+
this.invalidateCache(parentId);
|
|
1147
|
+
resolve(addedNode);
|
|
1148
|
+
});
|
|
1149
|
+
}
|
|
1150
|
+
/**
|
|
1151
|
+
* Add a sibling node next to the anchor node.
|
|
1152
|
+
* @param anchorId The ID of the anchor node.
|
|
1153
|
+
* @param sibling The sibling node to add (without ID).
|
|
1154
|
+
* @param before If true, add before anchor; otherwise add after anchor.
|
|
1155
|
+
* @returns The added node with temporary ID, or undefined if service doesn't support editing.
|
|
1156
|
+
*/
|
|
1157
|
+
addSibling(anchorId, sibling, before = false) {
|
|
1158
|
+
return new Promise((resolve, reject) => {
|
|
1159
|
+
const editableService = this._editableService;
|
|
1160
|
+
if (!editableService) {
|
|
1161
|
+
resolve(undefined);
|
|
1162
|
+
return;
|
|
1163
|
+
}
|
|
1164
|
+
const anchor = this._nodes$.value.find((n) => n.id === anchorId);
|
|
1165
|
+
if (!anchor) {
|
|
1166
|
+
reject(`Anchor node with ID ${anchorId} not found`);
|
|
1167
|
+
return;
|
|
1168
|
+
}
|
|
1169
|
+
// get all siblings of the anchor
|
|
1170
|
+
const siblings = this._nodes$.value.filter((n) => n.parentId === anchor.parentId && n.y === anchor.y);
|
|
1171
|
+
siblings.sort((a, b) => a.x - b.x);
|
|
1172
|
+
const anchorIndex = siblings.findIndex((s) => s.id === anchorId);
|
|
1173
|
+
const insertPosition = before ? anchor.x : anchor.x + 1;
|
|
1174
|
+
// adjust x values of siblings that come after the insertion point
|
|
1175
|
+
siblings.slice(before ? anchorIndex : anchorIndex + 1).forEach((s) => {
|
|
1176
|
+
s.x++;
|
|
1177
|
+
editableService.updateNode(s.id, { x: s.x });
|
|
1178
|
+
});
|
|
1179
|
+
const newSibling = {
|
|
1180
|
+
...sibling,
|
|
1181
|
+
parentId: anchor.parentId,
|
|
1182
|
+
y: anchor.y,
|
|
1183
|
+
x: insertPosition,
|
|
1184
|
+
};
|
|
1185
|
+
const addedNode = editableService.addNode(newSibling, anchor.parentId);
|
|
1186
|
+
// add to visible list if the parent is expanded
|
|
1187
|
+
const nodes = this._nodes$.value;
|
|
1188
|
+
const anchorVisibleIndex = nodes.indexOf(anchor);
|
|
1189
|
+
if (anchorVisibleIndex !== -1) {
|
|
1190
|
+
const insertIndex = before
|
|
1191
|
+
? anchorVisibleIndex
|
|
1192
|
+
: anchorVisibleIndex + 1;
|
|
1193
|
+
const pagedNode = {
|
|
1194
|
+
...addedNode,
|
|
1195
|
+
paging: anchor.paging,
|
|
1196
|
+
expanded: false,
|
|
1197
|
+
};
|
|
1198
|
+
nodes.splice(insertIndex, 0, pagedNode);
|
|
1199
|
+
this._nodes$.next(nodes);
|
|
1200
|
+
}
|
|
1201
|
+
this._dirty = true;
|
|
1202
|
+
this.invalidateCache(anchor.parentId);
|
|
1203
|
+
resolve(addedNode);
|
|
1204
|
+
});
|
|
1205
|
+
}
|
|
1206
|
+
/**
|
|
1207
|
+
* Remove a node from the tree.
|
|
1208
|
+
* We only track the deletion and update visible siblings.
|
|
1209
|
+
* Coordinate adjustments for non-visible nodes are handled by applyLocalChanges.
|
|
1210
|
+
*/
|
|
1211
|
+
removeNode(nodeId) {
|
|
1212
|
+
return new Promise((resolve, reject) => {
|
|
1213
|
+
const editableService = this._editableService;
|
|
1214
|
+
if (!editableService) {
|
|
1215
|
+
resolve(false);
|
|
1216
|
+
return;
|
|
1217
|
+
}
|
|
1218
|
+
const node = this._nodes$.value.find((n) => n.id === nodeId);
|
|
1219
|
+
if (!node) {
|
|
1220
|
+
resolve(false);
|
|
1221
|
+
return;
|
|
1222
|
+
}
|
|
1223
|
+
const parentId = node.parentId;
|
|
1224
|
+
const nodeY = node.y;
|
|
1225
|
+
const nodeX = node.x;
|
|
1226
|
+
// CRITICAL: ensure the node is in the editable service's cache
|
|
1227
|
+
// before removing it, so its original data is preserved
|
|
1228
|
+
if (!editableService.getNode(nodeId)) {
|
|
1229
|
+
// pass the complete node including any extended properties (like 'key'):
|
|
1230
|
+
// this preserves all data needed for persistence
|
|
1231
|
+
editableService.updateNode(nodeId, node);
|
|
1232
|
+
console.log('Added node to cache before deletion:', nodeId);
|
|
1233
|
+
}
|
|
1234
|
+
// remove all descendants first
|
|
1235
|
+
const descendants = this.getDescendants(nodeId);
|
|
1236
|
+
descendants.forEach((descendant) => {
|
|
1237
|
+
// ensure each descendant is in cache before removing
|
|
1238
|
+
if (!editableService.getNode(descendant.id)) {
|
|
1239
|
+
editableService.updateNode(descendant.id, descendant);
|
|
1240
|
+
console.log('Added descendant to cache before deletion:', descendant.id);
|
|
1241
|
+
}
|
|
1242
|
+
editableService.removeNode(descendant.id);
|
|
1243
|
+
});
|
|
1244
|
+
// remove the node itself
|
|
1245
|
+
console.log('Removing node from editable service:', nodeId);
|
|
1246
|
+
editableService.removeNode(nodeId);
|
|
1247
|
+
// update ONLY visible siblings' x coordinates
|
|
1248
|
+
// (Non-visible siblings will get adjusted by applyLocalChanges)
|
|
1249
|
+
const visibleSiblings = this._nodes$.value.filter((n) => n.parentId === parentId && n.y === nodeY && n.x > nodeX);
|
|
1250
|
+
visibleSiblings.forEach((sibling) => {
|
|
1251
|
+
sibling.x--;
|
|
1252
|
+
// this will succeed because these nodes ARE in the local cache
|
|
1253
|
+
editableService.updateNode(sibling.id, { x: sibling.x });
|
|
1254
|
+
});
|
|
1255
|
+
// remove from visible list
|
|
1256
|
+
const nodes = this._nodes$.value;
|
|
1257
|
+
const nodeIndex = nodes.indexOf(node);
|
|
1258
|
+
if (nodeIndex !== -1) {
|
|
1259
|
+
let endIndex = nodeIndex + 1;
|
|
1260
|
+
while (endIndex < nodes.length && nodes[endIndex].y > nodeY) {
|
|
1261
|
+
endIndex++;
|
|
1262
|
+
}
|
|
1263
|
+
nodes.splice(nodeIndex, endIndex - nodeIndex);
|
|
1264
|
+
}
|
|
1265
|
+
// update parent hasChildren if needed
|
|
1266
|
+
if (parentId !== undefined) {
|
|
1267
|
+
const parent = this._nodes$.value.find((n) => n.id === parentId);
|
|
1268
|
+
if (parent && parent.paging.total === 1) {
|
|
1269
|
+
// this was the last child
|
|
1270
|
+
parent.hasChildren = false;
|
|
1271
|
+
parent.expanded = false;
|
|
1272
|
+
editableService.updateNode(parentId, { hasChildren: false });
|
|
1273
|
+
this._nodes$.next(nodes);
|
|
1274
|
+
this._dirty = true;
|
|
1275
|
+
this.invalidateCache(parentId);
|
|
1276
|
+
resolve(true);
|
|
1277
|
+
return;
|
|
1278
|
+
}
|
|
1279
|
+
}
|
|
1280
|
+
this._nodes$.next(nodes);
|
|
1281
|
+
this._dirty = true;
|
|
1282
|
+
this.invalidateCache(parentId);
|
|
1283
|
+
// reload the parent's current page to show the updated state
|
|
1284
|
+
if (parentId !== undefined) {
|
|
1285
|
+
const parent = this._nodes$.value.find((n) => n.id === parentId);
|
|
1286
|
+
if (parent?.expanded && parent?.paging) {
|
|
1287
|
+
this.changePage(parentId, parent.paging.pageNumber)
|
|
1288
|
+
.then(() => resolve(true))
|
|
1289
|
+
.catch(reject);
|
|
1290
|
+
return;
|
|
1291
|
+
}
|
|
1292
|
+
}
|
|
1293
|
+
resolve(true);
|
|
1294
|
+
});
|
|
1295
|
+
}
|
|
1296
|
+
/**
|
|
1297
|
+
* Replace a node with a new one.
|
|
1298
|
+
* @param oldNodeId The ID of the node to replace.
|
|
1299
|
+
* @param newNode The new node data (without ID).
|
|
1300
|
+
* @param keepDescendants If true, keep descendants; otherwise remove them.
|
|
1301
|
+
* @returns Promise that resolves to the new node, or undefined if service
|
|
1302
|
+
* doesn't support editing.
|
|
1303
|
+
*/
|
|
1304
|
+
replaceNode(oldNodeId, newNode, keepDescendants = true) {
|
|
1305
|
+
return new Promise((resolve, reject) => {
|
|
1306
|
+
const editableService = this._editableService;
|
|
1307
|
+
if (!editableService) {
|
|
1308
|
+
resolve(undefined);
|
|
1309
|
+
return;
|
|
1310
|
+
}
|
|
1311
|
+
const oldNode = this._nodes$.value.find((n) => n.id === oldNodeId);
|
|
1312
|
+
if (!oldNode) {
|
|
1313
|
+
reject(`Node with ID ${oldNodeId} not found`);
|
|
1314
|
+
return;
|
|
1315
|
+
}
|
|
1316
|
+
const updatedNode = {
|
|
1317
|
+
...newNode,
|
|
1318
|
+
id: oldNodeId,
|
|
1319
|
+
parentId: oldNode.parentId,
|
|
1320
|
+
y: oldNode.y,
|
|
1321
|
+
x: oldNode.x,
|
|
1322
|
+
};
|
|
1323
|
+
editableService.updateNode(oldNodeId, updatedNode);
|
|
1324
|
+
// handle descendants
|
|
1325
|
+
if (!keepDescendants) {
|
|
1326
|
+
const descendants = this.getDescendants(oldNodeId);
|
|
1327
|
+
descendants.forEach((descendant) => {
|
|
1328
|
+
editableService.removeNode(descendant.id);
|
|
1329
|
+
});
|
|
1330
|
+
// update hasChildren
|
|
1331
|
+
editableService.updateNode(oldNodeId, { hasChildren: false });
|
|
1332
|
+
}
|
|
1333
|
+
// update in visible list
|
|
1334
|
+
const nodes = this._nodes$.value;
|
|
1335
|
+
const nodeIndex = nodes.findIndex((n) => n.id === oldNodeId);
|
|
1336
|
+
if (nodeIndex !== -1) {
|
|
1337
|
+
const pagedNode = nodes[nodeIndex];
|
|
1338
|
+
Object.assign(pagedNode, updatedNode);
|
|
1339
|
+
if (!keepDescendants) {
|
|
1340
|
+
pagedNode.hasChildren = false;
|
|
1341
|
+
pagedNode.expanded = false;
|
|
1342
|
+
// remove descendants from visible list
|
|
1343
|
+
let endIndex = nodeIndex + 1;
|
|
1344
|
+
while (endIndex < nodes.length && nodes[endIndex].y > oldNode.y) {
|
|
1345
|
+
endIndex++;
|
|
1346
|
+
}
|
|
1347
|
+
if (endIndex > nodeIndex + 1) {
|
|
1348
|
+
nodes.splice(nodeIndex + 1, endIndex - nodeIndex - 1);
|
|
1349
|
+
}
|
|
1350
|
+
}
|
|
1351
|
+
this._nodes$.next(nodes);
|
|
1352
|
+
}
|
|
1353
|
+
this._dirty = true;
|
|
1354
|
+
this.invalidateCache(oldNode.parentId);
|
|
1355
|
+
resolve(updatedNode);
|
|
1356
|
+
});
|
|
1357
|
+
}
|
|
1358
|
+
/**
|
|
1359
|
+
* Get all descendants of a node.
|
|
1360
|
+
*/
|
|
1361
|
+
getDescendants(nodeId) {
|
|
1362
|
+
const node = this._nodes$.value.find((n) => n.id === nodeId);
|
|
1363
|
+
if (!node)
|
|
1364
|
+
return [];
|
|
1365
|
+
const descendants = [];
|
|
1366
|
+
const nodes = this._nodes$.value;
|
|
1367
|
+
const nodeIndex = nodes.indexOf(node);
|
|
1368
|
+
if (nodeIndex !== -1) {
|
|
1369
|
+
let i = nodeIndex + 1;
|
|
1370
|
+
while (i < nodes.length && nodes[i].y > node.y) {
|
|
1371
|
+
descendants.push(nodes[i]);
|
|
1372
|
+
i++;
|
|
1373
|
+
}
|
|
1374
|
+
}
|
|
1375
|
+
return descendants;
|
|
1376
|
+
}
|
|
1377
|
+
/**
|
|
1378
|
+
* Invalidate cache for a specific parent and its ancestors.
|
|
1379
|
+
*/
|
|
1380
|
+
invalidateCache(parentId) {
|
|
1381
|
+
// clear cache entries that might be affected by the change
|
|
1382
|
+
this._cache.clear();
|
|
1383
|
+
// if we have a specific parent, we could be more selective about
|
|
1384
|
+
// cache invalidation but for now, clearing all is safer
|
|
1385
|
+
}
|
|
1386
|
+
/**
|
|
1387
|
+
* Save all pending changes if the service supports it.
|
|
1388
|
+
* Preserves the current expansion state after saving.
|
|
1389
|
+
* @returns Promise that resolves when save is complete,
|
|
1390
|
+
* with ID mappings for new nodes.
|
|
1391
|
+
*/
|
|
1392
|
+
saveChanges() {
|
|
1393
|
+
return new Promise((resolve, reject) => {
|
|
1394
|
+
const editableService = this._editableService;
|
|
1395
|
+
if (!editableService) {
|
|
1396
|
+
resolve(undefined);
|
|
1397
|
+
return;
|
|
1398
|
+
}
|
|
1399
|
+
// capture current expansion state before saving
|
|
1400
|
+
const expandedNodeIds = this._nodes$.value
|
|
1401
|
+
.filter((n) => n.expanded)
|
|
1402
|
+
.map((n) => n.id);
|
|
1403
|
+
editableService.saveChanges().subscribe({
|
|
1404
|
+
next: (idMap) => {
|
|
1405
|
+
// update temporary IDs to permanent IDs in the visible list
|
|
1406
|
+
if (idMap.size > 0) {
|
|
1407
|
+
const nodes = this._nodes$.value;
|
|
1408
|
+
// update node IDs
|
|
1409
|
+
nodes.forEach((node) => {
|
|
1410
|
+
const newId = idMap.get(node.id);
|
|
1411
|
+
if (newId !== undefined) {
|
|
1412
|
+
node.id = newId;
|
|
1413
|
+
}
|
|
1414
|
+
});
|
|
1415
|
+
// update parent IDs for children of replaced nodes
|
|
1416
|
+
nodes.forEach((node) => {
|
|
1417
|
+
if (node.parentId !== undefined) {
|
|
1418
|
+
const newParentId = idMap.get(node.parentId);
|
|
1419
|
+
if (newParentId !== undefined) {
|
|
1420
|
+
node.parentId = newParentId;
|
|
1421
|
+
}
|
|
1422
|
+
}
|
|
1423
|
+
});
|
|
1424
|
+
this._nodes$.next(nodes);
|
|
1425
|
+
}
|
|
1426
|
+
// clear cache to force fresh data
|
|
1427
|
+
this._cache.clear();
|
|
1428
|
+
this._dirty = false;
|
|
1429
|
+
// restore expansion state by re-expanding previously expanded nodes
|
|
1430
|
+
const restoreExpansion = async () => {
|
|
1431
|
+
for (const oldId of expandedNodeIds) {
|
|
1432
|
+
const newId = idMap.get(oldId) ?? oldId;
|
|
1433
|
+
const node = this._nodes$.value.find((n) => n.id === newId);
|
|
1434
|
+
if (node && !node.expanded) {
|
|
1435
|
+
await this.expand(newId);
|
|
1436
|
+
}
|
|
1437
|
+
}
|
|
1438
|
+
};
|
|
1439
|
+
restoreExpansion().then(() => resolve(idMap));
|
|
1440
|
+
},
|
|
1441
|
+
error: (error) => reject(error),
|
|
1442
|
+
});
|
|
1443
|
+
});
|
|
1444
|
+
}
|
|
1445
|
+
/**
|
|
1446
|
+
* Check if there are unsaved changes.
|
|
1447
|
+
*/
|
|
1448
|
+
hasUnsavedChanges() {
|
|
1449
|
+
return this._editableService?.hasChanges() || false;
|
|
1450
|
+
}
|
|
1451
|
+
/**
|
|
1452
|
+
* Clear all unsaved changes.
|
|
1453
|
+
*/
|
|
1454
|
+
clearUnsavedChanges() {
|
|
1455
|
+
this._editableService?.clearChanges();
|
|
1456
|
+
this._dirty = true;
|
|
1457
|
+
}
|
|
1458
|
+
}
|
|
1459
|
+
|
|
1460
|
+
/**
|
|
1461
|
+
* Types of operations that can be performed on tree nodes.
|
|
1462
|
+
*/
|
|
1463
|
+
var ChangeOperationType;
|
|
1464
|
+
(function (ChangeOperationType) {
|
|
1465
|
+
ChangeOperationType["ADD"] = "add";
|
|
1466
|
+
ChangeOperationType["REMOVE"] = "remove";
|
|
1467
|
+
ChangeOperationType["UPDATE"] = "update";
|
|
1468
|
+
})(ChangeOperationType || (ChangeOperationType = {}));
|
|
1469
|
+
/**
|
|
1470
|
+
* Base implementation of EditablePagedTreeStoreService that handles change tracking
|
|
1471
|
+
* and provides common editing functionality. Implementers only need to override
|
|
1472
|
+
* the abstract methods for actual data persistence.
|
|
1473
|
+
*/
|
|
1474
|
+
class EditablePagedTreeStoreServiceBase {
|
|
1475
|
+
constructor() {
|
|
1476
|
+
this._changes = [];
|
|
1477
|
+
this._nextTempId = -1;
|
|
1478
|
+
this._nodes = new Map();
|
|
1479
|
+
this._removedNodes = new Set();
|
|
1480
|
+
this._hasChanges$ = new BehaviorSubject(false);
|
|
1481
|
+
/**
|
|
1482
|
+
* Observable that emits when the change state changes.
|
|
1483
|
+
*/
|
|
1484
|
+
this.hasChanges$ = this._hasChanges$.asObservable();
|
|
1485
|
+
}
|
|
1486
|
+
/**
|
|
1487
|
+
* Get nodes, including any local changes that haven't been saved yet.
|
|
1488
|
+
*/
|
|
1489
|
+
getNodes(filter, pageNumber, pageSize, hasMockRoot) {
|
|
1490
|
+
return this.fetchNodes(filter, pageNumber, pageSize, hasMockRoot).pipe(map((page) => this.applyLocalChanges(page, filter)));
|
|
1491
|
+
}
|
|
1492
|
+
/**
|
|
1493
|
+
* Apply local changes to a page of nodes.
|
|
1494
|
+
* This includes applying x-coordinate adjustments for siblings of deleted nodes.
|
|
1495
|
+
*/
|
|
1496
|
+
applyLocalChanges(page, filter) {
|
|
1497
|
+
let items = [...page.items];
|
|
1498
|
+
// Step 1: Remove deleted nodes from the current page
|
|
1499
|
+
items = items.filter((node) => !this._removedNodes.has(node.id));
|
|
1500
|
+
// Step 2: Calculate x-coordinate adjustments for this page based on deletions
|
|
1501
|
+
// For each removed node that would affect this page, adjust x coordinates
|
|
1502
|
+
const xAdjustments = new Map(); // nodeId -> adjustment amount
|
|
1503
|
+
this._removedNodes.forEach((removedId) => {
|
|
1504
|
+
const removeChange = this._changes.find((c) => c.type === ChangeOperationType.REMOVE && c.id === removedId);
|
|
1505
|
+
if (removeChange?.originalNode) {
|
|
1506
|
+
const removedNode = removeChange.originalNode;
|
|
1507
|
+
// For each item in current page that was a sibling after the removed node
|
|
1508
|
+
items.forEach((item) => {
|
|
1509
|
+
if (item.parentId === removedNode.parentId &&
|
|
1510
|
+
item.y === removedNode.y &&
|
|
1511
|
+
item.x > removedNode.x) {
|
|
1512
|
+
// This sibling should have its x decreased
|
|
1513
|
+
const currentAdjustment = xAdjustments.get(item.id) || 0;
|
|
1514
|
+
xAdjustments.set(item.id, currentAdjustment - 1);
|
|
1515
|
+
}
|
|
1516
|
+
});
|
|
1517
|
+
}
|
|
1518
|
+
});
|
|
1519
|
+
// Step 3: Apply updates to existing nodes, including coordinate adjustments
|
|
1520
|
+
items = items.map((node) => {
|
|
1521
|
+
let result = { ...node };
|
|
1522
|
+
// First apply any explicit updates from cache
|
|
1523
|
+
const updated = this._nodes.get(node.id);
|
|
1524
|
+
if (updated && !this._removedNodes.has(node.id)) {
|
|
1525
|
+
result = { ...result, ...updated };
|
|
1526
|
+
}
|
|
1527
|
+
// Then apply calculated x-coordinate adjustments from deletions
|
|
1528
|
+
const xAdjustment = xAdjustments.get(node.id);
|
|
1529
|
+
if (xAdjustment !== undefined && xAdjustment !== 0) {
|
|
1530
|
+
result.x = result.x + xAdjustment;
|
|
1531
|
+
}
|
|
1532
|
+
return result;
|
|
1533
|
+
});
|
|
1534
|
+
// Step 4: Add new nodes that belong to this specific page
|
|
1535
|
+
const pageParentId = filter.parentId;
|
|
1536
|
+
const newNodesForThisPage = Array.from(this._nodes.values()).filter((node) => node.id < 0 && // temporary ID (newly added)
|
|
1537
|
+
!this._removedNodes.has(node.id) && // not removed
|
|
1538
|
+
node.parentId === pageParentId && // belongs to the same parent
|
|
1539
|
+
this.matchesFilter(node, filter) && // matches the filter
|
|
1540
|
+
node.label !== undefined && // has all required properties
|
|
1541
|
+
node.x !== undefined &&
|
|
1542
|
+
node.y !== undefined);
|
|
1543
|
+
items.push(...newNodesForThisPage);
|
|
1544
|
+
// Step 5: Sort items by their position (x coordinate)
|
|
1545
|
+
items.sort((a, b) => a.x - b.x);
|
|
1546
|
+
// Step 6: Adjust total count to reflect deletions and additions for this parent
|
|
1547
|
+
let totalAdjustment = 0;
|
|
1548
|
+
// Count deletions that affect this parent
|
|
1549
|
+
this._removedNodes.forEach((removedId) => {
|
|
1550
|
+
const change = this._changes.find((c) => c.type === ChangeOperationType.REMOVE && c.id === removedId);
|
|
1551
|
+
if (change?.originalNode?.parentId === pageParentId) {
|
|
1552
|
+
totalAdjustment--;
|
|
1553
|
+
}
|
|
1554
|
+
});
|
|
1555
|
+
// Count additions for this parent
|
|
1556
|
+
totalAdjustment += newNodesForThisPage.length;
|
|
1557
|
+
const adjustedTotal = Math.max(0, page.total + totalAdjustment);
|
|
1558
|
+
const adjustedPageCount = Math.ceil(adjustedTotal / (page.pageSize || 20));
|
|
1559
|
+
return {
|
|
1560
|
+
...page,
|
|
1561
|
+
items: items,
|
|
1562
|
+
total: adjustedTotal,
|
|
1563
|
+
pageCount: adjustedPageCount,
|
|
1564
|
+
};
|
|
1565
|
+
}
|
|
1566
|
+
/**
|
|
1567
|
+
* Check if a node matches the given filter.
|
|
1568
|
+
*/
|
|
1569
|
+
matchesFilter(node, filter) {
|
|
1570
|
+
if (filter.parentId !== undefined && node.parentId !== filter.parentId) {
|
|
1571
|
+
return false;
|
|
1572
|
+
}
|
|
1573
|
+
if (filter.tags &&
|
|
1574
|
+
filter.tags.length > 0 &&
|
|
1575
|
+
(!node.tag || !filter.tags.includes(node.tag))) {
|
|
1576
|
+
return false;
|
|
1577
|
+
}
|
|
1578
|
+
return true;
|
|
1579
|
+
}
|
|
1580
|
+
/**
|
|
1581
|
+
* Add a new node to the tree.
|
|
1582
|
+
*/
|
|
1583
|
+
addNode(node, parentId, position) {
|
|
1584
|
+
const newNode = {
|
|
1585
|
+
...node,
|
|
1586
|
+
id: this._nextTempId--,
|
|
1587
|
+
parentId,
|
|
1588
|
+
};
|
|
1589
|
+
this._nodes.set(newNode.id, newNode);
|
|
1590
|
+
this._changes.push({
|
|
1591
|
+
type: ChangeOperationType.ADD,
|
|
1592
|
+
id: newNode.id,
|
|
1593
|
+
node: newNode,
|
|
1594
|
+
parentId,
|
|
1595
|
+
position,
|
|
1596
|
+
});
|
|
1597
|
+
this.updateHasChanges();
|
|
1598
|
+
return newNode;
|
|
1599
|
+
}
|
|
1600
|
+
/**
|
|
1601
|
+
* Remove a node from the tree.
|
|
1602
|
+
*/
|
|
1603
|
+
removeNode(id) {
|
|
1604
|
+
const node = this._nodes.get(id);
|
|
1605
|
+
if (node || !this._removedNodes.has(id)) {
|
|
1606
|
+
this._removedNodes.add(id);
|
|
1607
|
+
this._changes.push({
|
|
1608
|
+
type: ChangeOperationType.REMOVE,
|
|
1609
|
+
id,
|
|
1610
|
+
originalNode: node,
|
|
1611
|
+
});
|
|
1612
|
+
this.updateHasChanges();
|
|
1613
|
+
}
|
|
1614
|
+
}
|
|
1615
|
+
/**
|
|
1616
|
+
* Update an existing node.
|
|
1617
|
+
* If the node is in cache, update it with the partial updates.
|
|
1618
|
+
* If not in cache and updates is a complete TreeNode, add it to cache.
|
|
1619
|
+
* Otherwise skip (normal for pagination).
|
|
1620
|
+
*/
|
|
1621
|
+
updateNode(id, updates) {
|
|
1622
|
+
const existing = this._nodes.get(id);
|
|
1623
|
+
if (!existing) {
|
|
1624
|
+
// Check if updates contains a complete TreeNode (used for deletion prep)
|
|
1625
|
+
const isCompleteNode = updates.label !== undefined &&
|
|
1626
|
+
updates.y !== undefined &&
|
|
1627
|
+
updates.x !== undefined;
|
|
1628
|
+
if (isCompleteNode) {
|
|
1629
|
+
// Add the complete node to cache (used before deletion)
|
|
1630
|
+
const completeNode = { ...updates, id };
|
|
1631
|
+
this._nodes.set(id, completeNode);
|
|
1632
|
+
// Don't add a change operation - this is just caching for later operations
|
|
1633
|
+
}
|
|
1634
|
+
// Otherwise silently skip - normal for pagination
|
|
1635
|
+
return;
|
|
1636
|
+
}
|
|
1637
|
+
const updated = { ...existing, ...updates, id };
|
|
1638
|
+
this._nodes.set(id, updated);
|
|
1639
|
+
this._changes.push({
|
|
1640
|
+
type: ChangeOperationType.UPDATE,
|
|
1641
|
+
id,
|
|
1642
|
+
node: updated,
|
|
1643
|
+
originalNode: existing,
|
|
1644
|
+
});
|
|
1645
|
+
this.updateHasChanges();
|
|
1646
|
+
}
|
|
1647
|
+
/**
|
|
1648
|
+
* Save all pending changes.
|
|
1649
|
+
*/
|
|
1650
|
+
saveChanges() {
|
|
1651
|
+
if (this._changes.length === 0) {
|
|
1652
|
+
return of(new Map());
|
|
1653
|
+
}
|
|
1654
|
+
return this.persistChanges([...this._changes]).pipe(map((idMap) => {
|
|
1655
|
+
// update temporary IDs with permanent ones
|
|
1656
|
+
idMap.forEach((permanentId, tempId) => {
|
|
1657
|
+
const node = this._nodes.get(tempId);
|
|
1658
|
+
if (node) {
|
|
1659
|
+
this._nodes.delete(tempId);
|
|
1660
|
+
this._nodes.set(permanentId, { ...node, id: permanentId });
|
|
1661
|
+
}
|
|
1662
|
+
});
|
|
1663
|
+
// clear changes after successful save
|
|
1664
|
+
this._changes = [];
|
|
1665
|
+
this._removedNodes.clear();
|
|
1666
|
+
this.updateHasChanges();
|
|
1667
|
+
return idMap;
|
|
1668
|
+
}));
|
|
1669
|
+
}
|
|
1670
|
+
/**
|
|
1671
|
+
* Check if there are unsaved changes.
|
|
1672
|
+
*/
|
|
1673
|
+
hasChanges() {
|
|
1674
|
+
return this._changes.length > 0;
|
|
1675
|
+
}
|
|
1676
|
+
/**
|
|
1677
|
+
* Clear all pending changes.
|
|
1678
|
+
*/
|
|
1679
|
+
clearChanges() {
|
|
1680
|
+
this._changes = [];
|
|
1681
|
+
this._nodes.clear();
|
|
1682
|
+
this._removedNodes.clear();
|
|
1683
|
+
this.updateHasChanges();
|
|
1684
|
+
}
|
|
1685
|
+
/**
|
|
1686
|
+
* Get all pending changes.
|
|
1687
|
+
*/
|
|
1688
|
+
getChanges() {
|
|
1689
|
+
return [...this._changes];
|
|
1690
|
+
}
|
|
1691
|
+
/**
|
|
1692
|
+
* Get a node by ID, including local changes.
|
|
1693
|
+
*/
|
|
1694
|
+
getNode(id) {
|
|
1695
|
+
return this._nodes.get(id);
|
|
1696
|
+
}
|
|
1697
|
+
updateHasChanges() {
|
|
1698
|
+
this._hasChanges$.next(this._changes.length > 0);
|
|
1699
|
+
}
|
|
1037
1700
|
}
|
|
1038
1701
|
|
|
1039
1702
|
/*
|
|
@@ -1044,5 +1707,5 @@ class PagedTreeStore {
|
|
|
1044
1707
|
* Generated bundle index. Do not edit.
|
|
1045
1708
|
*/
|
|
1046
1709
|
|
|
1047
|
-
export { BrowserTreeNodeComponent, CompactPagerComponent, DEFAULT_PAGED_LIST_STORE_OPTIONS, LRUCache, PagedListStore, PagedTreeStore, RangeViewComponent };
|
|
1710
|
+
export { BrowserTreeNodeComponent, ChangeOperationType, CompactPagerComponent, DEFAULT_PAGED_LIST_STORE_OPTIONS, EditablePagedTreeStoreServiceBase, LRUCache, PagedListStore, PagedTreeStore, RangeViewComponent };
|
|
1048
1711
|
//# sourceMappingURL=myrmidon-paged-data-browsers.mjs.map
|