diff --git a/dist/css/ueblueprint-node-value-type-color.css b/dist/css/ueblueprint-node-value-type-color.css
index 1c84734..f464198 100755
--- a/dist/css/ueblueprint-node-value-type-color.css
+++ b/dist/css/ueblueprint-node-value-type-color.css
@@ -1 +1 @@
-.ueb{--ueb-node-value-color: white}.ueb-node-value-boolean{--ueb-node-value-color: #930000}.ueb-node-value-integer{--ueb-node-value-color: #1fe0ad}.ueb-node-value-float{--ueb-node-value-color: #9ffb44}.ueb-node-value-vector{--ueb-node-value-color: #fcc823}.ueb-node-value-rotator{--ueb-node-value-color: #9eb1fc}.ueb-node-value-string{--ueb-node-value-color: #fc00d2;--ueb-node-value-background: linear-gradient(90deg, #fc00d220, #fc00d280 15%, #fc00d250 85%, transparent)}.ueb-node-value-name{--ueb-node-value-color: #cb81fc}.ueb-node-value-objectreference{--ueb-node-value-color: #00a8f2}/*# sourceMappingURL=ueblueprint-node-value-type-color.css.map */
+.ueb{--ueb-node-value-color: white;--ueb-node-value-dim-color: #afafaf}.ueb-node-value-boolean{--ueb-node-value-color: #930000}.ueb-node-value-integer{--ueb-node-value-color: #1fe0ad}.ueb-node-value-float{--ueb-node-value-color: #9ffb44}.ueb-node-value-vector{--ueb-node-value-color: #fcc823}.ueb-node-value-rotator{--ueb-node-value-color: #9eb1fc}.ueb-node-value-string{--ueb-node-value-color: #fc00d2;--ueb-node-value-background: linear-gradient(90deg, #fc00d220, #fc00d280 15%, #fc00d250 85%, transparent)}.ueb-node-value-name{--ueb-node-value-color: #cb81fc}.ueb-node-value-objectreference{--ueb-node-value-color: #00a8f2}/*# sourceMappingURL=ueblueprint-node-value-type-color.css.map */
diff --git a/dist/css/ueblueprint-node-value-type-color.css.map b/dist/css/ueblueprint-node-value-type-color.css.map
index ae98ccd..95b351b 100755
--- a/dist/css/ueblueprint-node-value-type-color.css.map
+++ b/dist/css/ueblueprint-node-value-type-color.css.map
@@ -1 +1 @@
-{"version":3,"sourceRoot":"","sources":["../../scss/ueblueprint-node-value-type-color.scss"],"names":[],"mappings":"AAAA,KAEI,8BAGJ,wBAEI,gCAGJ,wBAEI,gCAGJ,sBAEI,gCAGJ,uBAEI,gCAGJ,wBAEI,gCAGJ,uBAEI,gCACA,0GAGJ,qBAEI,gCAGJ,gCAEI","file":"ueblueprint-node-value-type-color.css"}
\ No newline at end of file
+{"version":3,"sourceRoot":"","sources":["../../scss/ueblueprint-node-value-type-color.scss"],"names":[],"mappings":"AAAA,KAGI,8BACA,oCAGJ,wBAEI,gCAGJ,wBAEI,gCAGJ,sBAEI,gCAGJ,uBAEI,gCAGJ,wBAEI,gCAGJ,uBAEI,gCACA,0GAGJ,qBAEI,gCAGJ,gCAEI","file":"ueblueprint-node-value-type-color.css"}
\ No newline at end of file
diff --git a/dist/css/ueblueprint-style.css b/dist/css/ueblueprint-style.css
index b45e134..cf79568 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}/*# 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{--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 */
diff --git a/dist/css/ueblueprint-style.css.map b/dist/css/ueblueprint-style.css.map
index 1ad028a..d7496b5 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","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,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
diff --git a/dist/ueblueprint.js b/dist/ueblueprint.js
index d99523f..6ae2048 100755
--- a/dist/ueblueprint.js
+++ b/dist/ueblueprint.js
@@ -4,7 +4,7 @@ class Configuration {
static fontSize = "13px"
static gridAxisLineColor = "black"
static gridLineColor = "#353535"
- static gridLineWidth = 2 // pixel
+ static gridLineWidth = 1 // pixel
static gridSet = 8
static gridSetLineColor = "#161616"
static gridSize = 16 // pixel
@@ -405,7 +405,6 @@ class FastSelectionModel {
this.initialPosition[0] < this.boundaries.primaryP.v && this.initialPosition[0] < finalPosition[0]);
}
-
const secondaryBoundaryCrossed = (index, added) => {
this.selectFunc(this.rectangles[index], added);
this.computeBoundaries(finalPosition);
@@ -563,6 +562,8 @@ class SelectorTemplate extends Template {
class GraphSelector extends GraphElement {
+ static tagName = "ueb-selector"
+
constructor() {
super({}, new SelectorTemplate());
this.selectionModel = null;
@@ -594,7 +595,7 @@ class GraphSelector extends GraphElement {
}
}
-customElements.define("ueb-selector", GraphSelector);
+customElements.define(GraphSelector.tagName, GraphSelector);
/**
* This solves the sole purpose of providing compression capability for html inside template literals strings. Check rollup.config.js function minifyHTML()
@@ -675,7 +676,7 @@ class BlueprintTemplate extends Template {
blueprint.gridElement = blueprint.viewportElement.querySelector(".ueb-grid");
blueprint.nodesContainerElement = blueprint.querySelector("[data-nodes]");
blueprint.selectorElement = new GraphSelector();
- blueprint.nodesContainerElement.append(blueprint.selectorElement, ...blueprint.nodes);
+ blueprint.nodesContainerElement.append(blueprint.selectorElement, ...blueprint.getNodes());
this.applyEndDragScrolling(blueprint);
}
@@ -746,7 +747,6 @@ class Context {
this.blueprint.removeEventListener("blueprint-unfocus", this.blueprintUnfocusHandler);
}
-
/* Subclasses will probabily override the following methods */
listenEvents() {
}
@@ -1519,6 +1519,7 @@ class Copy extends Context {
/**
* @typedef {import("../graph/GraphLink").default} GraphLink
+ * @typedef {import("../graph/GraphLinkMessage").default} GraphLinkMessage
*/
class LinkTemplate extends Template {
@@ -1532,7 +1533,7 @@ class LinkTemplate extends Template {
/**
* Computes the html content of the target element.
- * @param {GraphLink} link Link connecting two graph nodes
+ * @param {GraphLink} link connecting two graph nodes
* @returns The result html
*/
render(link) {
@@ -1551,6 +1552,9 @@ class LinkTemplate extends Template {
super.apply(link);
link.classList.add("ueb-positioned");
link.pathElement = link.querySelector("path");
+ if (link.linkMessageElement) {
+ link.appendChild(link.linkMessageElement);
+ }
}
/**
@@ -1587,11 +1591,12 @@ class LinkTemplate extends Template {
link.style.setProperty("margin-left", `-${start}px`);
}
if (xInverted) {
- start = start + dx;
+ start += fillRatio * 100;
}
+ link.style.setProperty("--ueb-start-percentage", `${100 - start}%`);
const c1 = start + 15 * fillRatio;
let c2 = Math.max(40 / aspectRatio, 30) + start;
- const c2Decreasing = -0.05;
+ const c2Decreasing = -0.06;
const getMaxC2 = (m, p) => {
const a = -m * p[0] * p[0];
const q = p[1] - a / p[0];
@@ -1603,12 +1608,26 @@ class LinkTemplate extends Template {
// TODO move to CSS when Firefox will support property d
link.pathElement.setAttribute("d", d);
}
+
+ /**
+ *
+ * @param {GraphLink} link element
+ * @param {GraphLinkMessage} linkMessage
+ */
+ applyLinkMessage(link, linkMessage) {
+ link.querySelectorAll(linkMessage.constructor.tagName).forEach(element => element.remove());
+ link.appendChild(linkMessage);
+ link.linkMessageElement = linkMessage;
+ }
}
/**
* @typedef {import("./GraphPin").default} GraphPin
+ * @typedef {import("./GraphLinkMessage").default} GraphLinkMessage
*/
class GraphLink extends GraphElement {
+
+ static tagName = "ueb-link"
/** @type {GraphPin} */
#source
/** @type {GraphPin} */
@@ -1616,6 +1635,13 @@ class GraphLink extends GraphElement {
#nodeDeleteHandler
#nodeDragSourceHandler
#nodeDragDestinatonHandler
+ sourceLocation = [0, 0]
+ /** @type {SVGPathElement} */
+ pathElement
+ /** @type {GraphLinkMessage} */
+ linkMessageElement
+ originatesFromInput = false
+ destinationLocation = [0, 0]
/**
* @param {?GraphPin} source
@@ -1625,17 +1651,16 @@ class GraphLink extends GraphElement {
super({}, new LinkTemplate());
/** @type {import("../template/LinkTemplate").default} */
this.template;
- /** @type {SVGPathElement} */
- this.pathElement = null;
- this.originatesFromInput = false;
- this.sourceLocation = [0, 0];
- this.destinationLocation = [0, 0];
const self = this;
- this.#nodeDeleteHandler = _ => self.blueprint.removeGraphElement(self);
+ this.#nodeDeleteHandler = _ => self.remove();
this.#nodeDragSourceHandler = e => self.addSourceLocation(e.detail.value);
this.#nodeDragDestinatonHandler = e => self.addDestinationLocation(e.detail.value);
- this.setSourcePin(source);
- this.setDestinationPin(destination);
+ if (source) {
+ this.setSourcePin(source);
+ }
+ if (destination) {
+ this.setDestinationPin(destination);
+ }
}
/**
@@ -1703,7 +1728,6 @@ class GraphLink extends GraphElement {
this.template.applyFullLocation(this);
}
-
/**
*
* @returns {GraphPin}
@@ -1745,20 +1769,29 @@ class GraphLink extends GraphElement {
*/
setDestinationPin(graphPin) {
if (this.#destination) {
- const nodeElement = this.#source.getGraphNode();
+ const nodeElement = this.#destination.getGraphNode();
nodeElement.removeEventListener(Configuration.nodeDeleteEventName, this.#nodeDeleteHandler);
nodeElement.removeEventListener(Configuration.nodeDragLocalEventName, this.#nodeDragDestinatonHandler);
}
this.#destination = graphPin;
if (this.#destination) {
- const nodeElement = this.#source.getGraphNode();
+ const nodeElement = this.#destination.getGraphNode();
nodeElement.addEventListener(Configuration.nodeDeleteEventName, this.#nodeDeleteHandler);
nodeElement.addEventListener(Configuration.nodeDragLocalEventName, this.#nodeDragDestinatonHandler);
+ this.setDestinationLocation();
}
}
+
+ /**
+ *
+ * @param {GraphLinkMessage} linkMessage
+ */
+ setLinkMessage(linkMessage) {
+ this.template.applyLinkMessage(this, linkMessage);
+ }
}
-customElements.define("ueb-link", GraphLink);
+customElements.define(GraphLink.tagName, GraphLink);
/**
* @typedef {import("../graph/GraphPin").default} GraphPin
@@ -1808,6 +1841,94 @@ class PinTemplate extends Template {
}
}
+/**
+ * @typedef {import("../graph/GraphLinkMessage").default} GraphLinkMessage
+ */
+class LinkMessageTemplate extends Template {
+
+ /**
+ * Computes the html content of the target element.
+ * @param {GraphLinkMessage} linkMessage attached to link destination
+ * @returns The result html
+ */
+ render(linkMessage) {
+ return html`
+
+
+ `
+ }
+
+ /**
+ * Applies the style to the element.
+ * @param {GraphLinkMessage} linkMessage element
+ */
+ apply(linkMessage) {
+ super.apply(linkMessage);
+ linkMessage.linkElement = linkMessage.closest(GraphLink.tagName);
+ linkMessage.querySelector(".ueb-link-message").innerText = linkMessage.message(
+ linkMessage.linkElement.getSourcePin(),
+ linkMessage.linkElement.getDestinationPin()
+ );
+ }
+
+}
+
+/**
+ * @typedef {import("./GraphPin").default} GraphPin
+ * @typedef {import("./GraphLink").default} GraphLink
+ * @typedef {(sourcePin: GraphPin, sourcePin: GraphPin) => String} LinkRetrieval
+ */
+class GraphLinkMessage extends GraphElement {
+
+ static tagName = "ueb-link-message"
+ static CONVERT_TYPE = _ => new GraphLinkMessage(
+ "ueb-icon-conver-type",
+ /** @type {LinkRetrieval} */
+ (s, d) => `Convert ${s.getType()} to ${d.getType()}.`
+ )
+ static DIRECTIONS_INCOMPATIBLE = _ => new GraphLinkMessage(
+ "ueb-icon-directions-incompatible",
+ /** @type {LinkRetrieval} */
+ (s, d) => "Directions are not compatbile."
+ )
+ static PLACE_NODE = _ => new GraphLinkMessage(
+ "ueb-icon-place-node",
+ /** @type {LinkRetrieval} */
+ (s, d) => "Place a new node."
+ )
+ static REPLACE_LiNK = _ => new GraphLinkMessage(
+ "ueb-icon-replace-link",
+ /** @type {LinkRetrieval} */
+ (s, d) => "Replace existing input connections."
+ )
+ static SAME_NODE = _ => new GraphLinkMessage(
+ "ueb-icon-same-node",
+ /** @type {LinkRetrieval} */
+ (s, d) => "Both are on the same node."
+ )
+ static TYPES_INCOMPATIBLE = _ => new GraphLinkMessage(
+ "ueb-icon-types-incompatible",
+ /** @type {LinkRetrieval} */
+ (s, d) => `${s.getType()} is not compatible with ${d.getType()}.`
+ )
+
+ /** @type {String} */
+ icon
+ /** @type {String} */
+ message
+ /** @type {GraphLink} */
+ linkElement
+
+ constructor(icon, message) {
+ super({}, new LinkMessageTemplate());
+ this.icon = icon;
+ this.message = message;
+ }
+
+}
+
+customElements.define(GraphLinkMessage.tagName, GraphLinkMessage);
+
class Pointing extends Context {
constructor(target, blueprint, options) {
@@ -1965,8 +2086,12 @@ class MouseClickDrag extends Pointing {
}
}
+/** @typedef {import("../../graph/GraphPin").default} GraphPin */
class MouseCreateLink extends MouseClickDrag {
+ /** @type {NodeListOf} */
+ #listenedPins
+
/** @type {(e: MouseEvent) => void} */
#mouseenterHandler
@@ -1997,12 +2122,15 @@ class MouseCreateLink extends MouseClickDrag {
startDrag() {
this.link = new GraphLink(this.target, null);
+ this.link.setLinkMessage(GraphLinkMessage.PLACE_NODE());
this.blueprint.nodesContainerElement.insertBefore(this.link, this.blueprint.selectorElement.nextElementSibling);
- this.blueprint.querySelectorAll("ueb-pin." + this.target.isInput() ? "output" : "input")
- .forEach(pin => {
- pin.addEventListener("mouseenter", this.#mouseenterHandler);
- pin.addEventListener("mouseleave", this.#mouseleaveHandler);
- });
+ 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);
+ }
+ });
}
dragTo(location, movement) {
@@ -2010,13 +2138,19 @@ class MouseCreateLink extends MouseClickDrag {
}
endDrag() {
- this.blueprint.querySelectorAll("ueb-pin." + this.target.isInput() ? "output" : "input")
- .forEach(pin => {
- pin.removeEventListener("mouseenter", this.#mouseenterHandler);
- pin.removeEventListener("mouseleave", this.#mouseleaveHandler);
- });
- if (this.enteredPin) {
- this.link.setDestinationPin(this.link);
+ 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.blueprint.addGraphElement(this.link);
+ } else {
+ this.link.remove();
}
this.link = null;
}
@@ -2024,6 +2158,8 @@ class MouseCreateLink extends MouseClickDrag {
class GraphPin extends GraphElement {
+ static tagName = "ueb-pin"
+
constructor(entity) {
super(entity, new PinTemplate());
/** @type {import("../entity/PinEntity").default} */
@@ -2071,6 +2207,10 @@ class GraphPin extends GraphElement {
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
@@ -2084,7 +2224,7 @@ class GraphPin extends GraphElement {
}
}
-customElements.define("ueb-pin", GraphPin);
+customElements.define(GraphPin.tagName, GraphPin);
/**
* @typedef {import("../graph/SelectableDraggable").default} SelectableDraggable
@@ -2118,29 +2258,6 @@ class SelectableDraggableTemplate extends Template {
*/
class NodeTemplate extends SelectableDraggableTemplate {
- /**
- * Computes the html content of the target element.
- * @param {GraphNode} node Graph node element
- * @returns The result html
- */
- header(node) {
- return html`
- `
- }
-
- /**
- * Computes the html content of the target element.
- * @param {GraphNode} node Graph node element
- * @returns The result html
- */
- body(node) {
- let inputs = node.entity.CustomProperties.filter(v => v instanceof PinEntity$1);
- inputs.filter(v => v.isOutput());
- inputs = inputs.filter(v => v.isInput());
- return html`
- `
- }
-
/**
* Computes the html content of the target element.
* @param {GraphNode} node Graph node element
@@ -2314,6 +2431,8 @@ class SelectableDraggable extends GraphElement {
class GraphNode extends SelectableDraggable {
+ static tagName = "ueb-node"
+
/**
*
* @param {ObjectEntity} entity
@@ -2357,15 +2476,15 @@ class GraphNode extends SelectableDraggable {
}
dispatchDeleteEvent(value) {
- let dragEvent = new CustomEvent(Configuration.nodeDragEventName, {
+ let deleteEvent = new CustomEvent(Configuration.nodeDeleteEventName, {
bubbles: true,
cancelable: true,
});
- this.dispatchEvent(dragEvent);
+ this.dispatchEvent(deleteEvent);
}
}
-customElements.define("ueb-node", GraphNode);
+customElements.define(GraphNode.tagName, GraphNode);
let P = Parsimmon;
@@ -2733,55 +2852,57 @@ class Zoom extends MouseWheel {
class Blueprint extends GraphElement {
+ static tagName = "ueb-blueprint"
+ /** @type {number} */
+ gridSize = Configuration.gridSize
+ /** @type {GraphNode[]}" */
+ nodes = []
+ /** @type {GraphLink[]}" */
+ links = []
+ expandGridSize = Configuration.expandGridSize
+ /** @type {number[]} */
+ additional = /*[2 * this.expandGridSize, 2 * this.expandGridSize]*/[0, 0]
+ /** @type {number[]} */
+ translateValue = /*[this.expandGridSize, this.expandGridSize]*/[0, 0]
+ /** @type {number[]} */
+ mousePosition = [0, 0]
+ /** @type {HTMLElement} */
+ gridElement = null
+ /** @type {HTMLElement} */
+ viewportElement = null
+ /** @type {HTMLElement} */
+ overlayElement = null
+ /** @type {GraphSelector} */
+ selectorElement = null
+ /** @type {HTMLElement} */
+ nodesContainerElement = null
+ /** @type {number} */
+ zoom = 0
+ /** @type {HTMLElement} */
+ headerElement = null
+ focused = false
+ /** @type {(node: GraphNode) => BoundariesInfo} */
+ nodeBoundariesSupplier = node => {
+ let rect = node.getBoundingClientRect();
+ let gridRect = this.nodesContainerElement.getBoundingClientRect();
+ const scaleCorrection = 1 / this.getScale();
+ return {
+ primaryInf: (rect.left - gridRect.left) * scaleCorrection,
+ primarySup: (rect.right - gridRect.right) * scaleCorrection,
+ // Counter intuitive here: the y (secondary axis is positive towards the bottom, therefore upper bound "sup" is bottom)
+ secondaryInf: (rect.top - gridRect.top) * scaleCorrection,
+ secondarySup: (rect.bottom - gridRect.bottom) * scaleCorrection
+ }
+ }
+ /** @type {(node: GraphNode, selected: bool) => void}} */
+ nodeSelectToggleFunction = (node, selected) => {
+ node.setSelected(selected);
+ }
+
constructor() {
super({}, new BlueprintTemplate());
/** @type {BlueprintTemplate} */
this.template;
- /** @type {number} */
- this.gridSize = Configuration.gridSize;
- /** @type {GraphNode[]}" */
- this.nodes = [];
- /** @type {GraphLink[]}" */
- this.links = [];
- this.expandGridSize = Configuration.expandGridSize;
- /** @type {number[]} */
- this.additional = /*[2 * this.expandGridSize, 2 * this.expandGridSize]*/[0, 0];
- /** @type {number[]} */
- this.translateValue = /*[this.expandGridSize, this.expandGridSize]*/[0, 0];
- /** @type {number[]} */
- this.mousePosition = [0, 0];
- /** @type {HTMLElement} */
- this.gridElement = null;
- /** @type {HTMLElement} */
- this.viewportElement = null;
- /** @type {HTMLElement} */
- this.overlayElement = null;
- /** @type {GraphSelector} */
- this.selectorElement = null;
- /** @type {HTMLElement} */
- this.nodesContainerElement = null;
- /** @type {number} */
- this.zoom = 0;
- /** @type {HTMLElement} */
- this.headerElement = null;
- this.focused = false;
- /** @type {(node: GraphNode) => BoundariesInfo} */
- this.nodeBoundariesSupplier = node => {
- let rect = node.getBoundingClientRect();
- let gridRect = this.nodesContainerElement.getBoundingClientRect();
- const scaleCorrection = 1 / this.getScale();
- return {
- primaryInf: (rect.left - gridRect.left) * scaleCorrection,
- primarySup: (rect.right - gridRect.right) * scaleCorrection,
- // Counter intuitive here: the y (secondary axis is positive towards the bottom, therefore upper bound "sup" is bottom)
- secondaryInf: (rect.top - gridRect.top) * scaleCorrection,
- secondarySup: (rect.bottom - gridRect.bottom) * scaleCorrection
- }
- };
- /** @type {(node: GraphNode, selected: bool) => void}} */
- this.nodeSelectToggleFunction = (node, selected) => {
- node.setSelected(selected);
- };
}
/**
@@ -2974,7 +3095,6 @@ class Blueprint extends GraphElement {
this.template.applyZoom(this, zoom);
this.zoom = zoom;
-
if (center) {
center[0] += this.translateValue[0];
center[1] += this.translateValue[1];
@@ -3001,16 +3121,12 @@ class Blueprint extends GraphElement {
}
/**
- *
+ * Returns the list of nodes in this blueprint. It can filter the list providing just the selected ones.
* @returns {GraphNode[]} Nodes
*/
getNodes(selected = false) {
if (selected) {
return this.nodes.filter(
- /**
- *
- * @param {GraphNode} node
- */
node => node.selected
)
} else {
@@ -3018,18 +3134,26 @@ class Blueprint extends GraphElement {
}
}
+ /**
+ * Returns the list of links in this blueprint.
+ * @returns {GraphLink[]} Nodes
+ */
+ getLinks() {
+ return this.links
+ }
+
/**
* Select all nodes
*/
selectAll() {
- this.nodes.forEach(node => this.nodeSelectToggleFunction(node, true));
+ this.getNodes().forEach(node => this.nodeSelectToggleFunction(node, true));
}
/**
* Unselect all nodes
*/
unselectAll() {
- this.nodes.forEach(node => this.nodeSelectToggleFunction(node, false));
+ this.getNodes().forEach(node => this.nodeSelectToggleFunction(node, false));
}
/**
@@ -3037,16 +3161,23 @@ class Blueprint extends GraphElement {
* @param {...GraphElement} graphElements
*/
addGraphElement(...graphElements) {
- [...graphElements].forEach(v => {
- if (v instanceof GraphNode) {
- this.nodes.push(v);
- this.nodesContainerElement?.appendChild(v);
- }
- if (v instanceof GraphLink) {
- this.links.push(v);
- this.nodesContainerElement?.appendChild(v);
- }
- });
+ if (this.nodesContainerElement) {
+ graphElements.forEach(element => {
+ if (element.closest(Blueprint.tagName) != this) {
+ this.nodesContainerElement.appendChild(element);
+ }
+ this.nodes = [...this.querySelectorAll(GraphNode.tagName)];
+ this.links = [...this.querySelectorAll(GraphLink.tagName)];
+ });
+ } else {
+ graphElements.forEach(element => {
+ if (element instanceof GraphNode) {
+ this.nodes.push(element);
+ } else if (element instanceof GraphLink) {
+ this.links.push(element);
+ }
+ });
+ }
}
/**
@@ -3054,28 +3185,17 @@ class Blueprint extends GraphElement {
* @param {...GraphElement} graphElements
*/
removeGraphElement(...graphElements) {
- let deleteElements = [...graphElements];
- if (deleteElements.length == 0) {
- return
+ let removed = false;
+ graphElements.forEach(element => {
+ if (element.closest(Blueprint.tagName) == this) {
+ element.remove();
+ removed = false;
+ }
+ });
+ if (removed) {
+ this.nodes = [...this.querySelectorAll(GraphNode.tagName)];
+ this.links = [...this.querySelectorAll(GraphLink.tagName)];
}
- let currentDeleteI = 0;
- this.nodes = this.nodes.filter(v => {
- if (v == deleteElements[currentDeleteI]) {
- ++currentDeleteI;
- v.remove();
- return false
- }
- return true
- });
- currentDeleteI = 0;
- this.links = this.links.filter(v => {
- if (v == deleteElements[currentDeleteI]) {
- ++currentDeleteI;
- v.remove();
- return false
- }
- return true
- });
}
setFocused(value = true) {
@@ -3092,7 +3212,7 @@ class Blueprint extends GraphElement {
}
}
-customElements.define("ueb-blueprint", Blueprint);
+customElements.define(Blueprint.tagName, Blueprint);
class GeneralSerializer extends Serializer {
diff --git a/js/Blueprint.js b/js/Blueprint.js
index 81c71e3..061b28c 100755
--- a/js/Blueprint.js
+++ b/js/Blueprint.js
@@ -17,55 +17,57 @@ import Zoom from "./input/mouse/Zoom"
export default class Blueprint extends GraphElement {
+ static tagName = "ueb-blueprint"
+ /** @type {number} */
+ gridSize = Configuration.gridSize
+ /** @type {GraphNode[]}" */
+ nodes = []
+ /** @type {GraphLink[]}" */
+ links = []
+ expandGridSize = Configuration.expandGridSize
+ /** @type {number[]} */
+ additional = /*[2 * this.expandGridSize, 2 * this.expandGridSize]*/[0, 0]
+ /** @type {number[]} */
+ translateValue = /*[this.expandGridSize, this.expandGridSize]*/[0, 0]
+ /** @type {number[]} */
+ mousePosition = [0, 0]
+ /** @type {HTMLElement} */
+ gridElement = null
+ /** @type {HTMLElement} */
+ viewportElement = null
+ /** @type {HTMLElement} */
+ overlayElement = null
+ /** @type {GraphSelector} */
+ selectorElement = null
+ /** @type {HTMLElement} */
+ nodesContainerElement = null
+ /** @type {number} */
+ zoom = 0
+ /** @type {HTMLElement} */
+ headerElement = null
+ focused = false
+ /** @type {(node: GraphNode) => BoundariesInfo} */
+ nodeBoundariesSupplier = node => {
+ let rect = node.getBoundingClientRect()
+ let gridRect = this.nodesContainerElement.getBoundingClientRect()
+ const scaleCorrection = 1 / this.getScale()
+ return {
+ primaryInf: (rect.left - gridRect.left) * scaleCorrection,
+ primarySup: (rect.right - gridRect.right) * scaleCorrection,
+ // Counter intuitive here: the y (secondary axis is positive towards the bottom, therefore upper bound "sup" is bottom)
+ secondaryInf: (rect.top - gridRect.top) * scaleCorrection,
+ secondarySup: (rect.bottom - gridRect.bottom) * scaleCorrection
+ }
+ }
+ /** @type {(node: GraphNode, selected: bool) => void}} */
+ nodeSelectToggleFunction = (node, selected) => {
+ node.setSelected(selected)
+ }
+
constructor() {
super({}, new BlueprintTemplate())
/** @type {BlueprintTemplate} */
this.template
- /** @type {number} */
- this.gridSize = Configuration.gridSize
- /** @type {GraphNode[]}" */
- this.nodes = []
- /** @type {GraphLink[]}" */
- this.links = []
- this.expandGridSize = Configuration.expandGridSize
- /** @type {number[]} */
- this.additional = /*[2 * this.expandGridSize, 2 * this.expandGridSize]*/[0, 0]
- /** @type {number[]} */
- this.translateValue = /*[this.expandGridSize, this.expandGridSize]*/[0, 0]
- /** @type {number[]} */
- this.mousePosition = [0, 0]
- /** @type {HTMLElement} */
- this.gridElement = null
- /** @type {HTMLElement} */
- this.viewportElement = null
- /** @type {HTMLElement} */
- this.overlayElement = null
- /** @type {GraphSelector} */
- this.selectorElement = null
- /** @type {HTMLElement} */
- this.nodesContainerElement = null
- /** @type {number} */
- this.zoom = 0
- /** @type {HTMLElement} */
- this.headerElement = null
- this.focused = false
- /** @type {(node: GraphNode) => BoundariesInfo} */
- this.nodeBoundariesSupplier = node => {
- let rect = node.getBoundingClientRect()
- let gridRect = this.nodesContainerElement.getBoundingClientRect()
- const scaleCorrection = 1 / this.getScale()
- return {
- primaryInf: (rect.left - gridRect.left) * scaleCorrection,
- primarySup: (rect.right - gridRect.right) * scaleCorrection,
- // Counter intuitive here: the y (secondary axis is positive towards the bottom, therefore upper bound "sup" is bottom)
- secondaryInf: (rect.top - gridRect.top) * scaleCorrection,
- secondarySup: (rect.bottom - gridRect.bottom) * scaleCorrection
- }
- }
- /** @type {(node: GraphNode, selected: bool) => void}} */
- this.nodeSelectToggleFunction = (node, selected) => {
- node.setSelected(selected)
- }
}
/**
@@ -258,7 +260,6 @@ export default class Blueprint extends GraphElement {
this.template.applyZoom(this, zoom)
this.zoom = zoom
-
if (center) {
center[0] += this.translateValue[0]
center[1] += this.translateValue[1]
@@ -285,16 +286,12 @@ export default class Blueprint extends GraphElement {
}
/**
- *
+ * Returns the list of nodes in this blueprint. It can filter the list providing just the selected ones.
* @returns {GraphNode[]} Nodes
*/
getNodes(selected = false) {
if (selected) {
return this.nodes.filter(
- /**
- *
- * @param {GraphNode} node
- */
node => node.selected
)
} else {
@@ -302,18 +299,26 @@ export default class Blueprint extends GraphElement {
}
}
+ /**
+ * Returns the list of links in this blueprint.
+ * @returns {GraphLink[]} Nodes
+ */
+ getLinks() {
+ return this.links
+ }
+
/**
* Select all nodes
*/
selectAll() {
- this.nodes.forEach(node => this.nodeSelectToggleFunction(node, true))
+ this.getNodes().forEach(node => this.nodeSelectToggleFunction(node, true))
}
/**
* Unselect all nodes
*/
unselectAll() {
- this.nodes.forEach(node => this.nodeSelectToggleFunction(node, false))
+ this.getNodes().forEach(node => this.nodeSelectToggleFunction(node, false))
}
/**
@@ -321,16 +326,23 @@ export default class Blueprint extends GraphElement {
* @param {...GraphElement} graphElements
*/
addGraphElement(...graphElements) {
- [...graphElements].forEach(v => {
- if (v instanceof GraphNode) {
- this.nodes.push(v)
- this.nodesContainerElement?.appendChild(v)
- }
- if (v instanceof GraphLink) {
- this.links.push(v)
- this.nodesContainerElement?.appendChild(v)
- }
- })
+ if (this.nodesContainerElement) {
+ graphElements.forEach(element => {
+ if (element.closest(Blueprint.tagName) != this) {
+ this.nodesContainerElement.appendChild(element)
+ }
+ this.nodes = [...this.querySelectorAll(GraphNode.tagName)]
+ this.links = [...this.querySelectorAll(GraphLink.tagName)]
+ })
+ } else {
+ graphElements.forEach(element => {
+ if (element instanceof GraphNode) {
+ this.nodes.push(element)
+ } else if (element instanceof GraphLink) {
+ this.links.push(element)
+ }
+ })
+ }
}
/**
@@ -338,28 +350,17 @@ export default class Blueprint extends GraphElement {
* @param {...GraphElement} graphElements
*/
removeGraphElement(...graphElements) {
- let deleteElements = [...graphElements]
- if (deleteElements.length == 0) {
- return
+ let removed = false
+ graphElements.forEach(element => {
+ if (element.closest(Blueprint.tagName) == this) {
+ element.remove()
+ removed = false
+ }
+ })
+ if (removed) {
+ this.nodes = [...this.querySelectorAll(GraphNode.tagName)]
+ this.links = [...this.querySelectorAll(GraphLink.tagName)]
}
- let currentDeleteI = 0
- this.nodes = this.nodes.filter(v => {
- if (v == deleteElements[currentDeleteI]) {
- ++currentDeleteI
- v.remove()
- return false
- }
- return true
- })
- currentDeleteI = 0
- this.links = this.links.filter(v => {
- if (v == deleteElements[currentDeleteI]) {
- ++currentDeleteI
- v.remove()
- return false
- }
- return true
- })
}
setFocused(value = true) {
@@ -376,4 +377,4 @@ export default class Blueprint extends GraphElement {
}
}
-customElements.define("ueb-blueprint", Blueprint)
+customElements.define(Blueprint.tagName, Blueprint)
diff --git a/js/Configuration.js b/js/Configuration.js
index 5912da1..7762598 100755
--- a/js/Configuration.js
+++ b/js/Configuration.js
@@ -4,7 +4,7 @@ export default class Configuration {
static fontSize = "13px"
static gridAxisLineColor = "black"
static gridLineColor = "#353535"
- static gridLineWidth = 2 // pixel
+ static gridLineWidth = 1 // pixel
static gridSet = 8
static gridSetLineColor = "#161616"
static gridSize = 16 // pixel
diff --git a/js/graph/GraphLink.js b/js/graph/GraphLink.js
index d1f92a9..155a341 100755
--- a/js/graph/GraphLink.js
+++ b/js/graph/GraphLink.js
@@ -2,11 +2,13 @@ import GraphElement from "./GraphElement"
import LinkTemplate from "../template/LinkTemplate"
import Configuration from "../Configuration"
-
/**
* @typedef {import("./GraphPin").default} GraphPin
+ * @typedef {import("./GraphLinkMessage").default} GraphLinkMessage
*/
export default class GraphLink extends GraphElement {
+
+ static tagName = "ueb-link"
/** @type {GraphPin} */
#source
/** @type {GraphPin} */
@@ -14,6 +16,13 @@ export default class GraphLink extends GraphElement {
#nodeDeleteHandler
#nodeDragSourceHandler
#nodeDragDestinatonHandler
+ sourceLocation = [0, 0]
+ /** @type {SVGPathElement} */
+ pathElement
+ /** @type {GraphLinkMessage} */
+ linkMessageElement
+ originatesFromInput = false
+ destinationLocation = [0, 0]
/**
* @param {?GraphPin} source
@@ -23,17 +32,16 @@ export default class GraphLink extends GraphElement {
super({}, new LinkTemplate())
/** @type {import("../template/LinkTemplate").default} */
this.template
- /** @type {SVGPathElement} */
- this.pathElement = null
- this.originatesFromInput = false
- this.sourceLocation = [0, 0]
- this.destinationLocation = [0, 0]
const self = this
- this.#nodeDeleteHandler = _ => self.blueprint.removeGraphElement(self)
+ this.#nodeDeleteHandler = _ => self.remove()
this.#nodeDragSourceHandler = e => self.addSourceLocation(e.detail.value)
this.#nodeDragDestinatonHandler = e => self.addDestinationLocation(e.detail.value)
- this.setSourcePin(source)
- this.setDestinationPin(destination)
+ if (source) {
+ this.setSourcePin(source)
+ }
+ if (destination) {
+ this.setDestinationPin(destination)
+ }
}
/**
@@ -101,7 +109,6 @@ export default class GraphLink extends GraphElement {
this.template.applyFullLocation(this)
}
-
/**
*
* @returns {GraphPin}
@@ -143,17 +150,26 @@ export default class GraphLink extends GraphElement {
*/
setDestinationPin(graphPin) {
if (this.#destination) {
- const nodeElement = this.#source.getGraphNode()
+ const nodeElement = this.#destination.getGraphNode()
nodeElement.removeEventListener(Configuration.nodeDeleteEventName, this.#nodeDeleteHandler)
nodeElement.removeEventListener(Configuration.nodeDragLocalEventName, this.#nodeDragDestinatonHandler)
}
this.#destination = graphPin
if (this.#destination) {
- const nodeElement = this.#source.getGraphNode()
+ const nodeElement = this.#destination.getGraphNode()
nodeElement.addEventListener(Configuration.nodeDeleteEventName, this.#nodeDeleteHandler)
nodeElement.addEventListener(Configuration.nodeDragLocalEventName, this.#nodeDragDestinatonHandler)
+ this.setDestinationLocation()
}
}
+
+ /**
+ *
+ * @param {GraphLinkMessage} linkMessage
+ */
+ setLinkMessage(linkMessage) {
+ this.template.applyLinkMessage(this, linkMessage)
+ }
}
-customElements.define("ueb-link", GraphLink)
+customElements.define(GraphLink.tagName, GraphLink)
diff --git a/js/graph/GraphLinkMessage.js b/js/graph/GraphLinkMessage.js
new file mode 100644
index 0000000..c4d5314
--- /dev/null
+++ b/js/graph/GraphLinkMessage.js
@@ -0,0 +1,58 @@
+import LinkMessageTemplate from "../template/LinkMessageTemplate";
+import GraphElement from "./GraphElement";
+
+/**
+ * @typedef {import("./GraphPin").default} GraphPin
+ * @typedef {import("./GraphLink").default} GraphLink
+ * @typedef {(sourcePin: GraphPin, sourcePin: GraphPin) => String} LinkRetrieval
+ */
+export default class GraphLinkMessage extends GraphElement {
+
+ static tagName = "ueb-link-message"
+ static CONVERT_TYPE = _ => new GraphLinkMessage(
+ "ueb-icon-conver-type",
+ /** @type {LinkRetrieval} */
+ (s, d) => `Convert ${s.getType()} to ${d.getType()}.`
+ )
+ static DIRECTIONS_INCOMPATIBLE = _ => new GraphLinkMessage(
+ "ueb-icon-directions-incompatible",
+ /** @type {LinkRetrieval} */
+ (s, d) => "Directions are not compatbile."
+ )
+ static PLACE_NODE = _ => new GraphLinkMessage(
+ "ueb-icon-place-node",
+ /** @type {LinkRetrieval} */
+ (s, d) => "Place a new node."
+ )
+ static REPLACE_LiNK = _ => new GraphLinkMessage(
+ "ueb-icon-replace-link",
+ /** @type {LinkRetrieval} */
+ (s, d) => "Replace existing input connections."
+ )
+ static SAME_NODE = _ => new GraphLinkMessage(
+ "ueb-icon-same-node",
+ /** @type {LinkRetrieval} */
+ (s, d) => "Both are on the same node."
+ )
+ static TYPES_INCOMPATIBLE = _ => new GraphLinkMessage(
+ "ueb-icon-types-incompatible",
+ /** @type {LinkRetrieval} */
+ (s, d) => `${s.getType()} is not compatible with ${d.getType()}.`
+ )
+
+ /** @type {String} */
+ icon
+ /** @type {String} */
+ message
+ /** @type {GraphLink} */
+ linkElement
+
+ constructor(icon, message) {
+ super({}, new LinkMessageTemplate())
+ this.icon = icon
+ this.message = message
+ }
+
+}
+
+customElements.define(GraphLinkMessage.tagName, GraphLinkMessage)
diff --git a/js/graph/GraphNode.js b/js/graph/GraphNode.js
index 4a9749c..65983ee 100755
--- a/js/graph/GraphNode.js
+++ b/js/graph/GraphNode.js
@@ -7,6 +7,8 @@ import Configuration from "../Configuration"
export default class GraphNode extends SelectableDraggable {
+ static tagName = "ueb-node"
+
/**
*
* @param {ObjectEntity} entity
@@ -50,12 +52,12 @@ export default class GraphNode extends SelectableDraggable {
}
dispatchDeleteEvent(value) {
- let dragEvent = new CustomEvent(Configuration.nodeDragEventName, {
+ let deleteEvent = new CustomEvent(Configuration.nodeDeleteEventName, {
bubbles: true,
cancelable: true,
})
- this.dispatchEvent(dragEvent)
+ this.dispatchEvent(deleteEvent)
}
}
-customElements.define("ueb-node", GraphNode)
+customElements.define(GraphNode.tagName, GraphNode)
diff --git a/js/graph/GraphPin.js b/js/graph/GraphPin.js
index eb95596..a2746ce 100755
--- a/js/graph/GraphPin.js
+++ b/js/graph/GraphPin.js
@@ -4,6 +4,8 @@ import MouseCreateLink from "../input/mouse/MouseCreateLink"
export default class GraphPin extends GraphElement {
+ static tagName = "ueb-pin"
+
constructor(entity) {
super(entity, new PinTemplate())
/** @type {import("../entity/PinEntity").default} */
@@ -51,6 +53,10 @@ export default class GraphPin extends GraphElement {
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
@@ -64,4 +70,4 @@ export default class GraphPin extends GraphElement {
}
}
-customElements.define("ueb-pin", GraphPin)
+customElements.define(GraphPin.tagName, GraphPin)
diff --git a/js/graph/GraphSelector.js b/js/graph/GraphSelector.js
index 7b4b547..937ac70 100755
--- a/js/graph/GraphSelector.js
+++ b/js/graph/GraphSelector.js
@@ -4,6 +4,8 @@ import SelectorTemplate from "../template/SelectorTemplate"
export default class GraphSelector extends GraphElement {
+ static tagName = "ueb-selector"
+
constructor() {
super({}, new SelectorTemplate())
this.selectionModel = null
@@ -35,4 +37,4 @@ export default class GraphSelector extends GraphElement {
}
}
-customElements.define("ueb-selector", GraphSelector)
+customElements.define(GraphSelector.tagName, GraphSelector)
diff --git a/js/input/Context.js b/js/input/Context.js
index 123c443..0960c27 100755
--- a/js/input/Context.js
+++ b/js/input/Context.js
@@ -21,7 +21,6 @@ export default class Context {
this.blueprint.removeEventListener("blueprint-unfocus", this.blueprintUnfocusHandler)
}
-
/* Subclasses will probabily override the following methods */
listenEvents() {
}
diff --git a/js/input/keybaord/KeyboardCanc.js b/js/input/keybaord/KeyboardCanc.js
index bb86f4b..a329062 100755
--- a/js/input/keybaord/KeyboardCanc.js
+++ b/js/input/keybaord/KeyboardCanc.js
@@ -1,7 +1,6 @@
import KeyboardShortcut from "./KeyboardShortcut"
import Configuration from "../../Configuration"
-
export default class KeyvoardCanc extends KeyboardShortcut {
/**
diff --git a/js/input/keybaord/KeyboardSelectAll.js b/js/input/keybaord/KeyboardSelectAll.js
index 5e21f7f..15bee2d 100755
--- a/js/input/keybaord/KeyboardSelectAll.js
+++ b/js/input/keybaord/KeyboardSelectAll.js
@@ -1,7 +1,6 @@
import KeyboardShortcut from "./KeyboardShortcut"
import Configuration from "../../Configuration"
-
export default class KeyboardSelectAll extends KeyboardShortcut {
/**
diff --git a/js/input/mouse/MouseCreateLink.js b/js/input/mouse/MouseCreateLink.js
index 50188ee..8b3a43d 100755
--- a/js/input/mouse/MouseCreateLink.js
+++ b/js/input/mouse/MouseCreateLink.js
@@ -1,8 +1,13 @@
import GraphLink from "../../graph/GraphLink"
+import GraphLinkMessage from "../../graph/GraphLinkMessage"
import MouseClickDrag from "./MouseClickDrag"
+/** @typedef {import("../../graph/GraphPin").default} GraphPin */
export default class MouseCreateLink extends MouseClickDrag {
+ /** @type {NodeListOf} */
+ #listenedPins
+
/** @type {(e: MouseEvent) => void} */
#mouseenterHandler
@@ -33,12 +38,15 @@ export default class MouseCreateLink extends MouseClickDrag {
startDrag() {
this.link = new GraphLink(this.target, null)
+ this.link.setLinkMessage(GraphLinkMessage.PLACE_NODE())
this.blueprint.nodesContainerElement.insertBefore(this.link, this.blueprint.selectorElement.nextElementSibling)
- this.blueprint.querySelectorAll("ueb-pin." + this.target.isInput() ? "output" : "input")
- .forEach(pin => {
- pin.addEventListener("mouseenter", this.#mouseenterHandler)
- pin.addEventListener("mouseleave", this.#mouseleaveHandler)
- })
+ 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)
+ }
+ })
}
dragTo(location, movement) {
@@ -46,15 +54,19 @@ export default class MouseCreateLink extends MouseClickDrag {
}
endDrag() {
- this.blueprint.querySelectorAll("ueb-pin." + this.target.isInput() ? "output" : "input")
- .forEach(pin => {
- pin.removeEventListener("mouseenter", this.#mouseenterHandler)
- pin.removeEventListener("mouseleave", this.#mouseleaveHandler)
- })
- if (this.enteredPin) {
- this.link.setDestinationPin(this.link)
+ 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.blueprint.addGraphElement(this.link)
} else {
- // this.link.remove()
+ this.link.remove()
}
this.link = null
}
diff --git a/js/selection/FastSelectionModel.js b/js/selection/FastSelectionModel.js
index cd4271c..614cc06 100755
--- a/js/selection/FastSelectionModel.js
+++ b/js/selection/FastSelectionModel.js
@@ -140,7 +140,6 @@ export default class FastSelectionModel {
this.initialPosition[0] < this.boundaries.primaryP.v && this.initialPosition[0] < finalPosition[0])
}
-
const secondaryBoundaryCrossed = (index, added) => {
this.selectFunc(this.rectangles[index], added)
this.computeBoundaries(finalPosition)
diff --git a/js/template/BlueprintTemplate.js b/js/template/BlueprintTemplate.js
index e898223..07fa420 100755
--- a/js/template/BlueprintTemplate.js
+++ b/js/template/BlueprintTemplate.js
@@ -78,7 +78,7 @@ export default class BlueprintTemplate extends Template {
blueprint.gridElement = blueprint.viewportElement.querySelector(".ueb-grid")
blueprint.nodesContainerElement = blueprint.querySelector("[data-nodes]")
blueprint.selectorElement = new GraphSelector()
- blueprint.nodesContainerElement.append(blueprint.selectorElement, ...blueprint.nodes)
+ blueprint.nodesContainerElement.append(blueprint.selectorElement, ...blueprint.getNodes())
this.applyEndDragScrolling(blueprint)
}
diff --git a/js/template/LinkMessageTemplate.js b/js/template/LinkMessageTemplate.js
new file mode 100644
index 0000000..4788cbe
--- /dev/null
+++ b/js/template/LinkMessageTemplate.js
@@ -0,0 +1,35 @@
+import GraphLink from "../graph/GraphLink"
+import html from "./html"
+import Template from "./Template"
+
+/**
+ * @typedef {import("../graph/GraphLinkMessage").default} GraphLinkMessage
+ */
+export default class LinkMessageTemplate extends Template {
+
+ /**
+ * Computes the html content of the target element.
+ * @param {GraphLinkMessage} linkMessage attached to link destination
+ * @returns The result html
+ */
+ render(linkMessage) {
+ return html`
+
+
+ `
+ }
+
+ /**
+ * Applies the style to the element.
+ * @param {GraphLinkMessage} linkMessage element
+ */
+ apply(linkMessage) {
+ super.apply(linkMessage)
+ linkMessage.linkElement = linkMessage.closest(GraphLink.tagName)
+ linkMessage.querySelector(".ueb-link-message").innerText = linkMessage.message(
+ linkMessage.linkElement.getSourcePin(),
+ linkMessage.linkElement.getDestinationPin()
+ )
+ }
+
+}
\ No newline at end of file
diff --git a/js/template/LinkTemplate.js b/js/template/LinkTemplate.js
index 3bf23f3..45bd3c8 100755
--- a/js/template/LinkTemplate.js
+++ b/js/template/LinkTemplate.js
@@ -2,10 +2,10 @@ import html from "./html"
import sanitizeText from "./sanitizeText"
import Template from "./Template"
import Configuration from "../Configuration"
-import Utility from "../Utility"
/**
* @typedef {import("../graph/GraphLink").default} GraphLink
+ * @typedef {import("../graph/GraphLinkMessage").default} GraphLinkMessage
*/
export default class LinkTemplate extends Template {
@@ -19,7 +19,7 @@ export default class LinkTemplate extends Template {
/**
* Computes the html content of the target element.
- * @param {GraphLink} link Link connecting two graph nodes
+ * @param {GraphLink} link connecting two graph nodes
* @returns The result html
*/
render(link) {
@@ -38,6 +38,9 @@ export default class LinkTemplate extends Template {
super.apply(link)
link.classList.add("ueb-positioned")
link.pathElement = link.querySelector("path")
+ if (link.linkMessageElement) {
+ link.appendChild(link.linkMessageElement)
+ }
}
/**
@@ -74,11 +77,12 @@ export default class LinkTemplate extends Template {
link.style.setProperty("margin-left", `-${start}px`)
}
if (xInverted) {
- start = start + fillRatio * 100
+ start += fillRatio * 100
}
+ link.style.setProperty("--ueb-start-percentage", `${100 - start}%`)
const c1 = start + 15 * fillRatio
let c2 = Math.max(40 / aspectRatio, 30) + start
- const c2Decreasing = -0.05
+ const c2Decreasing = -0.06
const getMaxC2 = (m, p) => {
const a = -m * p[0] * p[0]
const q = p[1] - a / p[0]
@@ -90,4 +94,15 @@ export default class LinkTemplate extends Template {
// TODO move to CSS when Firefox will support property d
link.pathElement.setAttribute("d", d)
}
+
+ /**
+ *
+ * @param {GraphLink} link element
+ * @param {GraphLinkMessage} linkMessage
+ */
+ applyLinkMessage(link, linkMessage) {
+ link.querySelectorAll(linkMessage.constructor.tagName).forEach(element => element.remove())
+ link.appendChild(linkMessage)
+ link.linkMessageElement = linkMessage
+ }
}
diff --git a/js/template/NodeTemplate.js b/js/template/NodeTemplate.js
index fa639ab..0c46a79 100755
--- a/js/template/NodeTemplate.js
+++ b/js/template/NodeTemplate.js
@@ -9,29 +9,6 @@ import SelectableDraggableTemplate from "./SelectableDraggableTemplate"
*/
export default class NodeTemplate extends SelectableDraggableTemplate {
- /**
- * Computes the html content of the target element.
- * @param {GraphNode} node Graph node element
- * @returns The result html
- */
- header(node) {
- return html`
- `
- }
-
- /**
- * Computes the html content of the target element.
- * @param {GraphNode} node Graph node element
- * @returns The result html
- */
- body(node) {
- let inputs = node.entity.CustomProperties.filter(v => v instanceof PinEntity)
- let outputs = inputs.filter(v => v.isOutput())
- inputs = inputs.filter(v => v.isInput())
- return html`
- `
- }
-
/**
* Computes the html content of the target element.
* @param {GraphNode} node Graph node element
diff --git a/scss/ueblueprint-node-value-type-color.scss b/scss/ueblueprint-node-value-type-color.scss
index 201db48..a499bb9 100755
--- a/scss/ueblueprint-node-value-type-color.scss
+++ b/scss/ueblueprint-node-value-type-color.scss
@@ -1,6 +1,8 @@
.ueb {
- $ueb-node-value-color : white;
- --ueb-node-value-color: #{$ueb-node-value-color};
+ $ueb-node-value-color : white;
+ $ueb-node-value-dim-color : #afafaf;
+ --ueb-node-value-color : #{$ueb-node-value-color};
+ --ueb-node-value-dim-color: #{$ueb-node-value-dim-color};
}
.ueb-node-value-boolean {
diff --git a/scss/ueblueprint-style.scss b/scss/ueblueprint-style.scss
index b0cafff..6f33a88 100644
--- a/scss/ueblueprint-style.scss
+++ b/scss/ueblueprint-style.scss
@@ -462,4 +462,17 @@ ueb-link svg path {
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;
}
\ No newline at end of file