diff --git a/apps/frontend/src/app/model/config.ts b/apps/frontend/src/app/model/config.ts
index 9eaf5f6..24bc67c 100644
--- a/apps/frontend/src/app/model/config.ts
+++ b/apps/frontend/src/app/model/config.ts
@@ -1,6 +1,7 @@
export interface Config {
groups?: string[];
scopes: string[];
+ focus?: string;
}
export const initConfig: Config = {
diff --git a/apps/frontend/src/app/shell/filter-tree/filter-tree.component.html b/apps/frontend/src/app/shell/filter-tree/filter-tree.component.html
index d72cf01..d0fa394 100644
--- a/apps/frontend/src/app/shell/filter-tree/filter-tree.component.html
+++ b/apps/frontend/src/app/shell/filter-tree/filter-tree.component.html
@@ -8,6 +8,7 @@
{{ node.name }}
@@ -22,10 +23,32 @@
{{ tree.isExpanded(node) ? 'expand_more' : 'chevron_right' }}
+
{{ node.name }}
+
+ @if (hasFocus(node)) {
+
+ } @else {
+
+ }
+
+
diff --git a/apps/frontend/src/app/shell/filter-tree/filter-tree.component.ts b/apps/frontend/src/app/shell/filter-tree/filter-tree.component.ts
index 121c5d5..8cf797e 100644
--- a/apps/frontend/src/app/shell/filter-tree/filter-tree.component.ts
+++ b/apps/frontend/src/app/shell/filter-tree/filter-tree.component.ts
@@ -1,11 +1,19 @@
+import { CdkMenuModule } from '@angular/cdk/menu';
import { CdkTree } from '@angular/cdk/tree';
-import { Component, inject, OnInit, viewChild } from '@angular/core';
+import {
+ ChangeDetectorRef,
+ Component,
+ inject,
+ OnInit,
+ viewChild,
+} from '@angular/core';
import { MatButtonModule } from '@angular/material/button';
import {
MatCheckboxChange,
MatCheckboxModule,
} from '@angular/material/checkbox';
import { MatIconModule } from '@angular/material/icon';
+import { MatMenuModule, MatMenuTrigger } from '@angular/material/menu';
import { MatTreeModule, MatTreeNestedDataSource } from '@angular/material/tree';
import { combineLatest, of } from 'rxjs';
@@ -20,7 +28,14 @@ const MIN_OPEN_LEVEL = 2;
@Component({
selector: 'app-filter-tree',
standalone: true,
- imports: [MatTreeModule, MatIconModule, MatButtonModule, MatCheckboxModule],
+ imports: [
+ MatTreeModule,
+ MatIconModule,
+ MatButtonModule,
+ MatCheckboxModule,
+ MatMenuModule,
+ CdkMenuModule,
+ ],
templateUrl: './filter-tree.component.html',
styleUrl: './filter-tree.component.css',
})
@@ -28,6 +43,7 @@ export class FilterTreeComponent implements OnInit {
private folderService = inject(FolderService);
private configService = inject(ConfigService);
private eventService = inject(EventService);
+ private cdr = inject(ChangeDetectorRef);
tree = viewChild.required>(CdkTree);
dataSource = new MatTreeNestedDataSource();
@@ -36,9 +52,8 @@ export class FilterTreeComponent implements OnInit {
selected = new Set();
folders: Folder[] = [];
- childrenAccessor = (folder: Folder) => of(folder.folders);
- hasChild = (_: number, node: Folder) =>
- !!node.folders && node.folders.length > 0;
+ childrenAccessor = ({ folders }: Folder) => of(folders);
+ hasChild = (_: number, { folders }: Folder) => !!folders?.length;
ngOnInit(): void {
const folders$ = this.folderService.load();
@@ -46,17 +61,29 @@ export class FilterTreeComponent implements OnInit {
combineLatest({
folders: folders$,
config: config$,
- }).subscribe((result) => {
- this.dataSource.data = result.folders;
- this.config = result.config;
- this.folders = result.folders;
+ }).subscribe(({ folders, config }) => {
+ const focusFolder = this.findFolder(folders, config.focus);
+ this.dataSource.data = focusFolder ? [focusFolder] : folders;
+ this.config = config;
+ this.folders = folders;
this.selected.clear();
this.config.scopes.forEach((scope) => this.selected.add(scope));
- this.expandChecked(result.folders);
+ this.expandChecked(this.dataSource.data);
removeFocus();
});
}
+ private findFolder(folders: Folder[], focus?: string): Folder | undefined {
+ if (!focus || !folders?.length) return undefined;
+ return (
+ folders.find((folder) => folder && folder.path === focus) ??
+ this.findFolder(
+ folders.flatMap(({ folders }) => folders),
+ focus
+ )
+ );
+ }
+
expandChecked(folders: Folder[], depth = 0): boolean {
let open = depth <= MIN_OPEN_LEVEL;
for (const folder of folders) {
@@ -71,8 +98,40 @@ export class FilterTreeComponent implements OnInit {
return open;
}
- isChecked(folder: Folder): boolean {
- return this.selected.has(folder.path);
+ noContextMenu(event: MouseEvent) {
+ event.preventDefault();
+ }
+
+ onContextMenu(event: MouseEvent, trigger: MatMenuTrigger) {
+ event.preventDefault();
+ trigger.openMenu();
+ }
+
+ selectChildren(folder: Folder) {
+ this.deselectParents(folder);
+ this.selected.delete(folder.path);
+ for (const child of folder.folders) {
+ this.selected.add(child.path);
+ }
+ this.tree().expand(folder);
+ this.updateConfig();
+ }
+
+ focusTree(folder?: Folder) {
+ this.dataSource = new MatTreeNestedDataSource();
+ this.dataSource.data = folder ? [{ ...folder }] : this.folders;
+ this.config.focus = folder?.path;
+ this.expandChecked(this.dataSource.data);
+ this.tree().renderNodeChanges(this.dataSource.data);
+ this.updateConfig();
+ }
+
+ isChecked({ path }: Folder): boolean {
+ return this.selected.has(path);
+ }
+
+ hasFocus({ path }: Folder): boolean {
+ return path === this.config.focus;
}
onCheckChange(folder: Folder, $event: MatCheckboxChange) {
@@ -84,7 +143,10 @@ export class FilterTreeComponent implements OnInit {
this.deselectParents(folder);
this.deselectSubtree(folder.folders);
+ this.updateConfig();
+ }
+ private updateConfig() {
this.config.scopes = [...this.selected];
this.config.groups = this.findParents();
diff --git a/apps/frontend/src/app/ui/graph/graph.ts b/apps/frontend/src/app/ui/graph/graph.ts
index 56afa3b..78050b4 100644
--- a/apps/frontend/src/app/ui/graph/graph.ts
+++ b/apps/frontend/src/app/ui/graph/graph.ts
@@ -75,7 +75,7 @@ function createGraph(container: HTMLElement, graph: Graph): cytoscape.Core {
'text-valign': 'center',
'text-halign': 'center',
height: '20px',
- width: 'label',
+ width: '100px',
padding: '10px',
'background-color': '#60a3bc',
'border-color': '#1e272e',
@@ -118,7 +118,6 @@ function createGraph(container: HTMLElement, graph: Graph): cytoscape.Core {
nodes: graph.nodes,
edges: graph.edges,
},
- wheelSensitivity: 0.2,
zoomingEnabled: true,
userZoomingEnabled: true,
panningEnabled: true,