diff --git a/dist/css/ueblueprint-style.css b/dist/css/ueblueprint-style.css
index cf79568..d9c08c1 100755
--- a/dist/css/ueblueprint-style.css
+++ b/dist/css/ueblueprint-style.css
@@ -1 +1 @@
-@font-face{font-family:"Roboto";font-style:light;src:url("../font/roboto-light.woff2") format("woff2"),url("../font/roboto-light.woff") format("woff")}@font-face{font-family:"Roboto";font-style:regular;src:url("../font/roboto-regular.woff2") format("woff2"),url("../font/roboto-regular.woff") format("woff")}ueb-blueprint{display:block;position:relative;font-family:Roboto,Noto,Oxygen,Ubuntu,"Open Sans","Helvetica Neue",sans-serif;font-size:var(--ueb-font-size);user-select:none}.ueb-viewport-header{display:flex;position:absolute;top:0;right:0;left:0;height:1.5em;background:rgba(0,0,0,.5);z-index:1}.ueb-viewport-zoom{color:#4d4d4db7}.ueb-viewport-body{position:relative;height:30rem;overflow:hidden;scrollbar-width:none}ueb-blueprint[data-focused=true] .ueb-viewport-body{overflow:scroll}.ueb-grid{--ueb-grid-line-actual-width: calc(var(--ueb-grid-line-width) / var(--ueb-scale));position:absolute;min-width:100%;min-height:100%;width:calc((100% + var(--ueb-additional-x)*1px)/var(--ueb-scale));height:calc((100% + var(--ueb-additional-y)*1px)/var(--ueb-scale));background-color:#262626;background-image:linear-gradient(var(--ueb-grid-axis-line-color), var(--ueb-grid-axis-line-color)),linear-gradient(var(--ueb-grid-axis-line-color), var(--ueb-grid-axis-line-color)),linear-gradient(to right, var(--ueb-grid-set-line-color), var(--ueb-grid-set-line-color) var(--ueb-grid-line-actual-width), transparent var(--ueb-grid-line-actual-width), transparent),linear-gradient(to bottom, var(--ueb-grid-set-line-color), var(--ueb-grid-set-line-color) var(--ueb-grid-line-actual-width), transparent var(--ueb-grid-line-actual-width), transparent),linear-gradient(to right, var(--ueb-grid-line-color), var(--ueb-grid-line-color) var(--ueb-grid-line-actual-width), transparent var(--ueb-grid-line-actual-width), transparent),linear-gradient(to bottom, var(--ueb-grid-line-color), var(--ueb-grid-line-color) var(--ueb-grid-line-actual-width), transparent var(--ueb-grid-line-actual-width), transparent);background-size:100% var(--ueb-grid-line-actual-width),var(--ueb-grid-line-actual-width) 100%,calc(var(--ueb-grid-set)*var(--ueb-grid-actual-size)) calc(var(--ueb-grid-set)*var(--ueb-grid-actual-size)),calc(var(--ueb-grid-set)*var(--ueb-grid-actual-size)) calc(var(--ueb-grid-set)*var(--ueb-grid-actual-size)),var(--ueb-grid-actual-size) var(--ueb-grid-actual-size),var(--ueb-grid-actual-size) var(--ueb-grid-actual-size);background-position:calc(var(--ueb-translate-x)*1px) calc(var(--ueb-translate-y)*1px);background-repeat:repeat-x,repeat-y,repeat,repeat,repeat,repeat;transform:scale(var(--ueb-scale), var(--ueb-scale));transform-origin:0 0;overflow:hidden}ueb-blueprint[data-drag-scrolling=true] .ueb-grid{cursor:grabbing}.ueb-zoom--.ueb,.ueb{--ueb-scale: 1;--ueb-grid-actual-size: var(--ueb-grid-size)}.ueb-zoom--1.ueb{--ueb-scale: 0.875}.ueb-zoom--2.ueb{--ueb-scale: 0.75}.ueb-zoom--3.ueb{--ueb-scale: 0.675}.ueb-zoom--4.ueb{--ueb-scale: 0.5;--ueb-grid-actual-size: calc(var(--ueb-grid-size) * 2)}.ueb-zoom--5.ueb{--ueb-scale: 0.375;--ueb-grid-actual-size: calc(var(--ueb-grid-size) * 2)}.ueb-zoom--6.ueb{--ueb-scale: 0.333333;--ueb-grid-actual-size: calc(var(--ueb-grid-size) * 3)}.ueb-zoom--7.ueb{--ueb-scale: 0.3;--ueb-grid-actual-size: calc(var(--ueb-grid-size) * 3)}.ueb-zoom--8.ueb{--ueb-scale: 0.266666;--ueb-grid-actual-size: calc(var(--ueb-grid-size) * 3)}.ueb-zoom--9.ueb{--ueb-scale: 0.233333;--ueb-grid-actual-size: calc(var(--ueb-grid-size) * 3)}.ueb-zoom--10.ueb{--ueb-scale: 0.2;--ueb-grid-actual-size: calc(var(--ueb-grid-size) * 3)}.ueb-zoom--11.ueb{--ueb-scale: 0.166666;--ueb-grid-actual-size: calc(var(--ueb-grid-size) * 6)}.ueb-zoom--12.ueb{--ueb-scale: 0.133333;--ueb-grid-actual-size: calc(var(--ueb-grid-size) * 6)}.ueb-grid-content{position:relative;width:0;height:0;transform:translateX(calc(var(--ueb-translate-x) * 1px)) translateY(calc(var(--ueb-translate-y) * 1px))}ueb-node{display:block;position:absolute;transform:translateX(calc(var(--ueb-position-x) * 1px)) translateY(calc(var(--ueb-position-y) * 1px));border-radius:var(--ueb-node-radius);box-shadow:0 0 1px 0 #000,1px 4px 6px 0 rgba(0,0,0,.3);will-change:transform}ueb-blueprint[data-drag-scrolling=false][data-selecting=false] ueb-node{cursor:move}.ueb-node-border{margin:-3px;padding:3px;border-radius:calc(var(--ueb-node-radius)*1.4)}.ueb-selected{z-index:1}.ueb-selected>.ueb-node-border{background-image:linear-gradient(to right, #f1b000 0%, #f1b000 100%),linear-gradient(to bottom, #f1b000 0%, #cc6700 100%),linear-gradient(to right, #cc6700 0%, #cc6700 100%),linear-gradient(to bottom, #f1b000 0%, #cc6700 100%);background-size:100% 7px,7px 100%,100% 7px,7px 100%;background-position:top,right,bottom,left;background-repeat:repeat-x,repeat-y,repeat-x,repeat-y;outline:3px solid #cc6700;outline-offset:-6px}.ueb-node-content{position:relative;padding:1px;box-shadow:inset 0 0 2px 0 #000;border-radius:var(--ueb-node-radius);background:rgba(0,0,0,.7);overflow:hidden}.ueb-node-header{padding:.2em .7em;box-shadow:inset 0 1px 2px 0 #313631,inset 0 2px 0 0 #92c381;border-radius:var(--ueb-node-radius) var(--ueb-node-radius) 0 0;background:linear-gradient(170deg, #5f815a 0%, #5f815a 50%, transparent 100%);color:silver;font-weight:600;white-space:nowrap}.ueb-node-name{background:radial-gradient(closest-side, rgba(0, 0, 0, 0.5) 0%, transparent 90%);margin:-0.1em -1.6em;padding:.1em 1.6em}.ueb-node-body{display:flex;padding:6px 0;color:#fff;font-weight:100;white-space:nowrap}.ueb-node-inputs{margin-right:auto;padding-left:8px}.ueb-node-outputs{padding-right:8px}ueb-pin{display:block;padding:1px 2px}ueb-blueprint[data-drag-scrolling=false] .ueb-grid{cursor:default}ueb-blueprint[data-drag-scrolling=false][data-selecting=false] ueb-node ueb-pin:hover{background:var(--ueb-node-value-background);cursor:crosshair}.ueb-node-value-icon{display:inline-block;position:relative;width:.85em;height:.85em;vertical-align:baseline;margin:0 .4em -1px .1em}.ueb-node-value-icon::before{content:"";display:block;position:absolute;top:0;right:0;bottom:0;left:0;border:2px solid var(--ueb-node-value-color);border-radius:50%}.ueb-node-value-fill::before{background:var(--ueb-node-value-color)}.ueb-node-value-icon::after{content:"";display:block;position:absolute;top:calc(50% - .3em);left:calc(100% + 1px);width:0;height:0;border-top:.3em solid transparent;border-bottom:.3em solid transparent;border-left:.3em solid var(--ueb-node-value-color)}.ueb-positioned,ueb-blueprint[data-selecting=true] ueb-selector{--ueb-computed-min-x: min(var(--ueb-from-x), var(--ueb-to-x));--ueb-computed-max-x: max(var(--ueb-from-x), var(--ueb-to-x));--ueb-computed-min-y: min(var(--ueb-from-y), var(--ueb-to-y));--ueb-computed-max-y: max(var(--ueb-from-y), var(--ueb-to-y));--ueb-computed-width: max(var(--ueb-from-x) - var(--ueb-to-x), var(--ueb-to-x) - var(--ueb-from-x));--ueb-computed-height: max(var(--ueb-from-y) - var(--ueb-to-y), var(--ueb-to-y) - var(--ueb-from-y));position:absolute;top:calc(var(--ueb-computed-min-y)*1px);left:calc(var(--ueb-computed-min-x)*1px);width:calc(var(--ueb-computed-width)*1px);height:calc(var(--ueb-computed-height)*1px)}ueb-selector{display:block;position:absolute;visibility:hidden;top:0;left:0;width:0;height:0;background-image:repeating-linear-gradient(90deg, transparent, transparent calc(1px / var(--ueb-scale)), white calc(2px / var(--ueb-scale)), white calc(7px / var(--ueb-scale)), transparent calc(7px / var(--ueb-scale)), transparent calc(11px / var(--ueb-scale))),repeating-linear-gradient(90deg, black, black calc(8px / var(--ueb-scale)), transparent calc(9px / var(--ueb-scale)), transparent calc(11px / var(--ueb-scale))),repeating-linear-gradient(90deg, transparent, transparent calc(1px / var(--ueb-scale)), white calc(2px / var(--ueb-scale)), white calc(7px / var(--ueb-scale)), transparent calc(7px / var(--ueb-scale)), transparent calc(11px / var(--ueb-scale))),repeating-linear-gradient(90deg, black, black calc(8px / var(--ueb-scale)), transparent calc(9px / var(--ueb-scale)), transparent calc(11px / var(--ueb-scale))),repeating-linear-gradient(180deg, transparent, transparent calc(1px / var(--ueb-scale)), white calc(1px / var(--ueb-scale)), white calc(7px / var(--ueb-scale)), transparent calc(7px / var(--ueb-scale)), transparent calc(11px / var(--ueb-scale))),repeating-linear-gradient(180deg, black, black calc(8px / var(--ueb-scale)), transparent calc(9px / var(--ueb-scale)), transparent calc(11px / var(--ueb-scale))),repeating-linear-gradient(0deg, transparent, transparent calc(1px / var(--ueb-scale)), white calc(2px / var(--ueb-scale)), white calc(7px / var(--ueb-scale)), transparent calc(7px / var(--ueb-scale)), transparent calc(11px / var(--ueb-scale))),repeating-linear-gradient(0deg, black, black calc(8px / var(--ueb-scale)), transparent calc(9px / var(--ueb-scale)), transparent calc(11px / var(--ueb-scale)));background-size:100% calc(1px/var(--ueb-scale)),100% calc(3px/var(--ueb-scale)),100% calc(1px/var(--ueb-scale)),100% calc(3px/var(--ueb-scale)),calc(1px/var(--ueb-scale)) 100%,calc(3px/var(--ueb-scale)) 100%,calc(1px/var(--ueb-scale)) 100%,calc(3px/var(--ueb-scale)) 100%;background-position:0 calc(1px/var(--ueb-scale)),0 0,0 calc(100% - 1px/var(--ueb-scale)),0 100%,calc(1px/var(--ueb-scale)) 0,0 0,calc(100% - 1px/var(--ueb-scale)) 0,100% 0;background-repeat:no-repeat}ueb-blueprint[data-selecting=true] ueb-selector{visibility:visible}ueb-selector>*{visibility:visible}ueb-link{--ueb-computed-width-px: calc(var(--ueb-computed-width) * 1px);display:block;min-width:calc(var(--ueb-link-min-width)*1px);border:1px solid red}ueb-link svg{--ueb-output-invert: calc(2 * var(--ueb-from-input) - 1);position:absolute;top:0;left:0;width:100%;height:100%;transform:scaleY(clamp(-1, (var(--ueb-to-y) - var(--ueb-from-y)) * var(--ueb-output-invert), 1));overflow:visible}ueb-link svg path{--ueb-width-below-threshold: clamp( /* min */ 0, /* input */ calc(var(--ueb-width-threshold) - (var(--ueb-to-x) - var(--ueb-from-x))), /* max */ 1);transform:skewX(calc(var(--ueb-width-below-threshold) * var(--ueb-link-skew) * -1rad));transform-origin:0 0;stroke-width:2;transition:stroke-width .5s}ueb-link svg path:hover{stroke-width:5}ueb-link-message{display:block;position:absolute;left:calc(var(--ueb-start-percentage) + 15px);bottom:-42px;border:1px solid #000;padding:4px 8px;border-radius:2px;background:linear-gradient(to bottom, #2a2a2a 0, #151515 50%, #2a2a2a 100%);color:var(--ueb-node-value-dim-color);white-space:nowrap}/*# sourceMappingURL=ueblueprint-style.css.map */
+@font-face{font-family:"Roboto";font-style:light;src:url("../font/roboto-light.woff2") format("woff2"),url("../font/roboto-light.woff") format("woff")}@font-face{font-family:"Roboto";font-style:regular;src:url("../font/roboto-regular.woff2") format("woff2"),url("../font/roboto-regular.woff") format("woff")}ueb-blueprint{display:block;position:relative;font-family:Roboto,Noto,Oxygen,Ubuntu,"Open Sans","Helvetica Neue",sans-serif;font-size:var(--ueb-font-size);user-select:none}.ueb-viewport-header{display:flex;position:absolute;top:0;right:0;left:0;height:1.5em;background:rgba(0,0,0,.5);z-index:1}.ueb-viewport-zoom{color:#4d4d4db7}.ueb-viewport-body{position:relative;height:30rem;overflow:hidden;scrollbar-width:none}ueb-blueprint[data-focused=true] .ueb-viewport-body{overflow:scroll}.ueb-grid{--ueb-grid-line-actual-width: calc(var(--ueb-grid-line-width) / var(--ueb-scale));position:absolute;min-width:100%;min-height:100%;width:calc((100% + var(--ueb-additional-x)*1px)/var(--ueb-scale));height:calc((100% + var(--ueb-additional-y)*1px)/var(--ueb-scale));background-color:#262626;background-image:linear-gradient(var(--ueb-grid-axis-line-color), var(--ueb-grid-axis-line-color)),linear-gradient(var(--ueb-grid-axis-line-color), var(--ueb-grid-axis-line-color)),linear-gradient(to right, var(--ueb-grid-set-line-color), var(--ueb-grid-set-line-color) var(--ueb-grid-line-actual-width), transparent var(--ueb-grid-line-actual-width), transparent),linear-gradient(to bottom, var(--ueb-grid-set-line-color), var(--ueb-grid-set-line-color) var(--ueb-grid-line-actual-width), transparent var(--ueb-grid-line-actual-width), transparent),linear-gradient(to right, var(--ueb-grid-line-color), var(--ueb-grid-line-color) var(--ueb-grid-line-actual-width), transparent var(--ueb-grid-line-actual-width), transparent),linear-gradient(to bottom, var(--ueb-grid-line-color), var(--ueb-grid-line-color) var(--ueb-grid-line-actual-width), transparent var(--ueb-grid-line-actual-width), transparent);background-size:100% var(--ueb-grid-line-actual-width),var(--ueb-grid-line-actual-width) 100%,calc(var(--ueb-grid-set)*var(--ueb-grid-actual-size)) calc(var(--ueb-grid-set)*var(--ueb-grid-actual-size)),calc(var(--ueb-grid-set)*var(--ueb-grid-actual-size)) calc(var(--ueb-grid-set)*var(--ueb-grid-actual-size)),var(--ueb-grid-actual-size) var(--ueb-grid-actual-size),var(--ueb-grid-actual-size) var(--ueb-grid-actual-size);background-position:calc(var(--ueb-translate-x)*1px) calc(var(--ueb-translate-y)*1px);background-repeat:repeat-x,repeat-y,repeat,repeat,repeat,repeat;transform:scale(var(--ueb-scale), var(--ueb-scale));transform-origin:0 0;overflow:hidden}ueb-blueprint[data-drag-scrolling=true] .ueb-grid{cursor:grabbing}.ueb-zoom--.ueb,.ueb{--ueb-scale: 1;--ueb-grid-actual-size: var(--ueb-grid-size)}.ueb-zoom--1.ueb{--ueb-scale: 0.875}.ueb-zoom--2.ueb{--ueb-scale: 0.75}.ueb-zoom--3.ueb{--ueb-scale: 0.675}.ueb-zoom--4.ueb{--ueb-scale: 0.5;--ueb-grid-actual-size: calc(var(--ueb-grid-size) * 2)}.ueb-zoom--5.ueb{--ueb-scale: 0.375;--ueb-grid-actual-size: calc(var(--ueb-grid-size) * 2)}.ueb-zoom--6.ueb{--ueb-scale: 0.333333;--ueb-grid-actual-size: calc(var(--ueb-grid-size) * 3)}.ueb-zoom--7.ueb{--ueb-scale: 0.3;--ueb-grid-actual-size: calc(var(--ueb-grid-size) * 3)}.ueb-zoom--8.ueb{--ueb-scale: 0.266666;--ueb-grid-actual-size: calc(var(--ueb-grid-size) * 3)}.ueb-zoom--9.ueb{--ueb-scale: 0.233333;--ueb-grid-actual-size: calc(var(--ueb-grid-size) * 3)}.ueb-zoom--10.ueb{--ueb-scale: 0.2;--ueb-grid-actual-size: calc(var(--ueb-grid-size) * 3)}.ueb-zoom--11.ueb{--ueb-scale: 0.166666;--ueb-grid-actual-size: calc(var(--ueb-grid-size) * 6)}.ueb-zoom--12.ueb{--ueb-scale: 0.133333;--ueb-grid-actual-size: calc(var(--ueb-grid-size) * 6)}.ueb-grid-content{position:relative;width:0;height:0;transform:translateX(calc(var(--ueb-translate-x) * 1px)) translateY(calc(var(--ueb-translate-y) * 1px))}ueb-node{display:block;position:absolute;transform:translateX(calc(var(--ueb-position-x) * 1px)) translateY(calc(var(--ueb-position-y) * 1px));border-radius:var(--ueb-node-radius);box-shadow:0 0 1px 0 #000,1px 4px 6px 0 rgba(0,0,0,.3);will-change:transform}ueb-blueprint[data-drag-scrolling=false][data-selecting=false] ueb-node{cursor:move}.ueb-node-border{margin:-3px;padding:3px;border-radius:calc(var(--ueb-node-radius)*1.4)}.ueb-selected{z-index:1}.ueb-selected>.ueb-node-border{background-image:linear-gradient(to right, #f1b000 0%, #f1b000 100%),linear-gradient(to bottom, #f1b000 0%, #cc6700 100%),linear-gradient(to right, #cc6700 0%, #cc6700 100%),linear-gradient(to bottom, #f1b000 0%, #cc6700 100%);background-size:100% 7px,7px 100%,100% 7px,7px 100%;background-position:top,right,bottom,left;background-repeat:repeat-x,repeat-y,repeat-x,repeat-y;outline:3px solid #cc6700;outline-offset:-6px}.ueb-node-content{position:relative;padding:1px;box-shadow:inset 0 0 2px 0 #000;border-radius:var(--ueb-node-radius);background:rgba(0,0,0,.7);overflow:hidden}.ueb-node-header{padding:.2em .7em;box-shadow:inset 0 1px 2px 0 #313631,inset 0 2px 0 0 #92c381;border-radius:var(--ueb-node-radius) var(--ueb-node-radius) 0 0;background:linear-gradient(170deg, #5f815a 0%, #5f815a 50%, transparent 100%);color:silver;font-weight:600;white-space:nowrap}.ueb-node-name{background:radial-gradient(closest-side, rgba(0, 0, 0, 0.5) 0%, transparent 90%);margin:-0.1em -1.6em;padding:.1em 1.6em}.ueb-node-body{display:flex;padding:6px 0;color:#fff;font-weight:100;white-space:nowrap}.ueb-node-inputs{margin-right:auto;padding-left:8px}.ueb-node-outputs{padding-right:8px}ueb-pin{display:block;padding:1px 2px}ueb-blueprint[data-drag-scrolling=false] .ueb-grid{cursor:default}ueb-blueprint[data-drag-scrolling=false][data-selecting=false] ueb-node ueb-pin:hover{background:var(--ueb-node-value-background);cursor:crosshair}.ueb-node-value-icon{display:inline-block;position:relative;width:.85em;height:.85em;vertical-align:baseline;margin:0 .4em -1px .1em}.ueb-node-value-icon::before{content:"";display:block;position:absolute;top:0;right:0;bottom:0;left:0;border:2px solid var(--ueb-node-value-color);border-radius:50%}.ueb-node-value-fill::before{background:var(--ueb-node-value-color)}.ueb-node-value-icon::after{content:"";display:block;position:absolute;top:calc(50% - .3em);left:calc(100% + 1px);width:0;height:0;border-top:.3em solid transparent;border-bottom:.3em solid transparent;border-left:.3em solid var(--ueb-node-value-color)}.ueb-positioned,ueb-blueprint[data-selecting=true] ueb-selector{--ueb-computed-min-x: min(var(--ueb-from-x), var(--ueb-to-x));--ueb-computed-max-x: max(var(--ueb-from-x), var(--ueb-to-x));--ueb-computed-min-y: min(var(--ueb-from-y), var(--ueb-to-y));--ueb-computed-max-y: max(var(--ueb-from-y), var(--ueb-to-y));--ueb-computed-width: max(var(--ueb-from-x) - var(--ueb-to-x), var(--ueb-to-x) - var(--ueb-from-x));--ueb-computed-height: max(var(--ueb-from-y) - var(--ueb-to-y), var(--ueb-to-y) - var(--ueb-from-y));position:absolute;top:calc(var(--ueb-computed-min-y)*1px);left:calc(var(--ueb-computed-min-x)*1px);width:calc(var(--ueb-computed-width)*1px);height:calc(var(--ueb-computed-height)*1px)}ueb-selector{display:block;position:absolute;visibility:hidden;top:0;left:0;width:0;height:0;background-image:repeating-linear-gradient(90deg, transparent, transparent calc(1px / var(--ueb-scale)), white calc(2px / var(--ueb-scale)), white calc(7px / var(--ueb-scale)), transparent calc(7px / var(--ueb-scale)), transparent calc(11px / var(--ueb-scale))),repeating-linear-gradient(90deg, black, black calc(8px / var(--ueb-scale)), transparent calc(9px / var(--ueb-scale)), transparent calc(11px / var(--ueb-scale))),repeating-linear-gradient(90deg, transparent, transparent calc(1px / var(--ueb-scale)), white calc(2px / var(--ueb-scale)), white calc(7px / var(--ueb-scale)), transparent calc(7px / var(--ueb-scale)), transparent calc(11px / var(--ueb-scale))),repeating-linear-gradient(90deg, black, black calc(8px / var(--ueb-scale)), transparent calc(9px / var(--ueb-scale)), transparent calc(11px / var(--ueb-scale))),repeating-linear-gradient(180deg, transparent, transparent calc(1px / var(--ueb-scale)), white calc(1px / var(--ueb-scale)), white calc(7px / var(--ueb-scale)), transparent calc(7px / var(--ueb-scale)), transparent calc(11px / var(--ueb-scale))),repeating-linear-gradient(180deg, black, black calc(8px / var(--ueb-scale)), transparent calc(9px / var(--ueb-scale)), transparent calc(11px / var(--ueb-scale))),repeating-linear-gradient(0deg, transparent, transparent calc(1px / var(--ueb-scale)), white calc(2px / var(--ueb-scale)), white calc(7px / var(--ueb-scale)), transparent calc(7px / var(--ueb-scale)), transparent calc(11px / var(--ueb-scale))),repeating-linear-gradient(0deg, black, black calc(8px / var(--ueb-scale)), transparent calc(9px / var(--ueb-scale)), transparent calc(11px / var(--ueb-scale)));background-size:100% calc(1px/var(--ueb-scale)),100% calc(3px/var(--ueb-scale)),100% calc(1px/var(--ueb-scale)),100% calc(3px/var(--ueb-scale)),calc(1px/var(--ueb-scale)) 100%,calc(3px/var(--ueb-scale)) 100%,calc(1px/var(--ueb-scale)) 100%,calc(3px/var(--ueb-scale)) 100%;background-position:0 calc(1px/var(--ueb-scale)),0 0,0 calc(100% - 1px/var(--ueb-scale)),0 100%,calc(1px/var(--ueb-scale)) 0,0 0,calc(100% - 1px/var(--ueb-scale)) 0,100% 0;background-repeat:no-repeat}ueb-blueprint[data-selecting=true] ueb-selector{visibility:visible}ueb-selector>*{visibility:visible}ueb-link{--ueb-computed-width-px: calc(var(--ueb-computed-width) * 1px);display:block;min-width:calc(var(--ueb-link-min-width)*1px);border:1px solid red}ueb-link svg{--ueb-output-invert: calc(2 * var(--ueb-from-input) - 1);position:absolute;top:0;left:0;width:100%;height:100%;transform:scaleY(clamp(-1, (var(--ueb-to-y) - var(--ueb-from-y)) * var(--ueb-output-invert), 1));overflow:visible}ueb-link svg path{stroke:var(--ueb-node-value-color)}ueb-link.ueb-link-dragging svg path,ueb-link svg g:hover path{stroke-width:5;transition:stroke-width .8s}ueb-link-message{display:block;position:absolute;left:calc(var(--ueb-start-percentage) + 15px);bottom:-42px;border:1px solid #000;padding:4px 8px;border-radius:2px;background:linear-gradient(to bottom, #2a2a2a 0, #151515 50%, #2a2a2a 100%);color:var(--ueb-node-value-dim-color);white-space:nowrap;z-index:1000000}/*# sourceMappingURL=ueblueprint-style.css.map */
diff --git a/dist/css/ueblueprint-style.css.map b/dist/css/ueblueprint-style.css.map
index d7496b5..f6bf47c 100755
--- a/dist/css/ueblueprint-style.css.map
+++ b/dist/css/ueblueprint-style.css.map
@@ -1 +1 @@
-{"version":3,"sourceRoot":"","sources":["../../scss/ueblueprint-style.scss"],"names":[],"mappings":"AAAA,WACI,qBACA,iBACA,IACI,kGAIR,WACI,qBACA,mBACA,IACI,sGAIR,cACI,cACA,kBACA,8EACA,+BACA,iBAGJ,qBACI,aACA,kBACA,MACA,QACA,OACA,aACA,0BACA,UAGJ,mBACI,gBAGJ,mBACI,kBACA,aACA,gBACA,qBAGJ,oDACI,gBAGJ,UACI,kFACA,kBACA,eACA,gBACA,kEACA,mEACA,yBACA,iBAEI,s3BA0BJ,gBAEI,sZAQJ,sFACA,gEACA,oDACA,qBACA,gBAGJ,kDACI,gBAGJ,qBAGI,eACA,6CAGJ,iBAEI,mBAGJ,iBAEI,kBAGJ,iBAEI,mBAGJ,iBAEI,iBACA,uDAGJ,iBAEI,mBACA,uDAGJ,iBACI,sBACA,uDAGJ,iBACI,iBACA,uDAGJ,iBACI,sBACA,uDAGJ,iBACI,sBACA,uDAGJ,kBAEI,iBACA,uDAGJ,kBAEI,sBACA,uDAGJ,kBAEI,sBACA,uDAGJ,kBACI,kBACA,QACA,SACA,wGAGJ,SACI,cACA,kBACA,sGACA,qCACA,uDACA,sBAGJ,wEACI,YAGJ,iBACI,YACA,YACA,+CAGJ,cACI,UAGJ,+BACI,iBACI,kNAIJ,oDACA,0CACA,sDACA,0BACA,oBAGJ,kBACI,kBACA,YACA,gCACA,qCACA,0BACA,gBAGJ,iBACI,kBACA,6DACA,gEACA,8EACA,aACA,gBACA,mBAGJ,eACI,iFACA,qBACA,mBAGJ,eACI,aACA,cACA,WACA,gBACA,mBAGJ,iBACI,kBACA,iBAGJ,kBACI,kBAGJ,QACI,cACA,gBAGJ,mDACI,eAGJ,sFACI,4CACA,iBAGJ,qBACI,qBACA,kBACA,YACA,aACA,wBACA,wBAGJ,6BACI,WACA,cACA,kBACA,MACA,QACA,SACA,OACA,6CACA,kBAGJ,6BACI,uCAGJ,4BACI,WACA,cACA,kBACA,qBACA,sBACA,QACA,SACA,kCACA,qCACA,mDAGJ,gEACI,8DACA,8DACA,8DACA,8DACA,oGACA,qGACA,kBACA,wCACA,yCACA,0CACA,4CAGJ,aACI,cACA,kBACA,kBACA,MACA,OACA,QACA,SACA,iBAEI,wlDAmDJ,gBAEI,gQAWJ,oBAEI,wJAOJ,4BAGJ,gDACI,mBAIJ,eACI,mBAGJ,SACI,+DACA,cACA,8CACA,qBAGJ,aACI,yDACA,kBACA,MACA,OACA,WACA,YACA,iGAKA,iBAGJ,kBAEI,oJAOA,uFACA,qBACA,eACA,4BAGJ,wBACI,eAGJ,iBACI,cACA,kBACA,8CACA,aACA,sBACA,gBACA,kBACA,4EACA,sCACA","file":"ueblueprint-style.css"}
\ No newline at end of file
+{"version":3,"sourceRoot":"","sources":["../../scss/ueblueprint-style.scss"],"names":[],"mappings":"AAAA,WACI,qBACA,iBACA,IACI,kGAIR,WACI,qBACA,mBACA,IACI,sGAIR,cACI,cACA,kBACA,8EACA,+BACA,iBAGJ,qBACI,aACA,kBACA,MACA,QACA,OACA,aACA,0BACA,UAGJ,mBACI,gBAGJ,mBACI,kBACA,aACA,gBACA,qBAGJ,oDACI,gBAGJ,UACI,kFACA,kBACA,eACA,gBACA,kEACA,mEACA,yBACA,iBAEI,s3BA0BJ,gBAEI,sZAQJ,sFACA,gEACA,oDACA,qBACA,gBAGJ,kDACI,gBAGJ,qBAGI,eACA,6CAGJ,iBAEI,mBAGJ,iBAEI,kBAGJ,iBAEI,mBAGJ,iBAEI,iBACA,uDAGJ,iBAEI,mBACA,uDAGJ,iBACI,sBACA,uDAGJ,iBACI,iBACA,uDAGJ,iBACI,sBACA,uDAGJ,iBACI,sBACA,uDAGJ,kBAEI,iBACA,uDAGJ,kBAEI,sBACA,uDAGJ,kBAEI,sBACA,uDAGJ,kBACI,kBACA,QACA,SACA,wGAGJ,SACI,cACA,kBACA,sGACA,qCACA,uDACA,sBAGJ,wEACI,YAGJ,iBACI,YACA,YACA,+CAGJ,cACI,UAGJ,+BACI,iBACI,kNAIJ,oDACA,0CACA,sDACA,0BACA,oBAGJ,kBACI,kBACA,YACA,gCACA,qCACA,0BACA,gBAGJ,iBACI,kBACA,6DACA,gEACA,8EACA,aACA,gBACA,mBAGJ,eACI,iFACA,qBACA,mBAGJ,eACI,aACA,cACA,WACA,gBACA,mBAGJ,iBACI,kBACA,iBAGJ,kBACI,kBAGJ,QACI,cACA,gBAGJ,mDACI,eAGJ,sFACI,4CACA,iBAGJ,qBACI,qBACA,kBACA,YACA,aACA,wBACA,wBAGJ,6BACI,WACA,cACA,kBACA,MACA,QACA,SACA,OACA,6CACA,kBAGJ,6BACI,uCAGJ,4BACI,WACA,cACA,kBACA,qBACA,sBACA,QACA,SACA,kCACA,qCACA,mDAGJ,gEACI,8DACA,8DACA,8DACA,8DACA,oGACA,qGACA,kBACA,wCACA,yCACA,0CACA,4CAGJ,aACI,cACA,kBACA,kBACA,MACA,OACA,QACA,SACA,iBAEI,wlDAmDJ,gBAEI,gQAWJ,oBAEI,wJAOJ,4BAGJ,gDACI,mBAIJ,eACI,mBAGJ,SACI,+DACA,cACA,8CACA,qBAGJ,aACI,yDACA,kBACA,MACA,OACA,WACA,YACA,iGAKA,iBAGJ,kBACI,mCAGJ,8DAEI,eACA,4BAGJ,iBACI,cACA,kBACA,8CACA,aACA,sBACA,gBACA,kBACA,4EACA,sCACA,mBACA","file":"ueblueprint-style.css"}
\ No newline at end of file
diff --git a/dist/ueblueprint.js b/dist/ueblueprint.js
index c729b0b..125a1a2 100755
--- a/dist/ueblueprint.js
+++ b/dist/ueblueprint.js
@@ -129,6 +129,30 @@ class Configuration {
*/
const html = String.raw;
+/**
+ * @typedef {import("../element/IElement").default} IElement
+ */
+class ITemplate {
+
+ /**
+ * Computes the html content of the target element.
+ * @param {IElement} entity Element of the graph
+ * @returns The result html
+ */
+ render(entity) {
+ return ""
+ }
+
+ /**
+ * Applies the style to the element.
+ * @param {IElement} element Element of the graph
+ */
+ apply(element) {
+ // TODO replace with the safer element.setHTML(...) when it will be available
+ element.innerHTML = this.render(element);
+ }
+}
+
document.createElement("div");
const tagReplacement = {
@@ -494,30 +518,6 @@ class IElement extends HTMLElement {
}
}
-/**
- * @typedef {import("../element/IElement").default} IElement
- */
-class ITemplate {
-
- /**
- * Computes the html content of the target element.
- * @param {IElement} entity Element of the graph
- * @returns The result html
- */
- render(entity) {
- return ""
- }
-
- /**
- * Applies the style to the element.
- * @param {IElement} element Element of the graph
- */
- apply(element) {
- // TODO replace with the safer element.setHTML(...) when it will be available
- element.innerHTML = this.render(element);
- }
-}
-
/**
* @typedef {import("../element/SelectorElement").default} SelectorElement
*/
@@ -1517,6 +1517,126 @@ class Copy extends IContext {
}
}
+let P = Parsimmon;
+
+class KeyGrammar {
+
+ // Creates a grammar where each alternative is the string from ModifierKey mapped to a number for bit or use
+ ModifierKey = r => P.alt(...Configuration.ModifierKeys.map((v, i) => P.string(v).map(_ => 1 << i)))
+ Key = r => P.alt(...Object.keys(Configuration.Keys).map(v => P.string(v))).map(v => Configuration.Keys[v])
+ KeyboardShortcut = r => P.alt(
+ P.seqMap(
+ P.seqMap(r.ModifierKey, P.optWhitespace, P.string(Configuration.keysSeparator), (v, _, __) => v)
+ .atLeast(1)
+ .map(v => v.reduce((acc, cur) => acc | cur)),
+ P.optWhitespace,
+ r.Key,
+ (modifierKeysFlag, _, key) => ({
+ key: key,
+ ctrlKey: Boolean(modifierKeysFlag & (1 << Configuration.ModifierKeys.indexOf("Ctrl"))),
+ shiftKey: Boolean(modifierKeysFlag & (1 << Configuration.ModifierKeys.indexOf("Shift"))),
+ altKey: Boolean(modifierKeysFlag & (1 << Configuration.ModifierKeys.indexOf("Alt"))),
+ metaKey: Boolean(modifierKeysFlag & (1 << Configuration.ModifierKeys.indexOf("Meta")))
+ })
+ ),
+ r.Key.map(v => ({ key: v }))
+ )
+ .trim(P.optWhitespace)
+}
+
+class IKeyboardShortcut extends IContext {
+
+ static keyGrammar = P.createLanguage(new KeyGrammar())
+
+ constructor(target, blueprint, options = {}) {
+ options.wantsFocusCallback = true;
+ super(target, blueprint, options);
+
+ /** @type {String[]} */
+ this.key = this.options.key;
+ this.ctrlKey = options.ctrlKey ?? false;
+ this.shiftKey = options.shiftKey ?? false;
+ this.altKey = options.altKey ?? false;
+ this.metaKey = options.metaKey ?? false;
+
+ let self = this;
+ this.keyDownHandler = e => {
+ if (
+ e.code == self.key
+ && e.ctrlKey === self.ctrlKey
+ && e.shiftKey === self.shiftKey
+ && e.altKey === self.altKey
+ && e.metaKey === self.metaKey
+ ) {
+ self.fire();
+ e.preventDefault();
+ return true
+ }
+ return false
+ };
+ }
+
+ /**
+ *
+ * @param {String} keyString
+ * @returns {Object}
+ */
+ static keyOptionsParse(options, keyString) {
+ options = {
+ ...options,
+ ...IKeyboardShortcut.keyGrammar.KeyboardShortcut.parse(keyString).value
+ };
+ return options
+ }
+
+ listenEvents() {
+ document.addEventListener("keydown", this.keyDownHandler);
+ }
+
+ unlistenEvents() {
+ document.removeEventListener("keydown", this.keyDownHandler);
+ }
+
+ fire() {
+ }
+}
+
+class KeyvoardCanc extends IKeyboardShortcut {
+
+ /**
+ *
+ * @param {HTMLElement} target
+ * @param {import("../../Blueprint").default} blueprint
+ * @param {OBject} options
+ */
+ constructor(target, blueprint, options = {}) {
+ options = IKeyboardShortcut.keyOptionsParse(options, Configuration.deleteNodesKeyboardKey);
+ super(target, blueprint, options);
+ }
+
+ fire() {
+ this.blueprint.removeGraphElement(...this.blueprint.getNodes(true));
+ }
+}
+
+class KeyboardSelectAll extends IKeyboardShortcut {
+
+ /**
+ *
+ * @param {HTMLElement} target
+ * @param {import("../../Blueprint").default} blueprint
+ * @param {Object} options
+ */
+ constructor(target, blueprint, options = {}) {
+ options = IKeyboardShortcut.keyOptionsParse(options, Configuration.selectAllKeyboardKey);
+ super(target, blueprint, options);
+ }
+
+ fire() {
+ this.blueprint.selectAll();
+ }
+}
+
/**
* @typedef {import("../element/LinkElement").default} LinkElement
* @typedef {import("../element/LinkMessageElement").default} LinkMessageElement
@@ -1537,9 +1657,13 @@ class LinkTemplate extends ITemplate {
* @returns The result html
*/
render(link) {
+ const uniqueId = crypto.randomUUID();
return html`
`
}
@@ -1549,17 +1673,39 @@ class LinkTemplate extends ITemplate {
* @param {LinkElement} link Element of the graph
*/
apply(link) {
- super.apply(link);
- link.classList.add("ueb-positioned");
- link.pathElement = link.querySelector("path");
if (link.linkMessageElement) {
link.appendChild(link.linkMessageElement);
}
+ super.apply(link);
+ link.classList.add("ueb-positioned");
+ link.pathElement = link.querySelector("path");
+ }
+
+ /**
+ *
+ * @param {LinkElement} link element
+ */
+ applyStartDragging(link) {
+ link.blueprint.dataset.creatingLink = true;
+ const referencePin = link.getSourcePin() ?? link.getDestinationPin();
+ if (referencePin) {
+ link.style.setProperty("--ueb-node-value-color", referencePin.getColor());
+ }
+ link.classList.add("ueb-link-dragging");
+ }
+
+ /**
+ *
+ * @param {LinkElement} link element
+ */
+ applyFinishDragging(link) {
+ link.blueprint.dataset.creatingLink = false;
+ link.classList.remove("ueb-link-dragging");
}
/**
* Applies the style relative to the source pin location.
- * @param {LinkElement} link Link element
+ * @param {LinkElement} link element
*/
applySourceLocation(link) {
link.style.setProperty("--ueb-from-input", link.originatesFromInput ? "0" : "1");
@@ -1794,146 +1940,18 @@ class LinkElement extends IElement {
this.linkMessageElement = null;
}
}
+
+ startDragging() {
+ this.template.applyStartDragging(this);
+ }
+
+ finishDragging() {
+ this.template.applyFinishDragging(this);
+ }
}
customElements.define(LinkElement.tagName, LinkElement);
-/**
- * @typedef {import("../element/PinElement").default} PinElement
- */
-class PinTemplate extends ITemplate {
-
- /**
- * Computes the html content of the pin.
- * @param {PinElement} pin html element
- * @returns The result html
- */
- render(pin) {
- if (pin.isInput()) {
- return html`
-
- ${sanitizeText(pin.getPinDisplayName())}
- `
- } else {
- return html`
- ${sanitizeText(pin.getPinDisplayName())}
-
- `
- }
- }
-
- /**
- * Applies the style to the element.
- * @param {PinElement} pin element of the graph
- */
- apply(pin) {
- super.apply(pin);
- pin.classList.add(
- "ueb-node-" + (pin.isInput() ? "input" : pin.isOutput() ? "output" : "hidden"), "ueb-node-value-" + sanitizeText(pin.getType()));
- pin.clickableElement = pin;
- }
-
- /**
- *
- * @param {PinElement} pin
- * @returns
- */
- getLinkLocation(pin) {
- const rect = pin.querySelector(".ueb-node-value-icon").getBoundingClientRect();
- return pin.blueprint.compensateTranslation(Utility.convertLocation(
- [(rect.left + rect.right) / 2, (rect.top + rect.bottom) / 2],
- pin.blueprint.gridElement))
- }
-}
-
-/**
- * @typedef {import("../element/LinkMessageElement").default} LinkMessageElement
- */
-class LinkMessageTemplate extends ITemplate {
-
- /**
- * Computes the html content of the target element.
- * @param {LinkMessageElement} linkMessage attached to link destination
- * @returns The result html
- */
- render(linkMessage) {
- return html`
-
-
- `
- }
-
- /**
- * Applies the style to the element.
- * @param {LinkMessageElement} linkMessage element
- */
- apply(linkMessage) {
- super.apply(linkMessage);
- linkMessage.linkElement = linkMessage.closest(LinkElement.tagName);
- linkMessage.querySelector(".ueb-link-message").innerText = linkMessage.message(
- linkMessage.linkElement.getSourcePin(),
- linkMessage.linkElement.getDestinationPin()
- );
- }
-
-}
-
-/**
- * @typedef {import("./PinElement").default} PinElement
- * @typedef {import("./LinkElement").default} LinkElement
- * @typedef {(sourcePin: PinElement, sourcePin: PinElement) => String} LinkRetrieval
- */
-class LinkMessageElement extends IElement {
-
- static tagName = "ueb-link-message"
- static convertType = _ => new LinkMessageElement(
- "ueb-icon-conver-type",
- /** @type {LinkRetrieval} */
- (s, d) => `Convert ${s.getType()} to ${d.getType()}.`
- )
- static directionsIncompatible = _ => new LinkMessageElement(
- "ueb-icon-directions-incompatible",
- /** @type {LinkRetrieval} */
- (s, d) => "Directions are not compatbile."
- )
- static placeNode = _ => new LinkMessageElement(
- "ueb-icon-place-node",
- /** @type {LinkRetrieval} */
- (s, d) => "Place a new node."
- )
- static replaceLink = _ => new LinkMessageElement(
- "ueb-icon-replace-link",
- /** @type {LinkRetrieval} */
- (s, d) => "Replace existing input connections."
- )
- static sameNode = _ => new LinkMessageElement(
- "ueb-icon-same-node",
- /** @type {LinkRetrieval} */
- (s, d) => "Both are on the same node."
- )
- static typesIncompatible = _ => new LinkMessageElement(
- "ueb-icon-types-incompatible",
- /** @type {LinkRetrieval} */
- (s, d) => `${s.getType()} is not compatible with ${d.getType()}.`
- )
-
- /** @type {String} */
- icon
- /** @type {String} */
- message
- /** @type {LinkElement} */
- linkElement
-
- constructor(icon, message) {
- super({}, new LinkMessageTemplate());
- this.icon = icon;
- this.message = message;
- }
-
-}
-
-customElements.define(LinkMessageElement.tagName, LinkMessageElement);
-
class IPointing extends IContext {
constructor(target, blueprint, options) {
@@ -2091,224 +2109,80 @@ class IMouseClickDrag extends IPointing {
}
}
-/**
- * @typedef {import("../../element/PinElement").default} PinElement
- * @typedef {import("../../element/LinkElement").default} LinkElement
- */
-class MouseCreateLink extends IMouseClickDrag {
-
- /** @type {NodeListOf} */
- #listenedPins
-
- /** @type {(e: MouseEvent) => void} */
- #mouseenterHandler
-
- /** @type {(e: MouseEvent) => void} */
- #mouseleaveHandler
-
- constructor(target, blueprint, options) {
- super(target, blueprint, options);
- /** @type {PinElement} */
- this.target;
- /** @type {LinkElement} */
- this.link;
- /** @type {PinElement} */
- this.enteredPin;
-
- let self = this;
- this.#mouseenterHandler = e => {
- if (!self.enteredPin) {
- self.enteredPin = e.target;
- }
- };
- this.#mouseleaveHandler = e => {
- if (self.enteredPin == e.target) {
- self.enteredPin = null;
- }
- };
- }
+class MouseScrollGraph extends IMouseClickDrag {
startDrag() {
- this.link = new LinkElement(this.target, null);
- this.link.setLinkMessage(LinkMessageElement.placeNode());
- this.blueprint.nodesContainerElement.insertBefore(this.link, this.blueprint.selectorElement.nextElementSibling);
- this.#listenedPins = this.blueprint.querySelectorAll(this.target.constructor.tagName);
- this.#listenedPins.forEach(pin => {
- if (pin != this.target) {
- pin.getClickableElement().addEventListener("mouseenter", this.#mouseenterHandler);
- pin.getClickableElement().addEventListener("mouseleave", this.#mouseleaveHandler);
- }
- });
+ this.blueprint.template.applyStartDragScrolling(this.blueprint);
}
dragTo(location, movement) {
- this.link.setDestinationLocation(location);
+ this.blueprint.scrollDelta([-movement[0], -movement[1]]);
}
endDrag() {
- this.#listenedPins.forEach(pin => {
- pin.removeEventListener("mouseenter", this.#mouseenterHandler);
- pin.removeEventListener("mouseleave", this.#mouseleaveHandler);
- });
- if (this.enteredPin && !this.blueprint.getLinks().find(
- link =>
- link.getSourcePin() == this.target && link.getDestinationPin() == this.enteredPin
- || link.getSourcePin() == this.enteredPin && link.getDestinationPin() == this.target
- )) {
- this.link.setDestinationPin(this.enteredPin);
- this.link.setLinkMessage(null);
- this.blueprint.addGraphElement(this.link);
- } else {
- this.link.remove();
- }
- this.link = null;
+ this.blueprint.template.applyEndDragScrolling(this.blueprint);
}
}
-class PinElement extends IElement {
+class MouseTracking extends IPointing {
- static tagName = "ueb-pin"
+ /** @type {IPointing} */
+ #mouseTracker = null
- constructor(entity) {
- super(entity, new PinTemplate());
- /** @type {import("../entity/PinEntity").default} */
- this.entity;
- /** @type {PinTemplate} */
- this.template;
- /** @type {HTMLElement} */
- this.clickableElement = null;
+ /** @type {(e: MouseEvent) => void} */
+ #mousemoveHandler
+
+ /** @type {(e: CustomEvent) => void} */
+ #trackingMouseStolenHandler
+
+ /** @type {(e: CustomEvent) => void} */
+ #trackingMouseGaveBackHandler
+
+ constructor(target, blueprint, options = {}) {
+ options.wantsFocusCallback = true;
+ super(target, blueprint, options);
+
+ let self = this;
+
+ this.#mousemoveHandler = e => {
+ self.blueprint.entity.mousePosition = self.locationFromEvent(e);
+ };
+
+ this.#trackingMouseStolenHandler = e => {
+ if (!self.#mouseTracker) {
+ e.preventDefault();
+ this.#mouseTracker = e.detail.tracker;
+ self.unlistenMouseMove();
+ }
+ };
+
+ this.#trackingMouseGaveBackHandler = e => {
+ if (self.#mouseTracker == e.detail.tracker) {
+ e.preventDefault();
+ self.#mouseTracker = null;
+ self.listenMouseMove();
+ }
+ };
}
- createInputObjects() {
- return [
- new MouseCreateLink(this.clickableElement, this.blueprint, {
- moveEverywhere: true,
- looseTarget: true
- }),
- ]
+ listenMouseMove() {
+ this.target.addEventListener("mousemove", this.#mousemoveHandler);
}
- /**
- *
- * @returns {String}
- */
- getPinDisplayName() {
- return this.entity.PinName
+ unlistenMouseMove() {
+ this.target.removeEventListener("mousemove", this.#mousemoveHandler);
}
- getAttributes() {
- return PinEntity.attributes
+ listenEvents() {
+ this.listenMouseMove();
+ this.blueprint.addEventListener(Configuration.trackingMouseEventName.begin, this.#trackingMouseStolenHandler);
+ this.blueprint.addEventListener(Configuration.trackingMouseEventName.end, this.#trackingMouseGaveBackHandler);
}
- isInput() {
- return this.entity.isInput()
- }
-
- isOutput() {
- return this.entity.isOutput()
- }
-
- isConnected() {
- return this.entity.isConnected()
- }
-
- getType() {
- return this.entity.getType()
- }
-
- getClickableElement() {
- return this.clickableElement
- }
-
- /**
- * Returns The exact location where the link originates from or arrives at.
- * @returns {Number[]} The location array
- */
- getLinkLocation() {
- return this.template.getLinkLocation(this)
- }
-
- getNodeElement() {
- return this.closest("ueb-node")
- }
-}
-
-customElements.define(PinElement.tagName, PinElement);
-
-/**
- * @typedef {import("../element/ISelectableDraggableElement").default} ISelectableDraggableElement
- */
-class SelectableDraggableTemplate extends ITemplate {
-
- /**
- * Returns the html elements rendered from this template.
- * @param {ISelectableDraggableElement} element Element of the graph
- */
- applyLocation(element) {
- element.style.setProperty("--ueb-position-x", sanitizeText(element.location[0]));
- element.style.setProperty("--ueb-position-y", sanitizeText(element.location[1]));
- }
-
- /**
- * Returns the html elements rendered from this template.
- * @param {ISelectableDraggableElement} element Element of the graph
- */
- applySelected(element) {
- if (element.selected) {
- element.classList.add("ueb-selected");
- } else {
- element.classList.remove("ueb-selected");
- }
- }
-}
-
-/**
- * @typedef {import("../element/NodeElement").default} NodeElement
- */
-class NodeTemplate extends SelectableDraggableTemplate {
-
- /**
- * Computes the html content of the target element.
- * @param {NodeElement} node Graph node element
- * @returns The result html
- */
- render(node) {
- return html`
-
- `
- }
-
- /**
- * Applies the style to the element.
- * @param {NodeElement} node Element of the graph
- */
- apply(node) {
- super.apply(node);
- if (node.selected) {
- node.classList.add("ueb-selected");
- }
- node.style.setProperty("--ueb-position-x", sanitizeText(node.location[0]));
- node.style.setProperty("--ueb-position-y", sanitizeText(node.location[1]));
- /** @type {HTMLElement} */
- let inputContainer = node.querySelector(".ueb-node-inputs");
- /** @type {HTMLElement} */
- let outputContainer = node.querySelector(".ueb-node-outputs");
- let pins = node.getPinEntities();
- pins.filter(v => v.isInput()).forEach(v => inputContainer.appendChild(new PinElement(v)));
- pins.filter(v => v.isOutput()).forEach(v => outputContainer.appendChild(new PinElement(v)));
+ unlistenEvents() {
+ this.unlistenMouseMove();
+ this.blueprint.removeEventListener(Configuration.trackingMouseEventName.begin, this.#trackingMouseStolenHandler);
+ this.blueprint.removeEventListener(Configuration.trackingMouseEventName.end, this.#trackingMouseGaveBackHandler);
}
}
@@ -2440,6 +2314,379 @@ class ISelectableDraggableElement extends IElement {
}
}
+/**
+ * @typedef {import("../element/LinkMessageElement").default} LinkMessageElement
+ */
+class LinkMessageTemplate extends ITemplate {
+
+ /**
+ * Computes the html content of the target element.
+ * @param {LinkMessageElement} linkMessage attached to link destination
+ * @returns The result html
+ */
+ render(linkMessage) {
+ return html`
+
+
+ `
+ }
+
+ /**
+ * Applies the style to the element.
+ * @param {LinkMessageElement} linkMessage element
+ */
+ apply(linkMessage) {
+ super.apply(linkMessage);
+ linkMessage.linkElement = linkMessage.closest(LinkElement.tagName);
+ linkMessage.querySelector(".ueb-link-message").innerText = linkMessage.message(
+ linkMessage.linkElement.getSourcePin(),
+ linkMessage.linkElement.getDestinationPin()
+ );
+ }
+
+}
+
+/**
+ * @typedef {import("./PinElement").default} PinElement
+ * @typedef {import("./LinkElement").default} LinkElement
+ * @typedef {(sourcePin: PinElement, sourcePin: PinElement) => String} LinkRetrieval
+ */
+class LinkMessageElement extends IElement {
+
+ static tagName = "ueb-link-message"
+ static convertType = _ => new LinkMessageElement(
+ "ueb-icon-conver-type",
+ /** @type {LinkRetrieval} */
+ (s, d) => `Convert ${s.getType()} to ${d.getType()}.`
+ )
+ static directionsIncompatible = _ => new LinkMessageElement(
+ "ueb-icon-directions-incompatible",
+ /** @type {LinkRetrieval} */
+ (s, d) => "Directions are not compatbile."
+ )
+ static placeNode = _ => new LinkMessageElement(
+ "ueb-icon-place-node",
+ /** @type {LinkRetrieval} */
+ (s, d) => "Place a new node."
+ )
+ static replaceLink = _ => new LinkMessageElement(
+ "ueb-icon-replace-link",
+ /** @type {LinkRetrieval} */
+ (s, d) => "Replace existing input connections."
+ )
+ static sameNode = _ => new LinkMessageElement(
+ "ueb-icon-same-node",
+ /** @type {LinkRetrieval} */
+ (s, d) => "Both are on the same node."
+ )
+ static typesIncompatible = _ => new LinkMessageElement(
+ "ueb-icon-types-incompatible",
+ /** @type {LinkRetrieval} */
+ (s, d) => `${s.getType()} is not compatible with ${d.getType()}.`
+ )
+
+ /** @type {String} */
+ icon
+ /** @type {String} */
+ message
+ /** @type {LinkElement} */
+ linkElement
+
+ constructor(icon, message) {
+ super({}, new LinkMessageTemplate());
+ this.icon = icon;
+ this.message = message;
+ }
+
+}
+
+customElements.define(LinkMessageElement.tagName, LinkMessageElement);
+
+/**
+ * @typedef {import("../../element/LinkElement").default} LinkElement
+ * @typedef {import("../../element/PinElement").default} PinElement
+ */
+class MouseCreateLink extends IMouseClickDrag {
+
+ /** @type {NodeListOf} */
+ #listenedPins
+
+ /** @type {(e: MouseEvent) => void} */
+ #mouseenterHandler
+
+ /** @type {(e: MouseEvent) => void} */
+ #mouseleaveHandler
+
+ constructor(target, blueprint, options) {
+ super(target, blueprint, options);
+ /** @type {PinElement} */
+ this.target;
+ /** @type {LinkElement} */
+ this.link;
+ /** @type {PinElement} */
+ this.enteredPin;
+
+ let self = this;
+ this.#mouseenterHandler = e => {
+ if (!self.enteredPin) {
+ self.enteredPin = e.target;
+ }
+ };
+ this.#mouseleaveHandler = e => {
+ if (self.enteredPin == e.target) {
+ self.enteredPin = null;
+ }
+ };
+ }
+
+ startDrag() {
+ this.link = new LinkElement(this.target, null);
+ this.link.setLinkMessage(LinkMessageElement.placeNode());
+ this.blueprint.nodesContainerElement.prepend(this.link);
+ this.#listenedPins = this.blueprint.querySelectorAll(this.target.constructor.tagName);
+ this.#listenedPins.forEach(pin => {
+ if (pin != this.target) {
+ pin.getClickableElement().addEventListener("mouseenter", this.#mouseenterHandler);
+ pin.getClickableElement().addEventListener("mouseleave", this.#mouseleaveHandler);
+ }
+ });
+ this.link.startDragging();
+ }
+
+ dragTo(location, movement) {
+ this.link.setDestinationLocation(location);
+ }
+
+ endDrag() {
+ this.#listenedPins.forEach(pin => {
+ pin.removeEventListener("mouseenter", this.#mouseenterHandler);
+ pin.removeEventListener("mouseleave", this.#mouseleaveHandler);
+ });
+ if (this.enteredPin && !this.blueprint.getLinks().find(
+ link =>
+ link.getSourcePin() == this.target && link.getDestinationPin() == this.enteredPin
+ || link.getSourcePin() == this.enteredPin && link.getDestinationPin() == this.target
+ )) {
+ this.blueprint.addGraphElement(this.link);
+ this.link.setDestinationPin(this.enteredPin);
+ this.link.setLinkMessage(null);
+ this.link.finishDragging();
+ } else {
+ this.link.finishDragging();
+ this.link.remove();
+ }
+ this.link = null;
+ }
+}
+
+/**
+ * @typedef {import("../element/PinElement").default} PinElement
+ */
+class PinTemplate extends ITemplate {
+
+ /**
+ * Computes the html content of the pin.
+ * @param {PinElement} pin html element
+ * @returns The result html
+ */
+ render(pin) {
+ if (pin.isInput()) {
+ return html`
+
+ ${sanitizeText(pin.getPinDisplayName())}
+ `
+ } else {
+ return html`
+ ${sanitizeText(pin.getPinDisplayName())}
+
+ `
+ }
+ }
+
+ /**
+ * Applies the style to the element.
+ * @param {PinElement} pin element of the graph
+ */
+ apply(pin) {
+ super.apply(pin);
+ pin.classList.add(
+ "ueb-node-" + (pin.isInput() ? "input" : pin.isOutput() ? "output" : "hidden"), "ueb-node-value-" + sanitizeText(pin.getType()));
+ pin.clickableElement = pin;
+ }
+
+ /**
+ *
+ * @param {PinElement} pin
+ * @returns
+ */
+ getLinkLocation(pin) {
+ const rect = pin.querySelector(".ueb-node-value-icon").getBoundingClientRect();
+ return pin.blueprint.compensateTranslation(Utility.convertLocation(
+ [(rect.left + rect.right) / 2, (rect.top + rect.bottom) / 2],
+ pin.blueprint.gridElement))
+ }
+}
+
+class PinElement extends IElement {
+
+ static tagName = "ueb-pin"
+
+ /** @type {HTMLElement} */
+ clickableElement
+
+ /** @type {String} */
+ #color
+
+ constructor(entity) {
+ super(entity, new PinTemplate());
+ /** @type {import("../entity/PinEntity").default} */
+ this.entity;
+ /** @type {PinTemplate} */
+ this.template;
+ }
+
+ connectedCallback() {
+ super.connectedCallback();
+ this.#color = window.getComputedStyle(this).getPropertyValue("--ueb-node-value-color");
+ }
+
+ createInputObjects() {
+ return [
+ new MouseCreateLink(this.clickableElement, this.blueprint, {
+ moveEverywhere: true,
+ looseTarget: true
+ }),
+ ]
+ }
+
+ /**
+ *
+ * @returns {String}
+ */
+ getPinDisplayName() {
+ return this.entity.PinName
+ }
+
+ getAttributes() {
+ return PinEntity.attributes
+ }
+
+ isInput() {
+ return this.entity.isInput()
+ }
+
+ isOutput() {
+ return this.entity.isOutput()
+ }
+
+ isConnected() {
+ return this.entity.isConnected()
+ }
+
+ getType() {
+ return this.entity.getType()
+ }
+
+ getClickableElement() {
+ return this.clickableElement
+ }
+
+ getColor() {
+ return this.#color
+ }
+
+ /**
+ * Returns The exact location where the link originates from or arrives at.
+ * @returns {Number[]} The location array
+ */
+ getLinkLocation() {
+ return this.template.getLinkLocation(this)
+ }
+
+ getNodeElement() {
+ return this.closest("ueb-node")
+ }
+}
+
+customElements.define(PinElement.tagName, PinElement);
+
+/**
+ * @typedef {import("../element/ISelectableDraggableElement").default} ISelectableDraggableElement
+ */
+class SelectableDraggableTemplate extends ITemplate {
+
+ /**
+ * Returns the html elements rendered from this template.
+ * @param {ISelectableDraggableElement} element Element of the graph
+ */
+ applyLocation(element) {
+ element.style.setProperty("--ueb-position-x", sanitizeText(element.location[0]));
+ element.style.setProperty("--ueb-position-y", sanitizeText(element.location[1]));
+ }
+
+ /**
+ * Returns the html elements rendered from this template.
+ * @param {ISelectableDraggableElement} element Element of the graph
+ */
+ applySelected(element) {
+ if (element.selected) {
+ element.classList.add("ueb-selected");
+ } else {
+ element.classList.remove("ueb-selected");
+ }
+ }
+}
+
+/**
+ * @typedef {import("../element/NodeElement").default} NodeElement
+ */
+class NodeTemplate extends SelectableDraggableTemplate {
+
+ /**
+ * Computes the html content of the target element.
+ * @param {NodeElement} node Graph node element
+ * @returns The result html
+ */
+ render(node) {
+ return html`
+
+ `
+ }
+
+ /**
+ * Applies the style to the element.
+ * @param {NodeElement} node Element of the graph
+ */
+ apply(node) {
+ super.apply(node);
+ if (node.selected) {
+ node.classList.add("ueb-selected");
+ }
+ node.style.setProperty("--ueb-position-x", sanitizeText(node.location[0]));
+ node.style.setProperty("--ueb-position-y", sanitizeText(node.location[1]));
+ /** @type {HTMLElement} */
+ let inputContainer = node.querySelector(".ueb-node-inputs");
+ /** @type {HTMLElement} */
+ let outputContainer = node.querySelector(".ueb-node-outputs");
+ let pins = node.getPinEntities();
+ pins.filter(v => v.isInput()).forEach(v => inputContainer.appendChild(new PinElement(v)));
+ pins.filter(v => v.isOutput()).forEach(v => outputContainer.appendChild(new PinElement(v)));
+ }
+}
+
class NodeElement extends ISelectableDraggableElement {
static tagName = "ueb-node"
@@ -2497,203 +2744,6 @@ class NodeElement extends ISelectableDraggableElement {
customElements.define(NodeElement.tagName, NodeElement);
-let P = Parsimmon;
-
-class KeyGrammar {
-
- // Creates a grammar where each alternative is the string from ModifierKey mapped to a number for bit or use
- ModifierKey = r => P.alt(...Configuration.ModifierKeys.map((v, i) => P.string(v).map(_ => 1 << i)))
- Key = r => P.alt(...Object.keys(Configuration.Keys).map(v => P.string(v))).map(v => Configuration.Keys[v])
- KeyboardShortcut = r => P.alt(
- P.seqMap(
- P.seqMap(r.ModifierKey, P.optWhitespace, P.string(Configuration.keysSeparator), (v, _, __) => v)
- .atLeast(1)
- .map(v => v.reduce((acc, cur) => acc | cur)),
- P.optWhitespace,
- r.Key,
- (modifierKeysFlag, _, key) => ({
- key: key,
- ctrlKey: Boolean(modifierKeysFlag & (1 << Configuration.ModifierKeys.indexOf("Ctrl"))),
- shiftKey: Boolean(modifierKeysFlag & (1 << Configuration.ModifierKeys.indexOf("Shift"))),
- altKey: Boolean(modifierKeysFlag & (1 << Configuration.ModifierKeys.indexOf("Alt"))),
- metaKey: Boolean(modifierKeysFlag & (1 << Configuration.ModifierKeys.indexOf("Meta")))
- })
- ),
- r.Key.map(v => ({ key: v }))
- )
- .trim(P.optWhitespace)
-}
-
-class IKeyboardShortcut extends IContext {
-
- static keyGrammar = P.createLanguage(new KeyGrammar())
-
- constructor(target, blueprint, options = {}) {
- options.wantsFocusCallback = true;
- super(target, blueprint, options);
-
- /** @type {String[]} */
- this.key = this.options.key;
- this.ctrlKey = options.ctrlKey ?? false;
- this.shiftKey = options.shiftKey ?? false;
- this.altKey = options.altKey ?? false;
- this.metaKey = options.metaKey ?? false;
-
- let self = this;
- this.keyDownHandler = e => {
- if (
- e.code == self.key
- && e.ctrlKey === self.ctrlKey
- && e.shiftKey === self.shiftKey
- && e.altKey === self.altKey
- && e.metaKey === self.metaKey
- ) {
- self.fire();
- e.preventDefault();
- return true
- }
- return false
- };
- }
-
- /**
- *
- * @param {String} keyString
- * @returns {Object}
- */
- static keyOptionsParse(options, keyString) {
- options = {
- ...options,
- ...IKeyboardShortcut.keyGrammar.KeyboardShortcut.parse(keyString).value
- };
- return options
- }
-
- listenEvents() {
- document.addEventListener("keydown", this.keyDownHandler);
- }
-
- unlistenEvents() {
- document.removeEventListener("keydown", this.keyDownHandler);
- }
-
- fire() {
- }
-}
-
-class KeyvoardCanc extends IKeyboardShortcut {
-
- /**
- *
- * @param {HTMLElement} target
- * @param {import("../../Blueprint").default} blueprint
- * @param {OBject} options
- */
- constructor(target, blueprint, options = {}) {
- options = IKeyboardShortcut.keyOptionsParse(options, Configuration.deleteNodesKeyboardKey);
- super(target, blueprint, options);
- }
-
- fire() {
- this.blueprint.removeGraphElement(...this.blueprint.getNodes(true));
- }
-}
-
-class KeyboardSelectAll extends IKeyboardShortcut {
-
- /**
- *
- * @param {HTMLElement} target
- * @param {import("../../Blueprint").default} blueprint
- * @param {Object} options
- */
- constructor(target, blueprint, options = {}) {
- options = IKeyboardShortcut.keyOptionsParse(options, Configuration.selectAllKeyboardKey);
- super(target, blueprint, options);
- }
-
- fire() {
- this.blueprint.selectAll();
- }
-}
-
-class MouseScrollGraph extends IMouseClickDrag {
-
- startDrag() {
- this.blueprint.template.applyStartDragScrolling(this.blueprint);
- }
-
- dragTo(location, movement) {
- this.blueprint.scrollDelta([-movement[0], -movement[1]]);
- }
-
- endDrag() {
- this.blueprint.template.applyEndDragScrolling(this.blueprint);
- }
-}
-
-class MouseTracking extends IPointing {
-
- /** @type {IPointing} */
- #mouseTracker = null
-
- /** @type {(e: MouseEvent) => void} */
- #mousemoveHandler
-
- /** @type {(e: CustomEvent) => void} */
- #trackingMouseStolenHandler
-
- /** @type {(e: CustomEvent) => void} */
- #trackingMouseGaveBackHandler
-
- constructor(target, blueprint, options = {}) {
- options.wantsFocusCallback = true;
- super(target, blueprint, options);
-
- let self = this;
-
- this.#mousemoveHandler = e => {
- self.blueprint.entity.mousePosition = self.locationFromEvent(e);
- };
-
- this.#trackingMouseStolenHandler = e => {
- if (!self.#mouseTracker) {
- e.preventDefault();
- this.#mouseTracker = e.detail.tracker;
- self.unlistenMouseMove();
- }
- };
-
- this.#trackingMouseGaveBackHandler = e => {
- if (self.#mouseTracker == e.detail.tracker) {
- e.preventDefault();
- self.#mouseTracker = null;
- self.listenMouseMove();
- }
- };
- }
-
- listenMouseMove() {
- this.target.addEventListener("mousemove", this.#mousemoveHandler);
- }
-
- unlistenMouseMove() {
- this.target.removeEventListener("mousemove", this.#mousemoveHandler);
- }
-
- listenEvents() {
- this.listenMouseMove();
- this.blueprint.addEventListener(Configuration.trackingMouseEventName.begin, this.#trackingMouseStolenHandler);
- this.blueprint.addEventListener(Configuration.trackingMouseEventName.end, this.#trackingMouseGaveBackHandler);
- }
-
- unlistenEvents() {
- this.unlistenMouseMove();
- this.blueprint.removeEventListener(Configuration.trackingMouseEventName.begin, this.#trackingMouseStolenHandler);
- this.blueprint.removeEventListener(Configuration.trackingMouseEventName.end, this.#trackingMouseGaveBackHandler);
- }
-}
-
class Paste extends IContext {
#pasteHandle
diff --git a/js/element/LinkElement.js b/js/element/LinkElement.js
index f3995c4..b3f18f3 100644
--- a/js/element/LinkElement.js
+++ b/js/element/LinkElement.js
@@ -175,6 +175,14 @@ export default class LinkElement extends IElement {
this.linkMessageElement = null
}
}
+
+ startDragging() {
+ this.template.applyStartDragging(this)
+ }
+
+ finishDragging() {
+ this.template.applyFinishDragging(this)
+ }
}
customElements.define(LinkElement.tagName, LinkElement)
diff --git a/js/element/PinElement.js b/js/element/PinElement.js
index 13cc437..2951034 100644
--- a/js/element/PinElement.js
+++ b/js/element/PinElement.js
@@ -6,14 +6,23 @@ export default class PinElement extends IElement {
static tagName = "ueb-pin"
+ /** @type {HTMLElement} */
+ clickableElement
+
+ /** @type {String} */
+ #color
+
constructor(entity) {
super(entity, new PinTemplate())
/** @type {import("../entity/PinEntity").default} */
this.entity
/** @type {PinTemplate} */
this.template
- /** @type {HTMLElement} */
- this.clickableElement = null
+ }
+
+ connectedCallback() {
+ super.connectedCallback()
+ this.#color = window.getComputedStyle(this).getPropertyValue("--ueb-node-value-color")
}
createInputObjects() {
@@ -57,6 +66,10 @@ export default class PinElement extends IElement {
return this.clickableElement
}
+ getColor() {
+ return this.#color
+ }
+
/**
* Returns The exact location where the link originates from or arrives at.
* @returns {Number[]} The location array
diff --git a/js/input/mouse/MouseCreateLink.js b/js/input/mouse/MouseCreateLink.js
index 7cc2090..eed2707 100755
--- a/js/input/mouse/MouseCreateLink.js
+++ b/js/input/mouse/MouseCreateLink.js
@@ -42,7 +42,7 @@ export default class MouseCreateLink extends IMouseClickDrag {
startDrag() {
this.link = new LinkElement(this.target, null)
this.link.setLinkMessage(LinkMessageElement.placeNode())
- this.blueprint.nodesContainerElement.insertBefore(this.link, this.blueprint.selectorElement.nextElementSibling)
+ this.blueprint.nodesContainerElement.prepend(this.link)
this.#listenedPins = this.blueprint.querySelectorAll(this.target.constructor.tagName)
this.#listenedPins.forEach(pin => {
if (pin != this.target) {
@@ -50,6 +50,7 @@ export default class MouseCreateLink extends IMouseClickDrag {
pin.getClickableElement().addEventListener("mouseleave", this.#mouseleaveHandler)
}
})
+ this.link.startDragging()
}
dragTo(location, movement) {
@@ -66,10 +67,12 @@ export default class MouseCreateLink extends IMouseClickDrag {
link.getSourcePin() == this.target && link.getDestinationPin() == this.enteredPin
|| link.getSourcePin() == this.enteredPin && link.getDestinationPin() == this.target
)) {
+ this.blueprint.addGraphElement(this.link)
this.link.setDestinationPin(this.enteredPin)
this.link.setLinkMessage(null)
- this.blueprint.addGraphElement(this.link)
+ this.link.finishDragging()
} else {
+ this.link.finishDragging()
this.link.remove()
}
this.link = null
diff --git a/js/template/LinkMessageTemplate.js b/js/template/LinkMessageTemplate.js
index e0406dc..3b74f82 100644
--- a/js/template/LinkMessageTemplate.js
+++ b/js/template/LinkMessageTemplate.js
@@ -1,6 +1,7 @@
import html from "./html"
import ITemplate from "./ITemplate"
import LinkElement from "../element/LinkElement"
+import sanitizeText from "./sanitizeText"
/**
* @typedef {import("../element/LinkMessageElement").default} LinkMessageElement
@@ -14,7 +15,7 @@ export default class LinkMessageTemplate extends ITemplate {
*/
render(linkMessage) {
return html`
-
+
`
}
diff --git a/js/template/LinkTemplate.js b/js/template/LinkTemplate.js
index 527ba2b..a8c5515 100755
--- a/js/template/LinkTemplate.js
+++ b/js/template/LinkTemplate.js
@@ -23,9 +23,13 @@ export default class LinkTemplate extends ITemplate {
* @returns The result html
*/
render(link) {
+ const uniqueId = crypto.randomUUID()
return html`
`
}
@@ -35,17 +39,39 @@ export default class LinkTemplate extends ITemplate {
* @param {LinkElement} link Element of the graph
*/
apply(link) {
- super.apply(link)
- link.classList.add("ueb-positioned")
- link.pathElement = link.querySelector("path")
if (link.linkMessageElement) {
link.appendChild(link.linkMessageElement)
}
+ super.apply(link)
+ link.classList.add("ueb-positioned")
+ link.pathElement = link.querySelector("path")
+ }
+
+ /**
+ *
+ * @param {LinkElement} link element
+ */
+ applyStartDragging(link) {
+ link.blueprint.dataset.creatingLink = true
+ const referencePin = link.getSourcePin() ?? link.getDestinationPin()
+ if (referencePin) {
+ link.style.setProperty("--ueb-node-value-color", referencePin.getColor())
+ }
+ link.classList.add("ueb-link-dragging")
+ }
+
+ /**
+ *
+ * @param {LinkElement} link element
+ */
+ applyFinishDragging(link) {
+ link.blueprint.dataset.creatingLink = false
+ link.classList.remove("ueb-link-dragging")
}
/**
* Applies the style relative to the source pin location.
- * @param {LinkElement} link Link element
+ * @param {LinkElement} link element
*/
applySourceLocation(link) {
link.style.setProperty("--ueb-from-input", link.originatesFromInput ? "0" : "1")
@@ -88,8 +114,8 @@ export default class LinkTemplate extends ITemplate {
const q = p[1] - a / p[0]
return x => a / x + q
}
- const controlPoint = [500, 140]
- c2 = Math.min(c2, getMaxC2(c2Decreasing, controlPoint)(width))
+ const controlPointC2 = [500, 140]
+ c2 = Math.min(c2, getMaxC2(c2Decreasing, controlPointC2)(width))
const d = Configuration.linkRightSVGPath(start, c1, c2)
// TODO move to CSS when Firefox will support property d
link.pathElement.setAttribute("d", d)
diff --git a/scss/ueblueprint-style.scss b/scss/ueblueprint-style.scss
index 6f33a88..461c4bf 100644
--- a/scss/ueblueprint-style.scss
+++ b/scss/ueblueprint-style.scss
@@ -446,22 +446,13 @@ ueb-link svg {
}
ueb-link svg path {
- /* flag stating whether or not the width of the link is below threshold or not */
- --ueb-width-below-threshold: clamp(
- /* min */
- 0,
- /* input */
- calc(var(--ueb-width-threshold) - (var(--ueb-to-x) - var(--ueb-from-x))),
- /* max */
- 1);
- transform : skewX(calc(var(--ueb-width-below-threshold) * var(--ueb-link-skew) * -1rad));
- transform-origin: 0 0;
- stroke-width : 2;
- transition : stroke-width 0.5s;
+ stroke: var(--ueb-node-value-color);
}
-ueb-link svg path:hover {
+ueb-link.ueb-link-dragging svg path,
+ueb-link svg g:hover path {
stroke-width: 5;
+ transition : stroke-width 0.8s;
}
ueb-link-message {
@@ -475,4 +466,5 @@ ueb-link-message {
background : linear-gradient(to bottom, #2a2a2a 0, #151515 50%, #2a2a2a 100%);
color : var(--ueb-node-value-dim-color);
white-space : nowrap;
+ z-index : 1000000;
}
\ No newline at end of file