retejs / rete

JavaScript framework for visual programming
https://retejs.org
MIT License
10.17k stars 653 forks source link

Angular: Add control / custom element in a connection. #663

Closed mianfrigo closed 1 year ago

mianfrigo commented 1 year ago

im currently trying to add menu that will be display in the connection between two node when the user hover the connection. Screenshot from 2023-10-02 12-09-41 Every time i try to add the component inside of the connection template i get this errorNG3003: Import cycles would need to be created to compile this component. is there a way do this...?

Ni55aN commented 1 year ago

can you share some code or a reproducible example on Codesandbox?

mianfrigo commented 1 year ago

Ok i found the way to add the component to the custom connection, i follow the example labeled connections, but for reason i can no center the component. here is what i got so far:

custom-connection.html

<svg data-testid="connection" #svg>
  <path [ngClass]="{ 'source-connected': !!data.source, 'target-connected': !!data.target }" [attr.d]="path" />
</svg>
<div class="connection-menu" #menu>
  <exb-node-menu
    [insertBetween]="true"
    [connectionId]="data.id"
    [editor]="data.editor"
    [area]="data.area"></exb-node-menu>
</div>

custom-connection.ts

export class CustomConnectionComponent implements AfterViewInit {
  @Input() data!: CustomConnection;
  @Input() start: any;
  @Input() end: any;
  @Input() path!: string;
  @ViewChild('svg') connection!: ElementRef;
  @ViewChild('menu') menu!: ElementRef;

  constructor() {}

  ngAfterViewInit(): void {
    timer(0).subscribe(() => {
      this.positionMenu();
    });
  }

  positionMenu(): void {
    const path = this.connection.nativeElement.firstChild;
    const pathTotalLength = path.getTotalLength();
    console.log(pathTotalLength);
    const point = path.getPointAtLength(pathTotalLength / 2);
    this.menu.nativeElement.style.transform = `translate(${point.x}px, ${point.y}px)`;
  }
}

custom-connection.scss

svg {
  overflow: visible;
  position: absolute;
  pointer-events: none;
  width: 9999px;
  height: 9999px;
  path {
    fill: none;
    stroke-width: 1px;
    stroke: var(--moderate-border-color);
    pointer-events: auto;

    &.source-connected:not(.target-connected) {
      stroke: var(--purple-border-color);
      stroke-dasharray: 2 2;
    }
  }
}

.connection-menu {
  position: absolute;
  top: 0;
  left: 0;
}

this the current result i have:

Screenshot from 2023-10-04 18-02-54

and at the moment of drag the node the button element does not stick to the svg path element.

Ni55aN commented 1 year ago

moment of drag the node the button element does not stick to the svg path element

doesn't stick at all or isn't centered in the middle of the path? Based on the image, I see that the top left border of the block almost aligned with the center of the path. It means that this block should have translate(-50%, -50%)

Ni55aN commented 1 year ago

btw, this gap can be fixed in socketPositionWatcher

image

mianfrigo commented 1 year ago

moment of drag the node the button element does not stick to the svg path element

doesn't stick at all or isn't centered in the middle of the path? Based on the image, I see that the top left border of the block almost aligned with the center of the path. It means that this block should have translate(-50%, -50%)

yes does not stick at all. I try trasnlate(-50%, -50%) on the div the wrapper the angular component but does not change anything.

This does not change anything they block stay in the same position.

<div class="connection-menu" #menu>
  <exb-node-menu
    [insertBetween]="true"
    [connectionId]="data.id"
    [editor]="data.editor"
    [area]="data.area"></exb-node-menu>
</div>

.connection-menu {
  position: absolute;
  top: 0;
  left: 0;
  transform: translate(-50%, -50%);
}
Ni55aN commented 1 year ago

I guess the issue is: your positionMenu() isn't called on path update. It should be called inside ngOnChanges (when path is changed)

Ni55aN commented 1 year ago

If you have access to source code of Pro examples, you can refer to Undirected example (it has components for Angular). It's Connection component has getter which returns the middle point of the path (consequently, it's called on every change detection)

Ni55aN commented 1 year ago

So, in your case positionMenu() can be turned into getter for <div class="connection-menu" [style]="{ left: point.x, top ....

mianfrigo commented 1 year ago

Thanks so much that works like a charm 🥳 .

I have ones again the initial issue, as you can see i have an angular component inside of the custom connection component, on build i have this error

The component 'NodeMenuComponent' is used in the template but importing it would create a cycle:
cwb-ng/libs/workflow-editor/src/lib/connection/custom-connection.component.ts -> 
cwb-ng/libs/workflow-editor/src/lib/components/node-menu/node-menu.component.ts -> 
cwb-ng/libs/workflow-editor/src/lib/editor-creator.ts -> 
cwb-ng/libs/workflow-editor/src/lib/connection/custom-connection.component.ts

I need to mention that the rete editor is an angular library that is been imported in to another project. I dont know if this is relevant.

Ni55aN commented 1 year ago

Does custom-connection.component.ts import something from editor-creator.ts?

mianfrigo commented 1 year ago

Oh yes i was importing the types from editor-creator.ts

export type CustomConnection = ClassicPreset.Connection<InputNode, CustomNode>;

export type Schemes = GetSchemes<CustomNode, CustomConnection>;
export type AreaExtra = AngularArea2D<Schemes>;

@Injectable()
export class EditorCreatorService {
  async createEditor(
    container: HTMLElement,
    injector: Injector,
  ): Promise<{ editor: NodeEditor<Schemes>; area: AreaPlugin<Schemes, AreaExtra>; destroy: () => void }> {
    const editor = new NodeEditor<Schemes>();
    const area = new AreaPlugin<Schemes, AreaExtra>(container);
    const connection = new ConnectionPlugin<Schemes, AreaExtra>();
    const angularRender = new AngularPlugin<Schemes, AreaExtra>({ injector });

i extracted them and now is all good.

mianfrigo commented 1 year ago

Thanks so much for your help man.