From 96f0d593e706c703cb38002e096ef31c72262c29 Mon Sep 17 00:00:00 2001 From: barsdeveloper Date: Sun, 27 Feb 2022 11:54:40 +0100 Subject: [PATCH] Links improved, refactoring --- .../css/ueblueprint-node-value-type-color.css | 2 +- .../ueblueprint-node-value-type-color.css.map | 2 +- dist/css/ueblueprint-style.css | 2 +- dist/css/ueblueprint-style.css.map | 2 +- dist/ueblueprint.js | 408 +++++++++++------- js/Blueprint.js | 171 ++++---- js/Configuration.js | 2 +- js/graph/GraphLink.js | 42 +- js/graph/GraphLinkMessage.js | 58 +++ js/graph/GraphNode.js | 8 +- js/graph/GraphPin.js | 8 +- js/graph/GraphSelector.js | 4 +- js/input/Context.js | 1 - js/input/keybaord/KeyboardCanc.js | 1 - js/input/keybaord/KeyboardSelectAll.js | 1 - js/input/mouse/MouseCreateLink.js | 38 +- js/selection/FastSelectionModel.js | 1 - js/template/BlueprintTemplate.js | 2 +- js/template/LinkMessageTemplate.js | 35 ++ js/template/LinkTemplate.js | 23 +- js/template/NodeTemplate.js | 23 - scss/ueblueprint-node-value-type-color.scss | 6 +- scss/ueblueprint-style.scss | 13 + 23 files changed, 554 insertions(+), 299 deletions(-) create mode 100644 js/graph/GraphLinkMessage.js create mode 100644 js/template/LinkMessageTemplate.js 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