/*! * Copyright (c) 2023, 2025, Oracle and/or its affiliates. */ /** * @file * * Contains the base view definition for all our elements. */ const { dia, g } = joint; const Flags = { ...dia.ElementView.Flags, DISABLED: "DISABLED", READONLY: "READONLY" }; class ElementView extends dia.ElementView { preinitialize() { super.preinitialize( ...arguments ); this.presentationAttributes = dia.ElementView.addPresentationAttributes( { "clip": Flags.UPDATE, "disabled": Flags.DISABLED, "readOnly": Flags.READONLY } ); } initialize() { super.initialize( ...arguments ); const model = this.model; const typeId = model.prop( "typeId" ); const typeLower = model.get( "type" ).toLowerCase(); const [prefix, elementShape] = typeLower.split( "." ); let cls = model.prop( "cls" ); cls = cls ? ( Array.isArray( cls ) ? cls : cls.split( " " ) ) : []; // replace placeholders in the classNames, e.g. 'myclass-{0}-{1}' -> 'myclass-apex-rect' cls = cls.map( name => name.replaceAll( "{0}", prefix ).replaceAll( "{1}", elementShape ) ); const el = this.el; const classList = el.classList; classList.add( `${typeLower.replaceAll( ".", "-" )}-element` ); if ( cls.length ) { classList.add( ...cls ); } const filters = model.get( "filters" ); if ( filters?.length ) { el.setAttribute( "filter", filters.reduce( ( prev, curr ) => `${prev} url(#${curr})`, "" ) ); } el.dataset.typeId = typeId; } onMount() { this.notify( "cell:mount", this, ...arguments ); super.onMount( ...arguments ); } confirmUpdate() { let flags = super.confirmUpdate( ...arguments ); if ( this.hasFlag( flags, Flags.DISABLED ) ) { this.updateDisabledState(); flags = this.removeFlag( flags, Flags.DISABLED ); } if ( this.hasFlag( flags, Flags.READONLY ) ) { this.updateReadOnlyState(); flags = this.removeFlag( flags, Flags.READONLY ); } return flags; } render() { super.render( ...arguments ); this.updateClipping(); return this; } update() { super.update( ...arguments ); this.updateClipping(); } translate() { super.translate( ...arguments ); this.updateClipping(); } updateDisabledState() { const disabled = this.model.isDisabled(); const { el } = this; if ( disabled ) { el.classList.add( "disabled" ); } else { el.classList.remove( "disabled" ); } } updateReadOnlyState() { const readOnly = this.model.readOnly(); const { el } = this; if ( readOnly ) { el.classList.add( "readonly" ); } else { el.classList.remove( "readonly" ); } } // Unlike the linkview, we are going to call this in render/update/translate and not in confirmUpdate updateClipping() { const { model, el } = this; const clip = model.get( "clip" ); if ( clip ) { this.clipToParent(); } else if ( el.style.getPropertyValue( "clip-path" ) ) { el.style.removeProperty( "clip-path" ); } } clipToParent() { const { model } = this; const parent = model.getParentCell(); if ( !parent ) { return; } const bbox = model.getBBox(); // This doesn't have to be view's bbox, the model is always correct const parentBBox = parent.getBBox(); const top = parentBBox.y - bbox.y; const left = parentBBox.x - bbox.x; const right = parentBBox.x + parentBBox.width - bbox.x; const bottom = parentBBox.y + parentBBox.height - bbox.y; this.el.style.setProperty( "clip-path", `rect(${top}px ${right}px ${bottom}px ${left}px)` ); this.updateHighlighters(); } getChildEl( jointSelector ) { return this.el.querySelector( `[joint-selector="${jointSelector}"]` ); } // override to be able to pass the source of the newly created link (passing down side param) // and to return the newly created linkView dragLinkStart( evt, magnet, x, y, side ) { this.model.startBatch( "add-link" ); const linkView = this.addLinkFromMagnet( magnet, x, y, side ); // backwards compatibility events linkView.notifyPointerdown( evt, x, y ); linkView.eventData( evt, linkView.startArrowheadMove( "target", { whenNotAllowed: "remove" } ) ); this.eventData( evt, { linkView } ); return linkView; // FN } // override to be able to pass the source of the newly created link (passing down side param) addLinkFromMagnet( magnet, x, y, side ) { const paper = this.paper; const graph = paper.model; const link = paper.getDefaultLink( this, magnet ); link.set( { source: this.getLinkEnd( magnet, x, y, link, "source", side ), target: { x: x, y: y } } ).addTo( graph, { async: false, ui: true } ); return link.findView( paper ); } // getLinkEnd: passes ...args - no need to do anything to pass the side // override to be able to pass the source of the newly created link (passing down side param) customizeLinkEnd( end, magnet, x, y, link, endType, side ) { const { paper } = this; const { connectionStrategy } = paper.options; if ( typeof connectionStrategy === "function" ) { const strategy = connectionStrategy.call( paper, end, this, magnet, new g.Point( x, y ), link, endType, paper, side ); if ( strategy ) { return strategy; } } return end; } } export default ElementView;