feat(editor): highlight selected connection endpoints
This commit is contained in:
parent
421ef23518
commit
c8085dad5a
|
|
@ -4,17 +4,30 @@
|
|||
|
||||
import { Injectable, signal } from '@angular/core';
|
||||
|
||||
export type SelectedConnection = {
|
||||
id: string;
|
||||
sourceId: string;
|
||||
sourceOutput: string;
|
||||
targetId: string;
|
||||
targetInput: string;
|
||||
};
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class GraphSelectionService {
|
||||
readonly selectedConnectionId = signal<string | null>(null);
|
||||
/** The two endpoints let their socket components light up with the selected wire. */
|
||||
readonly selectedConnection = signal<SelectedConnection | null>(null);
|
||||
readonly deleteConnectionRequestTick = signal(0);
|
||||
|
||||
selectConnection(connectionId: string | null | undefined) {
|
||||
this.selectedConnectionId.set(connectionId ? String(connectionId) : null);
|
||||
selectConnection(connectionId: string | null | undefined, endpoints?: Omit<SelectedConnection, 'id'>) {
|
||||
const id = connectionId ? String(connectionId) : null;
|
||||
this.selectedConnectionId.set(id);
|
||||
this.selectedConnection.set(id && endpoints ? { id, ...endpoints } : null);
|
||||
}
|
||||
|
||||
clearConnectionSelection() {
|
||||
this.selectedConnectionId.set(null);
|
||||
this.selectedConnection.set(null);
|
||||
}
|
||||
|
||||
requestDeleteSelectedConnection() {
|
||||
|
|
|
|||
|
|
@ -20,7 +20,9 @@ import { BiasComparisonViewStateService } from '@services/bias/bias-comparison-v
|
|||
selector: 'app-custom-connection',
|
||||
standalone: true,
|
||||
template: `
|
||||
<svg data-testid="connection" [attr.data-connection-id]="connectionId" [attr.data-loop-back]="isLoopBack || null">
|
||||
<svg data-testid="connection" [attr.data-connection-id]="connectionId" [attr.data-loop-back]="isLoopBack || null"
|
||||
[attr.data-connection-kind]="connectionKind" [attr.data-selected]="isSelected || null"
|
||||
[style.z-index]="isSelected ? 100 : 1">
|
||||
<path
|
||||
[class.connection-readonly]="isReadonly"
|
||||
[attr.d]="drawnPath"
|
||||
|
|
@ -214,6 +216,10 @@ export class CustomConnectionComponent {
|
|||
return String(this.data?.id ?? '');
|
||||
}
|
||||
|
||||
get connectionKind(): 'data' | 'dependency' | 'resource' {
|
||||
return this.isResourceLink ? 'resource' : this.isDependencyConnection ? 'dependency' : 'data';
|
||||
}
|
||||
|
||||
private get loopData(): LoopAwareConnection | undefined {
|
||||
return this.data as LoopAwareConnection | undefined;
|
||||
}
|
||||
|
|
@ -294,7 +300,9 @@ export class CustomConnectionComponent {
|
|||
}
|
||||
|
||||
get strokeColor(): string {
|
||||
if (this.isSelected) return '#f97316';
|
||||
if (this.isSelected) {
|
||||
return this.isResourceLink ? '#0f9f8d' : this.isDependencyConnection ? '#4f46e5' : '#f97316';
|
||||
}
|
||||
if (this.isBiasRoutingChange) return '#b45309';
|
||||
if (this.isLoopBack) return '#7c3aed';
|
||||
if (this.isResourceLink) return '#0f766e';
|
||||
|
|
@ -302,14 +310,16 @@ export class CustomConnectionComponent {
|
|||
}
|
||||
|
||||
get strokeWidth(): number {
|
||||
if (this.isSelected) return this.isDependencyConnection || this.isResourceLink ? 3.25 : 6;
|
||||
if (this.isSelected) return this.isDependencyConnection || this.isResourceLink ? 4 : 6;
|
||||
if (this.isBiasRoutingChange) return 6;
|
||||
return this.isDependencyConnection || this.isResourceLink ? 2.25 : 5;
|
||||
}
|
||||
|
||||
get strokeDasharray(): string | null {
|
||||
if (this.isLoopBack) return '12 7';
|
||||
if (this.isResourceLink) return '2 6';
|
||||
// A resource is a capability handle, not an ordering dependency: its teal segmented wire is
|
||||
// intentionally unlike the thin grey dashed dependency wire.
|
||||
if (this.isResourceLink) return '14 5 2 5';
|
||||
return this.isDependencyConnection ? '6 6' : null;
|
||||
}
|
||||
|
||||
|
|
@ -329,7 +339,12 @@ export class CustomConnectionComponent {
|
|||
if (this.isReadonly) return;
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
this.graphSelection.selectConnection(this.connectionId);
|
||||
this.graphSelection.selectConnection(this.connectionId, {
|
||||
sourceId: String(this.data?.source ?? ''),
|
||||
sourceOutput: String(this.data?.sourceOutput ?? ''),
|
||||
targetId: String(this.data?.target ?? ''),
|
||||
targetInput: String(this.data?.targetInput ?? '')
|
||||
});
|
||||
}
|
||||
|
||||
deleteConnection(event: Event) {
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import {
|
|||
Input,
|
||||
OnDestroy
|
||||
} from "@angular/core";
|
||||
import { GraphSelectionService } from '@services/graph-selection/graph-selection';
|
||||
|
||||
@Component({
|
||||
template: ``,
|
||||
|
|
@ -22,6 +23,7 @@ export class CustomSocket implements AfterViewInit, OnDestroy {
|
|||
@Input() emit!: any;
|
||||
@Input() rendered!: any;
|
||||
private readonly element = inject<ElementRef<HTMLElement>>(ElementRef);
|
||||
private readonly graphSelection = inject(GraphSelectionService);
|
||||
private resizeObserver: ResizeObserver | null = null;
|
||||
private renderFrame: number | null = null;
|
||||
private side: "input" | "output" = "input";
|
||||
|
|
@ -33,6 +35,7 @@ export class CustomSocket implements AfterViewInit, OnDestroy {
|
|||
|
||||
@HostBinding("style.width")
|
||||
get width() {
|
||||
if (this.isSelectedEndpoint) return this.kind === "resource" ? "11px" : "12px";
|
||||
return this.kind === "resource" ? "7px" : "8px";
|
||||
}
|
||||
|
||||
|
|
@ -45,7 +48,13 @@ export class CustomSocket implements AfterViewInit, OnDestroy {
|
|||
@HostBinding("style.borderRadius") get borderRadius() {
|
||||
return this.kind === "resource" ? "4px" : "999px";
|
||||
}
|
||||
@HostBinding("style.border") border = "1px solid rgba(255,255,255,0.9)";
|
||||
@HostBinding("style.border") get border() {
|
||||
return this.isSelectedEndpoint ? "2px solid #fff" : "1px solid rgba(255,255,255,0.9)";
|
||||
}
|
||||
|
||||
@HostBinding('attr.data-connection-endpoint-selected') get selectedEndpointAttribute() {
|
||||
return this.isSelectedEndpoint || null;
|
||||
}
|
||||
@HostBinding("style.cursor") get cursor() {
|
||||
return this.data?.__readonly === true ? "default" : "crosshair";
|
||||
}
|
||||
|
|
@ -56,6 +65,13 @@ export class CustomSocket implements AfterViewInit, OnDestroy {
|
|||
|
||||
@HostBinding("style.background")
|
||||
get bg() {
|
||||
if (this.isSelectedEndpoint) {
|
||||
return this.kind === 'resource'
|
||||
? 'linear-gradient(145deg, #5eead4 0%, #0f9f8d 100%)'
|
||||
: this.kind === 'dependency'
|
||||
? 'linear-gradient(145deg, #a5b4fc 0%, #4f46e5 100%)'
|
||||
: 'linear-gradient(145deg, #fdba74 0%, #ea580c 100%)';
|
||||
}
|
||||
if (this.kind === "resource") {
|
||||
return "linear-gradient(180deg, #5eead4 0%, #0f766e 100%)";
|
||||
}
|
||||
|
|
@ -71,6 +87,12 @@ export class CustomSocket implements AfterViewInit, OnDestroy {
|
|||
|
||||
@HostBinding("style.boxShadow")
|
||||
get sh() {
|
||||
if (this.isSelectedEndpoint) {
|
||||
const color = this.kind === 'resource' ? 'rgba(15,159,141,0.78)'
|
||||
: this.kind === 'dependency' ? 'rgba(79,70,229,0.72)'
|
||||
: 'rgba(234,88,12,0.72)';
|
||||
return `0 0 0 4px ${color}, 0 5px 14px ${color}`;
|
||||
}
|
||||
if (this.kind === "resource") {
|
||||
return "0 1px 4px rgba(15,118,110,0.32), 0 0 0 1px rgba(13,148,136,0.42)";
|
||||
}
|
||||
|
|
@ -92,6 +114,18 @@ export class CustomSocket implements AfterViewInit, OnDestroy {
|
|||
return kind === "dependency" || kind === "resource" ? kind : "data";
|
||||
}
|
||||
|
||||
/** True for either end of the selected data, dependency or resource connection. */
|
||||
get isSelectedEndpoint(): boolean {
|
||||
const selected = this.graphSelection.selectedConnection();
|
||||
if (!selected) return false;
|
||||
const nodeId = this.data?.nodeId ?? this.data?.payload?.nodeId;
|
||||
const key = this.data?.key ?? this.data?.payload?.key;
|
||||
if (nodeId == null || key == null) return false;
|
||||
return this.side === 'output'
|
||||
? String(nodeId) === selected.sourceId && String(key) === selected.sourceOutput
|
||||
: String(nodeId) === selected.targetId && String(key) === selected.targetInput;
|
||||
}
|
||||
|
||||
ngOnChanges(): void {
|
||||
this.side = this.resolveSocketSide();
|
||||
this.kind = this.resolveSocketKind();
|
||||
|
|
|
|||
Loading…
Reference in New Issue