diff --git a/font/roboto-bold.woff b/assets/fonts/roboto-bold.woff old mode 100755 new mode 100644 similarity index 100% rename from font/roboto-bold.woff rename to assets/fonts/roboto-bold.woff diff --git a/font/roboto-bold.woff2 b/assets/fonts/roboto-bold.woff2 old mode 100755 new mode 100644 similarity index 100% rename from font/roboto-bold.woff2 rename to assets/fonts/roboto-bold.woff2 diff --git a/font/roboto-light.woff b/assets/fonts/roboto-light.woff old mode 100755 new mode 100644 similarity index 100% rename from font/roboto-light.woff rename to assets/fonts/roboto-light.woff diff --git a/font/roboto-light.woff2 b/assets/fonts/roboto-light.woff2 old mode 100755 new mode 100644 similarity index 100% rename from font/roboto-light.woff2 rename to assets/fonts/roboto-light.woff2 diff --git a/font/roboto-regular.woff b/assets/fonts/roboto-regular.woff old mode 100755 new mode 100644 similarity index 100% rename from font/roboto-regular.woff rename to assets/fonts/roboto-regular.woff diff --git a/font/roboto-regular.woff2 b/assets/fonts/roboto-regular.woff2 old mode 100755 new mode 100644 similarity index 100% rename from font/roboto-regular.woff2 rename to assets/fonts/roboto-regular.woff2 diff --git a/assets/icons/alignment/icon_AlignNodesBottom_20px.png b/assets/icons/alignment/icon_AlignNodesBottom_20px.png new file mode 100644 index 0000000..129c64a Binary files /dev/null and b/assets/icons/alignment/icon_AlignNodesBottom_20px.png differ diff --git a/assets/icons/alignment/icon_AlignNodesCenter_20px.png b/assets/icons/alignment/icon_AlignNodesCenter_20px.png new file mode 100644 index 0000000..750947a Binary files /dev/null and b/assets/icons/alignment/icon_AlignNodesCenter_20px.png differ diff --git a/assets/icons/alignment/icon_AlignNodesLeft_20px.png b/assets/icons/alignment/icon_AlignNodesLeft_20px.png new file mode 100644 index 0000000..9daf1ff Binary files /dev/null and b/assets/icons/alignment/icon_AlignNodesLeft_20px.png differ diff --git a/assets/icons/alignment/icon_AlignNodesMiddle_20px.png b/assets/icons/alignment/icon_AlignNodesMiddle_20px.png new file mode 100644 index 0000000..4ef76bd Binary files /dev/null and b/assets/icons/alignment/icon_AlignNodesMiddle_20px.png differ diff --git a/assets/icons/alignment/icon_AlignNodesRight_20px.png b/assets/icons/alignment/icon_AlignNodesRight_20px.png new file mode 100644 index 0000000..c63e12a Binary files /dev/null and b/assets/icons/alignment/icon_AlignNodesRight_20px.png differ diff --git a/assets/icons/alignment/icon_AlignNodesTop_20px.png b/assets/icons/alignment/icon_AlignNodesTop_20px.png new file mode 100644 index 0000000..3636dca Binary files /dev/null and b/assets/icons/alignment/icon_AlignNodesTop_20px.png differ diff --git a/assets/icons/alignment/icon_DistributeNodesHorizontally_20px.png b/assets/icons/alignment/icon_DistributeNodesHorizontally_20px.png new file mode 100644 index 0000000..f214b5c Binary files /dev/null and b/assets/icons/alignment/icon_DistributeNodesHorizontally_20px.png differ diff --git a/assets/icons/alignment/icon_DistributeNodesVertically_20px.png b/assets/icons/alignment/icon_DistributeNodesVertically_20px.png new file mode 100644 index 0000000..9f9ea59 Binary files /dev/null and b/assets/icons/alignment/icon_DistributeNodesVertically_20px.png differ diff --git a/assets/icons/alignment/icon_StraightenConnections_20px.png b/assets/icons/alignment/icon_StraightenConnections_20px.png new file mode 100644 index 0000000..c4962a2 Binary files /dev/null and b/assets/icons/alignment/icon_StraightenConnections_20px.png differ diff --git a/dist/css/ueblueprint-node-value-type-color.css b/dist/css/ueblueprint-node-value-type-color.css index 8c36aa8..fc4d97b 100755 --- a/dist/css/ueblueprint-node-value-type-color.css +++ b/dist/css/ueblueprint-node-value-type-color.css @@ -1 +1 @@ -.ueb{--ueb-pin-color: white;--ueb-pin-dim-color: #afafaf}.ueb-pin-boolean{--ueb-pin-color: #930000}.ueb-pin-int{--ueb-pin-color: #1fe0ad}.ueb-pin-float{--ueb-pin-color: #9ffb44}.ueb-pin-vector{--ueb-pin-color: #fcc823}.ueb-pin-rotator{--ueb-pin-color: #9eb1fc}.ueb-pin-string{--ueb-pin-color: #fc00d2;--ueb-pin-background: linear-gradient(90deg, #fc00d220, #fc00d280 15%, #fc00d250 85%, transparent)}.ueb-pin-name{--ueb-pin-color: #cb81fc}.ueb-pin-object{--ueb-pin-color: #00a8f2}/*# sourceMappingURL=ueblueprint-node-value-type-color.css.map */ +.ueb{--ueb-pin-color: white;--ueb-pin-dim-color: #afafaf}.ueb-pin-boolean{--ueb-pin-color: #4d0000}.ueb-pin-class{--ueb-pin-color: #5800bb}.ueb-pin-float{--ueb-pin-color: #9ffb44}.ueb-pin-int{--ueb-pin-color: #1fe0ad}.ueb-pin-name{--ueb-pin-color: #cb81fc}.ueb-pin-object{--ueb-pin-color: #006603}.ueb-pin-rotator{--ueb-pin-color: #9eb1fc}.ueb-pin-string{--ueb-pin-color: #fc00d2;--ueb-pin-background: linear-gradient(90deg, #fc00d220, #fc00d280 15%, #fc00d250 85%, transparent)}.ueb-pin-vector{--ueb-pin-color: #fcc823}/*# 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 8404c5f..93535ee 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,KAGI,uBACA,6BAGJ,iBAEI,yBAGJ,aAEI,yBAGJ,eAEI,yBAGJ,gBAEI,yBAGJ,iBAEI,yBAGJ,gBAEI,yBACA,mGAGJ,cAEI,yBAGJ,gBAEI","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,uBACA,6BAGJ,iBAEI,yBAGJ,eAEI,yBAGJ,eAEI,yBAGJ,aAEI,yBAGJ,cAEI,yBAGJ,gBAGI,yBAGJ,iBAEI,yBAGJ,gBAEI,yBACA,mGAGJ,gBAEI","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 de75c05..1eab1ce 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-blueprint[data-drag-scrolling=false] .ueb-grid{cursor:default}.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>.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-node[data-advanced-display=Hidden] ueb-pin[data-advanced-view=true]{display:none}ueb-blueprint[data-drag-scrolling=false][data-selecting=false] ueb-pin:hover{background:var(--ueb-pin-background);cursor:crosshair}.ueb-node-outputs ueb-pin{text-align:right}.ueb-pin-icon{display:inline-block;position:relative;width:.85em;height:.85em;vertical-align:baseline;margin:0 .4em -1px .1em}.ueb-pin-icon::before{content:"";display:block;position:absolute;top:0;right:0;bottom:0;left:0;border:2px solid var(--ueb-pin-color);border-radius:50%}ueb-pin.ueb-pin-fill .ueb-pin-icon::before{background:var(--ueb-pin-color)}.ueb-pin-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-pin-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-from-input-coefficient: calc(2 * var(--ueb-from-input) - 1);--ueb-y-opposite: clamp(0, var(--ueb-from-y) - var(--ueb-to-y) - 1, 1);display:block;min-width:calc(var(--ueb-link-min-width)*1px);visibility:hidden}ueb-link svg{--ueb-y-opposite-coefficient: calc(2* var(--ueb-y-opposite) - 1);position:absolute;top:0;left:0;width:100%;height:100%;min-height:1px;transform:scaleY(calc(var(--ueb-y-opposite-coefficient) * var(--ueb-from-input-coefficient)));overflow:visible}ueb-link svg path{visibility:visible;stroke:var(--ueb-pin-color);stroke-width:1}ueb-link.ueb-link-dragging svg path,ueb-link svg g:hover path{stroke-width:5;transition:stroke-width .8s}ueb-link-message{display:block;visibility:visible;position:absolute;top:calc(100%*(1 - var(--ueb-y-opposite)) + 22px);left:calc((1 - var(--ueb-from-input))*100% + (var(--ueb-from-input-coefficient))*var(--ueb-start-percentage) + 15px);border:1px solid #000;padding:4px 8px;border-radius:2px;background:linear-gradient(to bottom, #2a2a2a 0, #151515 50%, #2a2a2a 100%);color:var(--ueb-pin-dim-color);white-space:nowrap;z-index:1000000}/*# 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-blueprint[data-drag-scrolling=false] .ueb-grid{cursor:default}.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>.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-node-expand{display:none}ueb-node[data-advanced-display] .ueb-node-expand{display:block}ueb-pin{display:block;padding:1px 2px}ueb-node[data-advanced-display=Hidden] ueb-pin[data-advanced-view=true]{display:none}ueb-blueprint[data-drag-scrolling=false][data-selecting=false] ueb-pin:hover{background:var(--ueb-pin-background);cursor:crosshair}.ueb-node-outputs ueb-pin{text-align:right}.ueb-pin-icon{display:inline-block;position:relative;width:.85em;height:.85em;vertical-align:baseline;margin:0 .4em -1px .1em}.ueb-pin-icon::before{content:"";display:block;position:absolute;top:0;right:0;bottom:0;left:0;border:2px solid var(--ueb-pin-color);border-radius:50%}ueb-pin.ueb-pin-fill .ueb-pin-icon::before{background:var(--ueb-pin-color)}.ueb-pin-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-pin-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-from-input-coefficient: calc(2 * var(--ueb-from-input) - 1);--ueb-y-opposite: clamp(0, var(--ueb-from-y) - var(--ueb-to-y) - 1, 1);display:block;min-width:calc(var(--ueb-link-min-width)*1px);visibility:hidden}ueb-link svg{--ueb-y-opposite-coefficient: calc(2* var(--ueb-y-opposite) - 1);position:absolute;top:0;left:0;width:100%;height:100%;min-height:1px;transform:scaleY(calc(var(--ueb-y-opposite-coefficient) * var(--ueb-from-input-coefficient)));overflow:visible}ueb-link svg path{visibility:visible;stroke:var(--ueb-pin-color);stroke-width:1}ueb-link.ueb-link-dragging svg path,ueb-link svg g:hover path{stroke-width:5;transition:stroke-width .8s}ueb-link-message{display:block;visibility:visible;position:absolute;top:calc(100%*(1 - var(--ueb-y-opposite)) + 22px);left:calc((1 - var(--ueb-from-input))*100% + (var(--ueb-from-input-coefficient))*var(--ueb-start-percentage) + 15px);border:1px solid #000;padding:4px 8px;border-radius:2px;background:linear-gradient(to bottom, #2a2a2a 0, #151515 50%, #2a2a2a 100%);color:var(--ueb-pin-dim-color);white-space:nowrap;z-index:1000000}/*# sourceMappingURL=ueblueprint-style.css.map */ diff --git a/dist/css/ueblueprint-style.css.map b/dist/css/ueblueprint-style.css.map index 1ed8e7f..16d69b2 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,mDACI,eAGJ,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,+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,wEACI,aAGJ,6EACI,qCACA,iBAGJ,0BACI,iBAGJ,cACI,qBACA,kBACA,YACA,aACA,wBACA,wBAGJ,sBACI,WACA,cACA,kBACA,MACA,QACA,SACA,OACA,sCACA,kBAGJ,2CACI,gCAGJ,qBACI,WACA,cACA,kBACA,qBACA,sBACA,QACA,SACA,kCACA,qCACA,4CAGJ,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,kEAEA,uEACA,cACA,8CAKA,kBAGJ,aACI,iEACA,kBACA,MACA,OACA,WACA,YACA,eACA,8FACA,iBAGJ,kBACI,mBACA,4BACA,eAGJ,8DAEI,eACA,4BAGJ,iBACI,cACA,mBACA,kBACA,kDACA,qHAOA,sBACA,gBACA,kBACA,4EACA,+BACA,mBACA","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,mDACI,eAGJ,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,+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,iBACI,aAGJ,iDACI,cAGJ,QACI,cACA,gBAGJ,wEACI,aAGJ,6EACI,qCACA,iBAGJ,0BACI,iBAGJ,cACI,qBACA,kBACA,YACA,aACA,wBACA,wBAGJ,sBACI,WACA,cACA,kBACA,MACA,QACA,SACA,OACA,sCACA,kBAGJ,2CACI,gCAGJ,qBACI,WACA,cACA,kBACA,qBACA,sBACA,QACA,SACA,kCACA,qCACA,4CAGJ,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,kEAEA,uEACA,cACA,8CAKA,kBAGJ,aACI,iEACA,kBACA,MACA,OACA,WACA,YACA,eACA,8FACA,iBAGJ,kBACI,mBACA,4BACA,eAGJ,8DAEI,eACA,4BAGJ,iBACI,cACA,mBACA,kBACA,kDACA,qHAOA,sBACA,gBACA,kBACA,4EACA,+BACA,mBACA","file":"ueblueprint-style.css"} \ No newline at end of file diff --git a/dist/ueblueprint.js b/dist/ueblueprint.js index 05fb48b..be0d427 100755 --- a/dist/ueblueprint.js +++ b/dist/ueblueprint.js @@ -1,5 +1,6 @@ class Configuration { static deleteNodesKeyboardKey = "Delete" + static enableZoomIn = ["LeftControl", "RightControl"] // Button to enable more than 0 (1:1) zoom static expandGridSize = 400 static fontSize = "13px" static gridAxisLineColor = "black" @@ -22,11 +23,13 @@ class Configuration { let end = 100 - start; return `M ${start} 0 C ${c1} 0, ${c2} 0, 50 50 S ${end - c1 + start} 100, ${end} 100` } + static maxZoom = 7 + static minZoom = -12 static nodeDeleteEventName = "ueb-node-delete" static nodeDragEventName = "ueb-node-drag" static nodeDragLocalEventName = "ueb-node-drag-local" static nodeRadius = 8 // in pixel - static selectAllKeyboardKey = "Ctrl+A" + static selectAllKeyboardKey = "(bCtrl=True,Key=A)" static trackingMouseEventName = { begin: "ueb-tracking-mouse-begin", end: "ueb-tracking-mouse-end" @@ -41,6 +44,12 @@ class Configuration { /* UE name: JS name */ "Backspace": "Backspace", "Tab": "Tab", + "LeftControl": "ControlLeft", + "RightControl": "ControlRight", + "LeftShift": "ShiftLeft", + "RightShift": "ShiftRight", + "LeftAlt": "AltLeft", + "RightAlt": "AltRight", "Enter": "Enter", "Pause": "Pause", "CapsLock": "CapsLock", @@ -50,23 +59,23 @@ class Configuration { "PageDown": "PageDown", "End": "End", "Home": "Home", - "ArrowLeft": "ArrowLeft", - "ArrowUp": "ArrowUp", - "ArrowRight": "ArrowRight", - "ArrowDown": "ArrowDown", + "ArrowLeft": "Left", + "ArrowUp": "Up", + "ArrowRight": "Right", + "ArrowDown": "Down", "PrintScreen": "PrintScreen", "Insert": "Insert", "Delete": "Delete", - "Digit0": "Digit0", - "Digit1": "Digit1", - "Digit2": "Digit2", - "Digit3": "Digit3", - "Digit4": "Digit4", - "Digit5": "Digit5", - "Digit6": "Digit6", - "Digit7": "Digit7", - "Digit8": "Digit8", - "Digit9": "Digit9", + "Zero": "Digit0", + "One": "Digit1", + "Two": "Digit2", + "Three": "Digit3", + "Four": "Digit4", + "Five": "Digit5", + "Six": "Digit6", + "Seven": "Digit7", + "Eight": "Digit8", + "Nine": "Digit9", "A": "KeyA", "B": "KeyB", "C": "KeyC", @@ -92,21 +101,21 @@ class Configuration { "X": "KeyX", "Y": "KeyY", "Z": "KeyZ", - "Numpad0": "Numpad0", - "Numpad1": "Numpad1", - "Numpad2": "Numpad2", - "Numpad3": "Numpad3", - "Numpad4": "Numpad4", - "Numpad5": "Numpad5", - "Numpad6": "Numpad6", - "Numpad7": "Numpad7", - "Numpad8": "Numpad8", - "Numpad9": "Numpad9", - "NumpadMultiply": "NumpadMultiply", - "NumpadAdd": "NumpadAdd", - "NumpadSubtract": "NumpadSubtract", - "NumpadDecimal": "NumpadDecimal", - "NumpadDivide": "NumpadDivide", + "NumPadZero": "Numpad0", + "NumPadOne": "Numpad1", + "NumPadTwo": "Numpad2", + "NumPadThree": "Numpad3", + "NumPadFour": "Numpad4", + "NumPadFive": "Numpad5", + "NumPadSix": "Numpad6", + "NumPadSeven": "Numpad7", + "NumPadEight": "Numpad8", + "NumPadNine": "Numpad9", + "Multiply": "NumpadMultiply", + "Add": "NumpadAdd", + "Subtract": "NumpadSubtract", + "Decimal": "NumpadDecimal", + "Divide": "NumpadDivide", "F1": "F1", "F2": "F2", "F3": "F3", @@ -485,19 +494,27 @@ class IElement extends HTMLElement { static tagName = "" + /** @type {Blueprint} */ + blueprint + + /** @type {IEntity} */ + entity + + /** @type {ITemplate} */ + template + + /** @type {IContext[]} */ + inputObjects = [] + /** * @param {IEntity} entity The entity containing blueprint related data for this graph element * @param {ITemplate} template The template to render this node */ constructor(entity, template) { super(); - /** @type {Blueprint} */ this.blueprint = null; - /** @type {IEntity} */ this.entity = entity; - /** @type {ITemplate} */ this.template = template; - /** @type {IContext[]} */ this.inputObjects = []; } @@ -515,15 +532,23 @@ class IElement extends HTMLElement { this.inputObjects.forEach(v => v.unlistenDOMElement()); } - createInputObjects() { - return [] + /** @param {IElement} element */ + isSameGraph(element) { + return this.blueprint && this.blueprint == element?.blueprint } /** - * @param {IElement} element + * @template {} T + * @param {new () => T} type + * @returns {T} */ - isSameGraph(element) { - return this.blueprint && this.blueprint == element?.blueprint + getInputObject(type) { + return this.inputObjects.find(object => object.constructor == type) + } + + // Subclasses will want to override + createInputObjects() { + return [] } } @@ -538,7 +563,6 @@ class SelectorTemplate extends ITemplate { */ apply(selector) { super.apply(selector); - selector.classList.add("ueb-positioned"); this.applyFinishSelecting(selector); } @@ -749,15 +773,37 @@ class BlueprintTemplate extends ITemplate { class IContext { + /** @type {HTMLElement} */ + target + + /** @type {import("../Blueprint").default}" */ + blueprint + + /** @type {Object} */ + options + + #hasFocus = false + + get hasFocus() { + return this.#hasFocus + } + + set hasFocus(_) { + } + constructor(target, blueprint, options) { - /** @type {HTMLElement} */ this.target = target; - /** @type {import("../Blueprint").default}" */ this.blueprint = blueprint; this.options = options; let self = this; - this.blueprintFocusHandler = _ => self.listenEvents(); - this.blueprintUnfocusHandler = _ => self.unlistenEvents(); + this.blueprintFocusHandler = _ => { + this.#hasFocus = true; + self.listenEvents(); + }; + this.blueprintUnfocusHandler = _ => { + self.unlistenEvents(); + this.#hasFocus = false; + }; if (options?.wantsFocusCallback ?? false) { this.blueprint.addEventListener("blueprint-focus", this.blueprintFocusHandler); this.blueprint.addEventListener("blueprint-unfocus", this.blueprintUnfocusHandler); @@ -947,7 +993,7 @@ class IEntity { * - A proper value. */ const value = Utility.objectGet(options, fullKey); - if (value !== null) { + if (value !== undefined) { target[property] = value; continue } @@ -1016,7 +1062,7 @@ class GuidEntity extends IEntity { } } -class Identifier extends IEntity { +class IdentifierEntity extends IEntity { static attributes = { value: String, @@ -1030,11 +1076,6 @@ class Identifier extends IEntity { }; } super(options); - /** @type {String} */ - this.value; - if (!this.value.match(/\w+/)) { - throw new Error("The value must be an identifier (/\w+/).") - } } valueOf() { @@ -1071,6 +1112,21 @@ class IntegerEntity extends IEntity { } } +class KeyBindingEntity extends IEntity { + + static attributes = { + ActionName: "", + bShift: false, + bCtrl: false, + bAlt: false, + bCmd: false, + Key: IdentifierEntity, + } + constructor(options = {}) { + super(options); + } +} + class LocalizedTextEntity extends IEntity { static lookbehind = "NSLOCTEXT" @@ -1167,16 +1223,14 @@ class PinEntity extends IEntity { linkTo(targetObjectName, targetPinEntity) { /** @type {PinReferenceEntity[]} */ this.LinkedTo; - const linkFound = this.LinkedTo.find( - /** @type {PinReferenceEntity} */ - pinReferenceEntity => { - return pinReferenceEntity.objectName == targetObjectName - && pinReferenceEntity.pinGuid.valueOf() == targetPinEntity.PinId.valueOf() - }); + const linkFound = this.LinkedTo?.find(pinReferenceEntity => { + return pinReferenceEntity.objectName == targetObjectName + && pinReferenceEntity.pinGuid.valueOf() == targetPinEntity.PinId.valueOf() + }); if (!linkFound) { - this.LinkedTo.push(new PinReferenceEntity({ + (this.LinkedTo ?? (this.LinkedTo = [])).push(new PinReferenceEntity({ objectName: targetObjectName, - pinGuid: targetPinEntity.PinId + pinGuid: targetPinEntity.PinId, })); return true } @@ -1190,14 +1244,16 @@ class PinEntity extends IEntity { unlinkFrom(targetObjectName, targetPinEntity) { /** @type {PinReferenceEntity[]} */ this.LinkedTo; - const indexElement = this.LinkedTo.findIndex( - /** @type {PinReferenceEntity} */ - pinReferenceEntity => { - return pinReferenceEntity.objectName == targetObjectName - && pinReferenceEntity.pinGuid == targetPinEntity.PinId - }); + const indexElement = this.LinkedTo.findIndex(pinReferenceEntity => { + return pinReferenceEntity.objectName == targetObjectName + && pinReferenceEntity.pinGuid == targetPinEntity.PinId + }); if (indexElement >= 0) { - this.LinkedTo.splice(indexElement, 1); + if (this.LinkedTo.length == 1) { + this.LinkedTo = undefined; + } else { + this.LinkedTo.splice(indexElement, 1); + } return true } return false @@ -1229,7 +1285,7 @@ class ObjectEntity extends IEntity { TargetType: new TypeInitialization(ObjectReferenceEntity, false, null), NodePosX: IntegerEntity, NodePosY: IntegerEntity, - AdvancedPinDisplay: new TypeInitialization(Identifier, false, null), + AdvancedPinDisplay: new TypeInitialization(IdentifierEntity, false, null), NodeGuid: GuidEntity, ErrorType: new TypeInitialization(IntegerEntity, false), ErrorMsg: new TypeInitialization(String, false, ""), @@ -1258,106 +1314,11 @@ var parsimmon_umd_min = {exports: {}}; var Parsimmon = /*@__PURE__*/getDefaultExportFromCjs(parsimmon_umd_min.exports); -let P$1 = Parsimmon; +let P = Parsimmon; class Grammar { - /** @param {Grammar} r */ - InlineWhitespace = r => P$1.regex(/[^\S\n]+/).desc("inline whitespace") - - /** @param {Grammar} r */ - InlineOptWhitespace = r => P$1.regex(/[^\S\n]*/).desc("inline optional whitespace") - - /** @param {Grammar} r */ - WhitespaceNewline = r => P$1.regex(/[^\S\n]*\n\s*/).desc("whitespace with at least a newline") - - /** @param {Grammar} r */ - Null = r => P$1.seq(P$1.string("("), r.InlineOptWhitespace, P$1.string(")")).map(_ => null).desc("null: ()") - - /** @param {Grammar} r */ - None = r => P$1.string("None").map(_ => new ObjectReferenceEntity({ type: "None", path: "" })).desc("none") - - /** @param {Grammar} r */ - Boolean = r => P$1.alt(P$1.string("True"), P$1.string("False")).map(v => v === "True" ? true : false).desc("either True or False") - - /** @param {Grammar} r */ - Number = r => P$1.regex(/[\-\+]?[0-9]+(?:\.[0-9]+)?/).map(Number).desc("a number") - - /** @param {Grammar} r */ - Integer = r => P$1.regex(/[\-\+]?[0-9]+/).map(v => new IntegerEntity(v)).desc("an integer") - - /** @param {Grammar} r */ - String = r => P$1.regex(/(?:[^"\\]|\\.)*/).wrap(P$1.string('"'), P$1.string('"')).desc('string (with possibility to escape the quote using \")') - - /** @param {Grammar} r */ - Word = r => P$1.regex(/[a-zA-Z]+/).desc("a word") - - /** @param {Grammar} r */ - Guid = r => P$1.regex(/[0-9a-zA-Z]{32}/).map(v => new GuidEntity({ value: v })).desc("32 digit hexadecimal (accepts all the letters for safety) value") - - /** @param {Grammar} */ - Identifier = r => P$1.regex(/\w+/).map(v => new Identifier(v)) - - /** @param {Grammar} r */ - PathSymbolEntity = r => P$1.regex(/[0-9a-zA-Z_]+/).map(v => new PathSymbolEntity({ value: v })) - - /** @param {Grammar} r */ - ReferencePath = r => P$1.seq(P$1.string("/"), r.PathSymbolEntity.map(v => v.toString()).sepBy1(P$1.string(".")).tieWith(".")) - .tie() - .atLeast(2) - .tie() - .desc('a path (words with possibly underscore, separated by ".", separated by "/")') - - /** @param {Grammar} r */ - Reference = r => P$1.alt( - r.None, - ...[r.ReferencePath.map(path => new ObjectReferenceEntity({ type: "", path: path }))].flatMap( - v => [v, v.trim(P$1.string('"'))] - ), - P$1.seqMap( - r.Word, - P$1.optWhitespace, - P$1.alt(P$1.string('"'), P$1.string('\'"')).chain( - result => r.ReferencePath.skip( - P$1.string(result.split("").reverse().join("")) - ) - ), - (referenceType, _, referencePath) => new ObjectReferenceEntity({ type: referenceType, path: referencePath }) - ) - ) - - /** @param {Grammar} r */ - AttributeName = r => r.Word.sepBy1(P$1.string(".")).tieWith(".").desc('words separated by ""') - - /** @param {Grammar} r */ - AttributeAnyValue = r => P$1.alt(r.Null, r.None, r.Boolean, r.Number, r.Integer, r.String, r.Guid, r.Reference, r.LocalizedText) - - /** @param {Grammar} r */ - LocalizedText = r => P$1.seqMap( - P$1.string(LocalizedTextEntity.lookbehind).skip(P$1.optWhitespace).skip(P$1.string("(")), - r.String.trim(P$1.optWhitespace), // namespace - P$1.string(","), - r.String.trim(P$1.optWhitespace), // key - P$1.string(","), - r.String.trim(P$1.optWhitespace), // value - P$1.string(")"), - (_, namespace, __, key, ___, value, ____) => new LocalizedTextEntity({ - namespace: namespace, - key: key, - value: value - }) - ) - - /** @param {Grammar} r */ - PinReference = r => P$1.seqMap( - r.PathSymbolEntity, - P$1.whitespace, - r.Guid, - (objectName, _, pinGuid) => new PinReferenceEntity({ - objectName: objectName, - pinGuid: pinGuid - }) - ) + /* --- Factory --- */ /** @param {Grammar} r */ static getGrammarForType(r, attributeType, defaultGrammar) { @@ -1372,7 +1333,7 @@ class Grammar { return r.String case GuidEntity: return r.Guid - case Identifier: + case IdentifierEntity: return r.Identifier case ObjectReferenceEntity: return r.Reference @@ -1385,8 +1346,8 @@ class Grammar { case PinEntity: return r.Pin case Array: - return P$1.seqMap( - P$1.string("("), + return P.seqMap( + P.string("("), attributeType .map(v => Grammar.getGrammarForType(r, Utility.getType(v))) .reduce((accum, cur) => @@ -1394,10 +1355,10 @@ class Grammar { ? r.AttributeAnyValue : accum.or(cur) ) - .trim(P$1.optWhitespace) - .sepBy(P$1.string(",")) - .skip(P$1.regex(/,?\s*/)), - P$1.string(")"), + .trim(P.optWhitespace) + .sepBy(P.string(",")) + .skip(P.regex(/,?\s*/)), + P.string(")"), (_, grammar, __) => grammar ) default: @@ -1406,7 +1367,7 @@ class Grammar { } /** @param {Grammar} r */ - static CreateAttributeGrammar = (r, entityType, valueSeparator = P$1.string("=").trim(P$1.optWhitespace)) => + static createAttributeGrammar = (r, entityType, valueSeparator = P.string("=").trim(P.optWhitespace)) => r.AttributeName.skip(valueSeparator) .chain(attributeName => { const attributeKey = attributeName.split("."); @@ -1419,36 +1380,165 @@ class Grammar { }) /** @param {Grammar} r */ - static CreateMultiAttributeGrammar = (r, entityType) => + static createMultiAttributeGrammar = (r, entityType) => /** * Basically this creates a parser that looks for a string like 'Key (A=False,B="Something",)' * Then it populates an object of type EntityType with the attribute values found inside the parentheses. */ - P$1.seqMap( + P.seqMap( entityType.lookbehind - ? P$1.seq(P$1.string(entityType.lookbehind), P$1.optWhitespace, P$1.string("(")) - : P$1.string("("), - Grammar.CreateAttributeGrammar(r, entityType) - .trim(P$1.optWhitespace) - .sepBy(P$1.string(",")) - .skip(P$1.regex(/,?/).then(P$1.optWhitespace)), // Optional trailing comma - P$1.string(')'), + ? P.seq(P.string(entityType.lookbehind), P.optWhitespace, P.string("(")) + : P.string("("), + Grammar.createAttributeGrammar(r, entityType) + .trim(P.optWhitespace) + .sepBy(P.string(",")) + .skip(P.regex(/,?/).then(P.optWhitespace)), // Optional trailing comma + P.string(')'), (_, attributes, __) => { let result = new entityType(); attributes.forEach(attributeSetter => attributeSetter(result)); return result }) - /** @param {Grammar} r */ - FunctionReference = r => Grammar.CreateMultiAttributeGrammar(r, FunctionReferenceEntity) + /* --- General --- */ /** @param {Grammar} r */ - Pin = r => Grammar.CreateMultiAttributeGrammar(r, PinEntity) + InlineWhitespace = r => P.regex(/[^\S\n]+/).desc("inline whitespace") + + /** @param {Grammar} r */ + InlineOptWhitespace = r => P.regex(/[^\S\n]*/).desc("inline optional whitespace") + + /** @param {Grammar} r */ + MultilineWhitespace = r => P.regex(/[^\S\n]*\n\s*/).desc("whitespace with at least a newline") + + /** @param {Grammar} r */ + Null = r => P.seq(P.string("("), r.InlineOptWhitespace, P.string(")")).map(_ => null).desc("null: ()") + + /** @param {Grammar} r */ + Boolean = r => P.alt(P.string("True"), P.string("False")).map(v => v === "True" ? true : false) + .desc("either True or False") + + /** @param {Grammar} r */ + Number = r => P.regex(/[\-\+]?[0-9]+(?:\.[0-9]+)?/).map(Number).desc("a number") + + /** @param {Grammar} r */ + Word = r => P.regex(/[a-zA-Z]+/).desc("a word") + + /** @param {Grammar} r */ + String = r => P.regex(/(?:[^"\\]|\\.)*/).wrap(P.string('"'), P.string('"')) + .desc('string (with possibility to escape the quote using \")') + + /** @param {Grammar} r */ + ReferencePath = r => P.seq( + P.string("/"), + r.PathSymbol + .map(v => v.toString()) + .sepBy1(P.string(".")) + .tieWith(".") + ) + .tie() + .atLeast(2) + .tie() + .desc('a path (words with possibly underscore, separated by ".", separated by "/")') + + /** @param {Grammar} r */ + AttributeName = r => r.Word.sepBy1(P.string(".")).tieWith(".").desc('words separated by ""') + + /* --- Entity --- */ + + /** @param {Grammar} r */ + None = r => P.string("None").map(_ => new ObjectReferenceEntity({ type: "None", path: "" })).desc("none") + + /** @param {Grammar} r */ + Integer = r => P.regex(/[\-\+]?[0-9]+/).map(v => new IntegerEntity(v)).desc("an integer") + + /** @param {Grammar} r */ + Guid = r => P.regex(/[0-9a-zA-Z]{32}/).map(v => new GuidEntity({ value: v })) + .desc("32 digit hexadecimal (accepts all the letters for safety) value") + + /** @param {Grammar} */ + Identifier = r => P.regex(/\w+/).map(v => new IdentifierEntity(v)) + + /** @param {Grammar} r */ + PathSymbol = r => P.regex(/[0-9a-zA-Z_]+/).map(v => new PathSymbolEntity({ value: v })) + + /** @param {Grammar} r */ + Reference = r => P.alt( + r.None, + ...[r.ReferencePath.map(path => new ObjectReferenceEntity({ type: "", path: path }))] + .flatMap(referencePath => [ + referencePath, // version having just path + referencePath.trim(P.string('"')) // Version having path surround with double quotes + ]), + P.seqMap( + r.Word, // Goes into referenceType + P.optWhitespace, // Goes into _ (ignored) + P.alt(...[r.ReferencePath].flatMap(referencePath => [ + referencePath.wrap(P.string(`"`), P.string(`"`)), + referencePath.wrap(P.string(`'"`), P.string(`"'`)) + ])), // Goes into referencePath + (referenceType, _, referencePath) => new ObjectReferenceEntity({ type: referenceType, path: referencePath }) + ), + r.Word.map(type => new ObjectReferenceEntity({ type: type, path: "" })), + ) + + /** @param {Grammar} r */ + LocalizedText = r => P.seqMap( + P.string(LocalizedTextEntity.lookbehind).skip(P.optWhitespace).skip(P.string("(")), // Goes into _ (ignored) + r.String.trim(P.optWhitespace), // Goes into namespace + P.string(","), // Goes into __ (ignored) + r.String.trim(P.optWhitespace), // Goes into key + P.string(","), // Goes into ___ (ignored) + r.String.trim(P.optWhitespace), // Goes into value + P.string(")"), // Goes into ____ (ignored) + (_, namespace, __, key, ___, value, ____) => new LocalizedTextEntity({ + namespace: namespace, + key: key, + value: value + }) + ) + + /** @param {Grammar} r */ + AttributeAnyValue = r => P.alt( + r.Null, + r.None, + r.Boolean, + r.Number, + r.Integer, + r.String, + r.Guid, + r.Reference, + r.LocalizedText) + + /** @param {Grammar} r */ + PinReference = r => P.seqMap( + r.PathSymbol, // Goes into objectNAme + P.whitespace, // Goes into _ (ignored) + r.Guid, // Goes into pinGuid + (objectName, _, pinGuid) => new PinReferenceEntity({ + objectName: objectName, + pinGuid: pinGuid + }) + ) + + /** @param {Grammar} r */ + FunctionReference = r => Grammar.createMultiAttributeGrammar(r, FunctionReferenceEntity) + + /** @param {Grammar} r */ + KeyBinding = r => P.alt( + r.Identifier.map(identifier => new KeyBindingEntity({ + Key: identifier + })), + Grammar.createMultiAttributeGrammar(r, KeyBindingEntity) + ) + + /** @param {Grammar} r */ + Pin = r => Grammar.createMultiAttributeGrammar(r, PinEntity) /** @param {Grammar} r */ CustomProperties = r => - P$1.string("CustomProperties") - .then(P$1.whitespace) + P.string("CustomProperties") + .then(P.whitespace) .then(r.Pin) .map(pin => entity => { /** @type {Array} */ @@ -1458,15 +1548,15 @@ class Grammar { }) /** @param {Grammar} r */ - Object = r => P$1.seqMap( - P$1.seq(P$1.string("Begin"), P$1.whitespace, P$1.string("Object"), P$1.whitespace), - P$1 + Object = r => P.seqMap( + P.seq(P.string("Begin"), P.whitespace, P.string("Object"), P.whitespace), + P .alt( r.CustomProperties, - Grammar.CreateAttributeGrammar(r, ObjectEntity) + Grammar.createAttributeGrammar(r, ObjectEntity) ) - .sepBy1(P$1.whitespace), - P$1.seq(r.WhitespaceNewline, P$1.string("End"), P$1.whitespace, P$1.string("Object")), + .sepBy1(P.whitespace), + P.seq(r.MultilineWhitespace, P.string("End"), P.whitespace, P.string("Object")), (_, attributes, __) => { let result = new ObjectEntity(); attributes.forEach(attributeSetter => attributeSetter(result)); @@ -1475,7 +1565,7 @@ class Grammar { ) /** @param {Grammar} r */ - MultipleObject = r => r.Object.sepBy1(P$1.whitespace).trim(P$1.optWhitespace) + MultipleObject = r => r.Object.sepBy1(P.whitespace).trim(P.optWhitespace) } class SerializerFactory { @@ -1653,75 +1743,70 @@ class Copy extends IContext { } } -let P = Parsimmon; - -class KeyGrammar { - - // Creates a grammar where each alternative is the string from ModifierKey mapped to a number for bit or use - ModifierKey = r => P.alt(...Configuration.ModifierKeys.map((v, i) => P.string(v).map(_ => 1 << i))) - Key = r => P.alt(...Object.keys(Configuration.Keys).map(v => P.string(v))).map(v => Configuration.Keys[v]) - KeyboardShortcut = r => P.alt( - P.seqMap( - P.seqMap(r.ModifierKey, P.optWhitespace, P.string(Configuration.keysSeparator), (v, _, __) => v) - .atLeast(1) - .map(v => v.reduce((acc, cur) => acc | cur)), - P.optWhitespace, - r.Key, - (modifierKeysFlag, _, key) => ({ - key: key, - ctrlKey: Boolean(modifierKeysFlag & (1 << Configuration.ModifierKeys.indexOf("Ctrl"))), - shiftKey: Boolean(modifierKeysFlag & (1 << Configuration.ModifierKeys.indexOf("Shift"))), - altKey: Boolean(modifierKeysFlag & (1 << Configuration.ModifierKeys.indexOf("Alt"))), - metaKey: Boolean(modifierKeysFlag & (1 << Configuration.ModifierKeys.indexOf("Meta"))) - }) - ), - r.Key.map(v => ({ key: v })) - ) - .trim(P.optWhitespace) -} - class IKeyboardShortcut extends IContext { - static keyGrammar = P.createLanguage(new KeyGrammar()) + /** @type {KeyBindingEntity} */ + #activationKeys constructor(target, blueprint, options = {}) { options.wantsFocusCallback = true; + options.activationKeys ??= []; + if (!(options.activationKeys instanceof Array)) { + options.activationKeys = [options.activationKeys]; + } + options.activationKeys = options.activationKeys.map(v => { + if (v instanceof KeyBindingEntity) { + return v + } + if (v.constructor === String) { + const parsed = ISerializer.grammar.KeyBinding.parse(v); + if (parsed.status) { + return parsed.value + } + } + throw new Error("Unexpected key value") + }); + super(target, blueprint, options); - /** @type {String[]} */ - this.key = this.options.key; - this.ctrlKey = options.ctrlKey ?? false; - this.shiftKey = options.shiftKey ?? false; - this.altKey = options.altKey ?? false; - this.metaKey = options.metaKey ?? false; + this.#activationKeys = this.options.activationKeys ?? []; let self = this; + /** @param {KeyboardEvent} e */ this.keyDownHandler = e => { if ( - e.code == self.key - && e.ctrlKey === self.ctrlKey - && e.shiftKey === self.shiftKey - && e.altKey === self.altKey - && e.metaKey === self.metaKey - ) { - self.fire(); + self.#activationKeys.some(keyEntry => + keyEntry.bShift === e.shiftKey + && keyEntry.bCtrl === e.ctrlKey + && keyEntry.bAlt === e.altKey + && keyEntry.bCmd === e.metaKey + && Configuration.Keys[keyEntry.Key] === e.code + )) { e.preventDefault(); - return true + self.fire(); + document.removeEventListener("keydown", self.keyDownHandler); + document.addEventListener("keyup", self.keyUpHandler); } - return false }; - } - /** - * @param {String} keyString - * @returns {Object} - */ - static keyOptionsParse(options, keyString) { - options = { - ...options, - ...IKeyboardShortcut.keyGrammar.KeyboardShortcut.parse(keyString).value + /** @param {KeyboardEvent} e */ + this.keyUpHandler = e => { + if ( + self.#activationKeys.some(keyEntry => + keyEntry.bShift && e.key === "Shift" + || keyEntry.bCtrl && e.key === "Control" + || keyEntry.bAlt && e.key === "Alt" + || keyEntry.bCmd && e.key === "Meta" // Unsure about this, what key is that? + || Configuration.Keys[keyEntry.Key] === e.code + + )) { + e.preventDefault(); + self.unfire(); + document.removeEventListener("keyup", this.keyUpHandler); + document.addEventListener("keydown", this.keyDownHandler); + } }; - return options + } listenEvents() { @@ -1732,11 +1817,16 @@ class IKeyboardShortcut extends IContext { document.removeEventListener("keydown", this.keyDownHandler); } + // Subclasses will want to override + fire() { } + + unfire() { + } } -class KeyvoardCanc extends IKeyboardShortcut { +class KeyboardCanc extends IKeyboardShortcut { /** * @param {HTMLElement} target @@ -1744,7 +1834,10 @@ class KeyvoardCanc extends IKeyboardShortcut { * @param {OBject} options */ constructor(target, blueprint, options = {}) { - options = IKeyboardShortcut.keyOptionsParse(options, Configuration.deleteNodesKeyboardKey); + options = { + ...options, + activationKeys: Configuration.deleteNodesKeyboardKey + }; super(target, blueprint, options); } @@ -1753,6 +1846,125 @@ class KeyvoardCanc extends IKeyboardShortcut { } } +class IPointing extends IContext { + + constructor(target, blueprint, options) { + super(target, blueprint, options); + this.movementSpace = this.blueprint?.getGridDOMElement() ?? document.documentElement; + } + + /** + * @param {MouseEvent} mouseEvent + */ + locationFromEvent(mouseEvent) { + return this.blueprint.compensateTranslation( + Utility.convertLocation( + [mouseEvent.clientX, mouseEvent.clientY], + this.movementSpace)) + } +} + +class IMouseWheel extends IPointing { + + /** @type {(e: WheelEvent) => void} */ + #mouseWheelHandler + + /** @type {(e: WheelEvent) => void} */ + #mouseParentWheelHandler + + /** + * @param {HTMLElement} target + * @param {import("../../Blueprint").default} blueprint + * @param {Object} options + */ + constructor(target, blueprint, options) { + options.wantsFocusCallback = true; + super(target, blueprint, options); + this.looseTarget = options?.looseTarget ?? true; + let self = this; + + this.#mouseWheelHandler = e => { + e.preventDefault(); + const location = self.locationFromEvent(e); + self.wheel(Math.sign(e.deltaY), location); + }; + this.#mouseParentWheelHandler = e => e.preventDefault(); + + if (this.blueprint.focused) { + this.movementSpace.addEventListener("wheel", this.#mouseWheelHandler, false); + } + } + + listenEvents() { + this.movementSpace.addEventListener("wheel", this.#mouseWheelHandler, false); + this.movementSpace.parentElement?.addEventListener("wheel", this.#mouseParentWheelHandler); + } + + unlistenEvents() { + this.movementSpace.removeEventListener("wheel", this.#mouseWheelHandler, false); + this.movementSpace.parentElement?.removeEventListener("wheel", this.#mouseParentWheelHandler); + } + + /* Subclasses will override the following method */ + wheel(variation, location) { + } +} + +class Zoom extends IMouseWheel { + + #enableZoonIn = false + + get enableZoonIn() { + return this.#enableZoonIn + } + + set enableZoonIn(value) { + value = Boolean(value); + if (value == this.#enableZoonIn) { + return + } + this.#enableZoonIn = value; + } + + wheel(variation, location) { + let zoomLevel = this.blueprint.getZoom(); + variation = -variation; + if (!this.enableZoonIn && zoomLevel == 0 && variation > 0) { + return + } + zoomLevel += variation; + this.blueprint.setZoom(zoomLevel, location); + } +} + +class KeyboardEnableZoom extends IKeyboardShortcut { + + /** @type {} */ + #zoomInputObject + + /** + * @param {HTMLElement} target + * @param {import("../../Blueprint").default} blueprint + * @param {OBject} options + */ + constructor(target, blueprint, options = {}) { + options = { + ...options, + activationKeys: Configuration.enableZoomIn + }; + super(target, blueprint, options); + } + + fire() { + this.zoomInputObject = this.blueprint.getInputObject(Zoom); + zoomInputObject.enableZoonIn = true; + } + + unfire() { + this.#zoomInputObject.enableZoom = false; + } +} + class KeyboardSelectAll extends IKeyboardShortcut { /** @@ -1761,7 +1973,10 @@ class KeyboardSelectAll extends IKeyboardShortcut { * @param {Object} options */ constructor(target, blueprint, options = {}) { - options = IKeyboardShortcut.keyOptionsParse(options, Configuration.selectAllKeyboardKey); + options = { + ...options, + activationKeys: Configuration.selectAllKeyboardKey + }; super(target, blueprint, options); } @@ -2140,24 +2355,6 @@ class LinkElement extends IElement { customElements.define(LinkElement.tagName, LinkElement); -class IPointing extends IContext { - - constructor(target, blueprint, options) { - super(target, blueprint, options); - this.movementSpace = this.blueprint?.getGridDOMElement() ?? document.documentElement; - } - - /** - * @param {MouseEvent} mouseEvent - */ - locationFromEvent(mouseEvent) { - return this.blueprint.compensateTranslation( - Utility.convertLocation( - [mouseEvent.clientX, mouseEvent.clientY], - this.movementSpace)) - } -} - /** * This class manages the ui gesture of mouse click and drag. Tha actual operations are implemented by the subclasses. */ @@ -2258,6 +2455,7 @@ class IMouseClickDrag extends IPointing { if (self.started) { self.endDrag(); } + self.unclicked(); if (self.#trackingMouse) { const dragEvent = self.getEvent(Configuration.trackingMouseEventName.end); this.target.dispatchEvent(dragEvent); @@ -2303,6 +2501,9 @@ class IMouseClickDrag extends IPointing { endDrag() { } + + unclicked(location) { + } } class MouseScrollGraph extends IMouseClickDrag { @@ -2961,6 +3162,9 @@ class NodeTemplate extends SelectableDraggableTemplate {
+ ` } @@ -3132,7 +3336,11 @@ class Select extends IMouseClickDrag { endDrag() { if (this.started) { this.selectorElement.finishSelecting(); - } else { + } + } + + unclicked() { + if (!this.started) { this.blueprint.unselectAll(); } } @@ -3173,61 +3381,6 @@ class Unfocus extends IContext { } } -class IMouseWheel extends IPointing { - - /** @type {(e: WheelEvent) => void} */ - #mouseWheelHandler - - /** @type {(e: WheelEvent) => void} */ - #mouseParentWheelHandler - - /** - * @param {HTMLElement} target - * @param {import("../../Blueprint").default} blueprint - * @param {Object} options - */ - constructor(target, blueprint, options) { - options.wantsFocusCallback = true; - super(target, blueprint, options); - this.looseTarget = options?.looseTarget ?? true; - let self = this; - - this.#mouseWheelHandler = e => { - e.preventDefault(); - const location = self.locationFromEvent(e); - self.wheel(Math.sign(e.deltaY), location); - }; - this.#mouseParentWheelHandler = e => e.preventDefault(); - - if (this.blueprint.focused) { - this.movementSpace.addEventListener("wheel", this.#mouseWheelHandler, false); - } - } - - listenEvents() { - this.movementSpace.addEventListener("wheel", this.#mouseWheelHandler, false); - this.movementSpace.parentElement?.addEventListener("wheel", this.#mouseParentWheelHandler); - } - - unlistenEvents() { - this.movementSpace.removeEventListener("wheel", this.#mouseWheelHandler, false); - this.movementSpace.parentElement?.removeEventListener("wheel", this.#mouseParentWheelHandler); - } - - /* Subclasses will override the following method */ - wheel(variation, location) { - } -} - -class Zoom extends IMouseWheel { - - wheel(variation, location) { - let zoomLevel = this.blueprint.getZoom(); - zoomLevel -= variation; - this.blueprint.setZoom(zoomLevel, location); - } -} - /** * @typedef {import("./element/PinElement").default} PinElement * @typedef {import("./entity/GuidEntity").default} GuidEntity @@ -3316,7 +3469,7 @@ class Blueprint extends IElement { return [ new Copy(this.getGridDOMElement(), this), new Paste(this.getGridDOMElement(), this), - new KeyvoardCanc(this.getGridDOMElement(), this), + new KeyboardCanc(this.getGridDOMElement(), this), new KeyboardSelectAll(this.getGridDOMElement, this), new Zoom(this.getGridDOMElement(), this, { looseTarget: true, @@ -3334,7 +3487,8 @@ class Blueprint extends IElement { moveEverywhere: true, }), new Unfocus(this.getGridDOMElement(), this), - new MouseTracking(this.getGridDOMElement(), this) + new MouseTracking(this.getGridDOMElement(), this), + new KeyboardEnableZoom(this.getGridDOMElement(), this), ] } @@ -3470,7 +3624,7 @@ class Blueprint extends IElement { } setZoom(zoom, center) { - zoom = Utility.clamp(zoom, -12, 0); + zoom = Utility.clamp(zoom, Configuration.minZoom, Configuration.maxZoom); if (zoom == this.zoom) { return } @@ -3670,26 +3824,37 @@ class ToStringSerializer extends GeneralSerializer { } function initializeSerializerFactory() { + SerializerFactory.registerSerializer( ObjectEntity, new ObjectSerializer() ); + SerializerFactory.registerSerializer( PinEntity, new GeneralSerializer(v => `${PinEntity.lookbehind} (${v})`, PinEntity, "", ",", true) ); + SerializerFactory.registerSerializer( FunctionReferenceEntity, new GeneralSerializer(v => `(${v})`, FunctionReferenceEntity, "", ",", false) ); + + SerializerFactory.registerSerializer( + KeyBindingEntity, + new GeneralSerializer(v => `(${v})`, KeyBindingEntity, "", ",", false) + ); + SerializerFactory.registerSerializer( LocalizedTextEntity, new GeneralSerializer(v => `${LocalizedTextEntity.lookbehind}(${v})`, LocalizedTextEntity, "", ", ", false, "", _ => "") ); + SerializerFactory.registerSerializer( PinReferenceEntity, new GeneralSerializer(v => v, PinReferenceEntity, "", " ", false, "", _ => "") ); + SerializerFactory.registerSerializer( ObjectReferenceEntity, new CustomSerializer( @@ -3700,9 +3865,13 @@ function initializeSerializerFactory() { : "" )) ); - SerializerFactory.registerSerializer(Identifier, new ToStringSerializer(Identifier)); + + SerializerFactory.registerSerializer(IdentifierEntity, new ToStringSerializer(IdentifierEntity)); + SerializerFactory.registerSerializer(PathSymbolEntity, new ToStringSerializer(PathSymbolEntity)); + SerializerFactory.registerSerializer(GuidEntity, new ToStringSerializer(GuidEntity)); + SerializerFactory.registerSerializer(IntegerEntity, new ToStringSerializer(IntegerEntity)); } diff --git a/js/Blueprint.js b/js/Blueprint.js index 1d4d415..9ec75dc 100755 --- a/js/Blueprint.js +++ b/js/Blueprint.js @@ -3,6 +3,7 @@ import Configuration from "./Configuration" import Copy from "./input/common/Copy" import IElement from "./element/IElement" import KeyboardCanc from "./input/keybaord/KeyboardCanc" +import KeyboardEnableZoom from "./input/keybaord/KeyboardEnableZoom" import KeyboardSelectAll from "./input/keybaord/KeyboardSelectAll" import LinkElement from "./element/LinkElement" import MouseScrollGraph from "./input/mouse/MouseScrollGraph" @@ -121,7 +122,8 @@ export default class Blueprint extends IElement { moveEverywhere: true, }), new Unfocus(this.getGridDOMElement(), this), - new MouseTracking(this.getGridDOMElement(), this) + new MouseTracking(this.getGridDOMElement(), this), + new KeyboardEnableZoom(this.getGridDOMElement(), this), ] } @@ -257,7 +259,7 @@ export default class Blueprint extends IElement { } setZoom(zoom, center) { - zoom = Utility.clamp(zoom, -12, 0) + zoom = Utility.clamp(zoom, Configuration.minZoom, Configuration.maxZoom) if (zoom == this.zoom) { return } diff --git a/js/Configuration.js b/js/Configuration.js index 56e4406..a1a2be6 100755 --- a/js/Configuration.js +++ b/js/Configuration.js @@ -1,5 +1,6 @@ export default class Configuration { static deleteNodesKeyboardKey = "Delete" + static enableZoomIn = ["LeftControl", "RightControl"] // Button to enable more than 0 (1:1) zoom static expandGridSize = 400 static fontSize = "13px" static gridAxisLineColor = "black" @@ -22,11 +23,13 @@ export default class Configuration { let end = 100 - start return `M ${start} 0 C ${c1} 0, ${c2} 0, 50 50 S ${end - c1 + start} 100, ${end} 100` } + static maxZoom = 7 + static minZoom = -12 static nodeDeleteEventName = "ueb-node-delete" static nodeDragEventName = "ueb-node-drag" static nodeDragLocalEventName = "ueb-node-drag-local" static nodeRadius = 8 // in pixel - static selectAllKeyboardKey = "Ctrl+A" + static selectAllKeyboardKey = "(bCtrl=True,Key=A)" static trackingMouseEventName = { begin: "ueb-tracking-mouse-begin", end: "ueb-tracking-mouse-end" @@ -41,6 +44,12 @@ export default class Configuration { /* UE name: JS name */ "Backspace": "Backspace", "Tab": "Tab", + "LeftControl": "ControlLeft", + "RightControl": "ControlRight", + "LeftShift": "ShiftLeft", + "RightShift": "ShiftRight", + "LeftAlt": "AltLeft", + "RightAlt": "AltRight", "Enter": "Enter", "Pause": "Pause", "CapsLock": "CapsLock", @@ -50,23 +59,23 @@ export default class Configuration { "PageDown": "PageDown", "End": "End", "Home": "Home", - "ArrowLeft": "ArrowLeft", - "ArrowUp": "ArrowUp", - "ArrowRight": "ArrowRight", - "ArrowDown": "ArrowDown", + "ArrowLeft": "Left", + "ArrowUp": "Up", + "ArrowRight": "Right", + "ArrowDown": "Down", "PrintScreen": "PrintScreen", "Insert": "Insert", "Delete": "Delete", - "Digit0": "Digit0", - "Digit1": "Digit1", - "Digit2": "Digit2", - "Digit3": "Digit3", - "Digit4": "Digit4", - "Digit5": "Digit5", - "Digit6": "Digit6", - "Digit7": "Digit7", - "Digit8": "Digit8", - "Digit9": "Digit9", + "Zero": "Digit0", + "One": "Digit1", + "Two": "Digit2", + "Three": "Digit3", + "Four": "Digit4", + "Five": "Digit5", + "Six": "Digit6", + "Seven": "Digit7", + "Eight": "Digit8", + "Nine": "Digit9", "A": "KeyA", "B": "KeyB", "C": "KeyC", @@ -92,21 +101,21 @@ export default class Configuration { "X": "KeyX", "Y": "KeyY", "Z": "KeyZ", - "Numpad0": "Numpad0", - "Numpad1": "Numpad1", - "Numpad2": "Numpad2", - "Numpad3": "Numpad3", - "Numpad4": "Numpad4", - "Numpad5": "Numpad5", - "Numpad6": "Numpad6", - "Numpad7": "Numpad7", - "Numpad8": "Numpad8", - "Numpad9": "Numpad9", - "NumpadMultiply": "NumpadMultiply", - "NumpadAdd": "NumpadAdd", - "NumpadSubtract": "NumpadSubtract", - "NumpadDecimal": "NumpadDecimal", - "NumpadDivide": "NumpadDivide", + "NumPadZero": "Numpad0", + "NumPadOne": "Numpad1", + "NumPadTwo": "Numpad2", + "NumPadThree": "Numpad3", + "NumPadFour": "Numpad4", + "NumPadFive": "Numpad5", + "NumPadSix": "Numpad6", + "NumPadSeven": "Numpad7", + "NumPadEight": "Numpad8", + "NumPadNine": "Numpad9", + "Multiply": "NumpadMultiply", + "Add": "NumpadAdd", + "Subtract": "NumpadSubtract", + "Decimal": "NumpadDecimal", + "Divide": "NumpadDivide", "F1": "F1", "F2": "F2", "F3": "F3", diff --git a/js/element/IElement.js b/js/element/IElement.js index 9e9fd68..b062e85 100644 --- a/js/element/IElement.js +++ b/js/element/IElement.js @@ -9,19 +9,27 @@ export default class IElement extends HTMLElement { static tagName = "" + /** @type {Blueprint} */ + blueprint + + /** @type {IEntity} */ + entity + + /** @type {ITemplate} */ + template + + /** @type {IContext[]} */ + inputObjects = [] + /** * @param {IEntity} entity The entity containing blueprint related data for this graph element * @param {ITemplate} template The template to render this node */ constructor(entity, template) { super() - /** @type {Blueprint} */ this.blueprint = null - /** @type {IEntity} */ this.entity = entity - /** @type {ITemplate} */ this.template = template - /** @type {IContext[]} */ this.inputObjects = [] } @@ -39,14 +47,22 @@ export default class IElement extends HTMLElement { this.inputObjects.forEach(v => v.unlistenDOMElement()) } - createInputObjects() { - return [] - } - - /** - * @param {IElement} element - */ + /** @param {IElement} element */ isSameGraph(element) { return this.blueprint && this.blueprint == element?.blueprint } + + /** + * @template {} T + * @param {new () => T} type + * @returns {T} + */ + getInputObject(type) { + return this.inputObjects.find(object => object.constructor == type) + } + + // Subclasses will want to override + createInputObjects() { + return [] + } } diff --git a/js/entity/IEntity.js b/js/entity/IEntity.js index f2e06bd..bdd94d1 100644 --- a/js/entity/IEntity.js +++ b/js/entity/IEntity.js @@ -28,7 +28,7 @@ export default class IEntity { * - A proper value. */ const value = Utility.objectGet(options, fullKey) - if (value !== null) { + if (value !== undefined) { target[property] = value continue } diff --git a/js/entity/Identifier.js b/js/entity/IdentifierEntity.js similarity index 66% rename from js/entity/Identifier.js rename to js/entity/IdentifierEntity.js index 129aca6..77cf068 100644 --- a/js/entity/Identifier.js +++ b/js/entity/IdentifierEntity.js @@ -1,7 +1,7 @@ import IEntity from "./IEntity" -export default class Identifier extends IEntity { +export default class IdentifierEntity extends IEntity { static attributes = { value: String, @@ -15,11 +15,6 @@ export default class Identifier extends IEntity { } } super(options) - /** @type {String} */ - this.value - if (!this.value.match(/\w+/)) { - throw new Error("The value must be an identifier (/\w+/).") - } } valueOf() { diff --git a/js/entity/KeyBindingEntity.js b/js/entity/KeyBindingEntity.js index 609decd..1d5ee24 100644 --- a/js/entity/KeyBindingEntity.js +++ b/js/entity/KeyBindingEntity.js @@ -1,12 +1,17 @@ +import IdentifierEntity from "./IdentifierEntity" import IEntity from "./IEntity" export default class KeyBindingEntity extends IEntity { static attributes = { - bCtrlDown: false, - bAltDown: false, - bShiftDown: false, - Key: String, - CommandName: String, + ActionName: "", + bShift: false, + bCtrl: false, + bAlt: false, + bCmd: false, + Key: IdentifierEntity, + } + constructor(options = {}) { + super(options) } } diff --git a/js/entity/ObjectEntity.js b/js/entity/ObjectEntity.js index 659b93c..17e2b39 100755 --- a/js/entity/ObjectEntity.js +++ b/js/entity/ObjectEntity.js @@ -1,6 +1,6 @@ import FunctionReferenceEntity from "./FunctionReferenceEntity" import GuidEntity from "./GuidEntity" -import Identifier from "./Identifier" +import IdentifierEntity from "./IdentifierEntity" import IEntity from "./IEntity" import IntegerEntity from "./IntegerEntity" import ObjectReferenceEntity from "./ObjectReferenceEntity" @@ -20,7 +20,7 @@ export default class ObjectEntity extends IEntity { TargetType: new TypeInitialization(ObjectReferenceEntity, false, null), NodePosX: IntegerEntity, NodePosY: IntegerEntity, - AdvancedPinDisplay: new TypeInitialization(Identifier, false, null), + AdvancedPinDisplay: new TypeInitialization(IdentifierEntity, false, null), NodeGuid: GuidEntity, ErrorType: new TypeInitialization(IntegerEntity, false), ErrorMsg: new TypeInitialization(String, false, ""), diff --git a/js/entity/PinEntity.js b/js/entity/PinEntity.js index 36a6ada..b3f9569 100755 --- a/js/entity/PinEntity.js +++ b/js/entity/PinEntity.js @@ -72,16 +72,14 @@ export default class PinEntity extends IEntity { linkTo(targetObjectName, targetPinEntity) { /** @type {PinReferenceEntity[]} */ this.LinkedTo - const linkFound = this.LinkedTo.find( - /** @type {PinReferenceEntity} */ - pinReferenceEntity => { - return pinReferenceEntity.objectName == targetObjectName - && pinReferenceEntity.pinGuid.valueOf() == targetPinEntity.PinId.valueOf() - }) + const linkFound = this.LinkedTo?.find(pinReferenceEntity => { + return pinReferenceEntity.objectName == targetObjectName + && pinReferenceEntity.pinGuid.valueOf() == targetPinEntity.PinId.valueOf() + }) if (!linkFound) { - this.LinkedTo.push(new PinReferenceEntity({ + (this.LinkedTo ?? (this.LinkedTo = [])).push(new PinReferenceEntity({ objectName: targetObjectName, - pinGuid: targetPinEntity.PinId + pinGuid: targetPinEntity.PinId, })) return true } @@ -95,14 +93,16 @@ export default class PinEntity extends IEntity { unlinkFrom(targetObjectName, targetPinEntity) { /** @type {PinReferenceEntity[]} */ this.LinkedTo - const indexElement = this.LinkedTo.findIndex( - /** @type {PinReferenceEntity} */ - pinReferenceEntity => { - return pinReferenceEntity.objectName == targetObjectName - && pinReferenceEntity.pinGuid == targetPinEntity.PinId - }) + const indexElement = this.LinkedTo.findIndex(pinReferenceEntity => { + return pinReferenceEntity.objectName == targetObjectName + && pinReferenceEntity.pinGuid == targetPinEntity.PinId + }) if (indexElement >= 0) { - this.LinkedTo.splice(indexElement, 1) + if (this.LinkedTo.length == 1) { + this.LinkedTo = undefined + } else { + this.LinkedTo.splice(indexElement, 1) + } return true } return false diff --git a/js/input/IContext.js b/js/input/IContext.js index 7ba61a8..91b7625 100644 --- a/js/input/IContext.js +++ b/js/input/IContext.js @@ -1,14 +1,36 @@ export default class IContext { + /** @type {HTMLElement} */ + target + + /** @type {import("../Blueprint").default}" */ + blueprint + + /** @type {Object} */ + options + + #hasFocus = false + + get hasFocus() { + return this.#hasFocus + } + + set hasFocus(_) { + } + constructor(target, blueprint, options) { - /** @type {HTMLElement} */ this.target = target - /** @type {import("../Blueprint").default}" */ this.blueprint = blueprint this.options = options let self = this - this.blueprintFocusHandler = _ => self.listenEvents() - this.blueprintUnfocusHandler = _ => self.unlistenEvents() + this.blueprintFocusHandler = _ => { + this.#hasFocus = true + self.listenEvents() + } + this.blueprintUnfocusHandler = _ => { + self.unlistenEvents() + this.#hasFocus = false + } if (options?.wantsFocusCallback ?? false) { this.blueprint.addEventListener("blueprint-focus", this.blueprintFocusHandler) this.blueprint.addEventListener("blueprint-unfocus", this.blueprintUnfocusHandler) diff --git a/js/input/keybaord/IKeyboardShortcut.js b/js/input/keybaord/IKeyboardShortcut.js index cce46c2..c805fb1 100644 --- a/js/input/keybaord/IKeyboardShortcut.js +++ b/js/input/keybaord/IKeyboardShortcut.js @@ -1,76 +1,71 @@ import Configuration from "../../Configuration" import IContext from "../IContext" -import Parsimmon from "parsimmon" - -let P = Parsimmon - -class KeyGrammar { - - // Creates a grammar where each alternative is the string from ModifierKey mapped to a number for bit or use - ModifierKey = r => P.alt(...Configuration.ModifierKeys.map((v, i) => P.string(v).map(_ => 1 << i))) - Key = r => P.alt(...Object.keys(Configuration.Keys).map(v => P.string(v))).map(v => Configuration.Keys[v]) - KeyboardShortcut = r => P.alt( - P.seqMap( - P.seqMap(r.ModifierKey, P.optWhitespace, P.string(Configuration.keysSeparator), (v, _, __) => v) - .atLeast(1) - .map(v => v.reduce((acc, cur) => acc | cur)), - P.optWhitespace, - r.Key, - (modifierKeysFlag, _, key) => ({ - key: key, - ctrlKey: Boolean(modifierKeysFlag & (1 << Configuration.ModifierKeys.indexOf("Ctrl"))), - shiftKey: Boolean(modifierKeysFlag & (1 << Configuration.ModifierKeys.indexOf("Shift"))), - altKey: Boolean(modifierKeysFlag & (1 << Configuration.ModifierKeys.indexOf("Alt"))), - metaKey: Boolean(modifierKeysFlag & (1 << Configuration.ModifierKeys.indexOf("Meta"))) - }) - ), - r.Key.map(v => ({ key: v })) - ) - .trim(P.optWhitespace) -} - +import ISerializer from "../../serialization/ISerializer" +import KeyBindingEntity from "../../entity/KeyBindingEntity" export default class IKeyboardShortcut extends IContext { - static keyGrammar = P.createLanguage(new KeyGrammar()) + /** @type {KeyBindingEntity} */ + #activationKeys constructor(target, blueprint, options = {}) { options.wantsFocusCallback = true + options.activationKeys ??= [] + if (!(options.activationKeys instanceof Array)) { + options.activationKeys = [options.activationKeys] + } + options.activationKeys = options.activationKeys.map(v => { + if (v instanceof KeyBindingEntity) { + return v + } + if (v.constructor === String) { + const parsed = ISerializer.grammar.KeyBinding.parse(v) + if (parsed.status) { + return parsed.value + } + } + throw new Error("Unexpected key value") + }) + super(target, blueprint, options) - /** @type {String[]} */ - this.key = this.options.key - this.ctrlKey = options.ctrlKey ?? false - this.shiftKey = options.shiftKey ?? false - this.altKey = options.altKey ?? false - this.metaKey = options.metaKey ?? false + this.#activationKeys = this.options.activationKeys ?? [] let self = this + /** @param {KeyboardEvent} e */ this.keyDownHandler = e => { if ( - e.code == self.key - && e.ctrlKey === self.ctrlKey - && e.shiftKey === self.shiftKey - && e.altKey === self.altKey - && e.metaKey === self.metaKey - ) { - self.fire() + self.#activationKeys.some(keyEntry => + keyEntry.bShift === e.shiftKey + && keyEntry.bCtrl === e.ctrlKey + && keyEntry.bAlt === e.altKey + && keyEntry.bCmd === e.metaKey + && Configuration.Keys[keyEntry.Key] === e.code + )) { e.preventDefault() - return true + self.fire() + document.removeEventListener("keydown", self.keyDownHandler) + document.addEventListener("keyup", self.keyUpHandler) } - return false } - } - /** - * @param {String} keyString - * @returns {Object} - */ - static keyOptionsParse(options, keyString) { - options = { - ...options, - ...IKeyboardShortcut.keyGrammar.KeyboardShortcut.parse(keyString).value + /** @param {KeyboardEvent} e */ + this.keyUpHandler = e => { + if ( + self.#activationKeys.some(keyEntry => + keyEntry.bShift && e.key === "Shift" + || keyEntry.bCtrl && e.key === "Control" + || keyEntry.bAlt && e.key === "Alt" + || keyEntry.bCmd && e.key === "Meta" // Unsure about this, what key is that? + || Configuration.Keys[keyEntry.Key] === e.code + + )) { + e.preventDefault() + self.unfire() + document.removeEventListener("keyup", this.keyUpHandler) + document.addEventListener("keydown", this.keyDownHandler) + } } - return options + } listenEvents() { @@ -81,6 +76,11 @@ export default class IKeyboardShortcut extends IContext { document.removeEventListener("keydown", this.keyDownHandler) } + // Subclasses will want to override + fire() { } + + unfire() { + } } diff --git a/js/input/keybaord/KeyboardCanc.js b/js/input/keybaord/KeyboardCanc.js index 6b4de39..575c1c3 100755 --- a/js/input/keybaord/KeyboardCanc.js +++ b/js/input/keybaord/KeyboardCanc.js @@ -1,7 +1,7 @@ import Configuration from "../../Configuration" import IKeyboardShortcut from "./IKeyboardShortcut" -export default class KeyvoardCanc extends IKeyboardShortcut { +export default class KeyboardCanc extends IKeyboardShortcut { /** * @param {HTMLElement} target @@ -9,7 +9,10 @@ export default class KeyvoardCanc extends IKeyboardShortcut { * @param {OBject} options */ constructor(target, blueprint, options = {}) { - options = IKeyboardShortcut.keyOptionsParse(options, Configuration.deleteNodesKeyboardKey) + options = { + ...options, + activationKeys: Configuration.deleteNodesKeyboardKey + } super(target, blueprint, options) } diff --git a/js/input/keybaord/KeyboardEnableZoom.js b/js/input/keybaord/KeyboardEnableZoom.js new file mode 100644 index 0000000..9805850 --- /dev/null +++ b/js/input/keybaord/KeyboardEnableZoom.js @@ -0,0 +1,31 @@ +import Configuration from "../../Configuration" +import IKeyboardShortcut from "./IKeyboardShortcut" +import Zoom from "../mouse/Zoom" + +export default class KeyboardEnableZoom extends IKeyboardShortcut { + + /** @type {} */ + #zoomInputObject + + /** + * @param {HTMLElement} target + * @param {import("../../Blueprint").default} blueprint + * @param {OBject} options + */ + constructor(target, blueprint, options = {}) { + options = { + ...options, + activationKeys: Configuration.enableZoomIn + } + super(target, blueprint, options) + } + + fire() { + this.zoomInputObject = this.blueprint.getInputObject(Zoom) + zoomInputObject.enableZoonIn = true + } + + unfire() { + this.#zoomInputObject.enableZoom = false + } +} \ No newline at end of file diff --git a/js/input/keybaord/KeyboardSelectAll.js b/js/input/keybaord/KeyboardSelectAll.js index 4ded72c..3d43315 100755 --- a/js/input/keybaord/KeyboardSelectAll.js +++ b/js/input/keybaord/KeyboardSelectAll.js @@ -9,7 +9,10 @@ export default class KeyboardSelectAll extends IKeyboardShortcut { * @param {Object} options */ constructor(target, blueprint, options = {}) { - options = IKeyboardShortcut.keyOptionsParse(options, Configuration.selectAllKeyboardKey) + options = { + ...options, + activationKeys: Configuration.selectAllKeyboardKey + } super(target, blueprint, options) } diff --git a/js/input/mouse/IMouseClickDrag.js b/js/input/mouse/IMouseClickDrag.js index a47d6a4..64b691a 100644 --- a/js/input/mouse/IMouseClickDrag.js +++ b/js/input/mouse/IMouseClickDrag.js @@ -101,6 +101,7 @@ export default class IMouseClickDrag extends IPointing { if (self.started) { self.endDrag() } + self.unclicked() if (self.#trackingMouse) { const dragEvent = self.getEvent(Configuration.trackingMouseEventName.end) this.target.dispatchEvent(dragEvent) @@ -146,4 +147,7 @@ export default class IMouseClickDrag extends IPointing { endDrag() { } + + unclicked(location) { + } } diff --git a/js/input/mouse/Select.js b/js/input/mouse/Select.js index 7fc7f6c..a59eaec 100755 --- a/js/input/mouse/Select.js +++ b/js/input/mouse/Select.js @@ -18,7 +18,11 @@ export default class Select extends IMouseClickDrag { endDrag() { if (this.started) { this.selectorElement.finishSelecting() - } else { + } + } + + unclicked() { + if (!this.started) { this.blueprint.unselectAll() } } diff --git a/js/input/mouse/Zoom.js b/js/input/mouse/Zoom.js index c30236b..37df9f9 100755 --- a/js/input/mouse/Zoom.js +++ b/js/input/mouse/Zoom.js @@ -2,9 +2,27 @@ import IMouseWheel from "./IMouseWheel" export default class Zoom extends IMouseWheel { + #enableZoonIn = false + + get enableZoonIn() { + return this.#enableZoonIn + } + + set enableZoonIn(value) { + value = Boolean(value) + if (value == this.#enableZoonIn) { + return + } + this.#enableZoonIn = value + } + wheel(variation, location) { let zoomLevel = this.blueprint.getZoom() - zoomLevel -= variation + variation = -variation + if (!this.enableZoonIn && zoomLevel == 0 && variation > 0) { + return + } + zoomLevel += variation this.blueprint.setZoom(zoomLevel, location) } } diff --git a/js/serialization/Grammar.js b/js/serialization/Grammar.js index 1bb67af..e40b27c 100755 --- a/js/serialization/Grammar.js +++ b/js/serialization/Grammar.js @@ -1,7 +1,8 @@ import FunctionReferenceEntity from "../entity/FunctionReferenceEntity" import GuidEntity from "../entity/GuidEntity" -import Identifier from "../entity/Identifier" +import IdentifierEntity from "../entity/IdentifierEntity" import IntegerEntity from "../entity/IntegerEntity" +import KeyBindingEntity from "../entity/KeyBindingEntity" import LocalizedTextEntity from "../entity/LocalizedTextEntity" import ObjectEntity from "../entity/ObjectEntity" import ObjectReferenceEntity from "../entity/ObjectReferenceEntity" @@ -15,102 +16,7 @@ let P = Parsimmon export default class Grammar { - /** @param {Grammar} r */ - InlineWhitespace = r => P.regex(/[^\S\n]+/).desc("inline whitespace") - - /** @param {Grammar} r */ - InlineOptWhitespace = r => P.regex(/[^\S\n]*/).desc("inline optional whitespace") - - /** @param {Grammar} r */ - WhitespaceNewline = r => P.regex(/[^\S\n]*\n\s*/).desc("whitespace with at least a newline") - - /** @param {Grammar} r */ - Null = r => P.seq(P.string("("), r.InlineOptWhitespace, P.string(")")).map(_ => null).desc("null: ()") - - /** @param {Grammar} r */ - None = r => P.string("None").map(_ => new ObjectReferenceEntity({ type: "None", path: "" })).desc("none") - - /** @param {Grammar} r */ - Boolean = r => P.alt(P.string("True"), P.string("False")).map(v => v === "True" ? true : false).desc("either True or False") - - /** @param {Grammar} r */ - Number = r => P.regex(/[\-\+]?[0-9]+(?:\.[0-9]+)?/).map(Number).desc("a number") - - /** @param {Grammar} r */ - Integer = r => P.regex(/[\-\+]?[0-9]+/).map(v => new IntegerEntity(v)).desc("an integer") - - /** @param {Grammar} r */ - String = r => P.regex(/(?:[^"\\]|\\.)*/).wrap(P.string('"'), P.string('"')).desc('string (with possibility to escape the quote using \")') - - /** @param {Grammar} r */ - Word = r => P.regex(/[a-zA-Z]+/).desc("a word") - - /** @param {Grammar} r */ - Guid = r => P.regex(/[0-9a-zA-Z]{32}/).map(v => new GuidEntity({ value: v })).desc("32 digit hexadecimal (accepts all the letters for safety) value") - - /** @param {Grammar} */ - Identifier = r => P.regex(/\w+/).map(v => new Identifier(v)) - - /** @param {Grammar} r */ - PathSymbolEntity = r => P.regex(/[0-9a-zA-Z_]+/).map(v => new PathSymbolEntity({ value: v })) - - /** @param {Grammar} r */ - ReferencePath = r => P.seq(P.string("/"), r.PathSymbolEntity.map(v => v.toString()).sepBy1(P.string(".")).tieWith(".")) - .tie() - .atLeast(2) - .tie() - .desc('a path (words with possibly underscore, separated by ".", separated by "/")') - - /** @param {Grammar} r */ - Reference = r => P.alt( - r.None, - ...[r.ReferencePath.map(path => new ObjectReferenceEntity({ type: "", path: path }))].flatMap( - v => [v, v.trim(P.string('"'))] - ), - P.seqMap( - r.Word, - P.optWhitespace, - P.alt(P.string('"'), P.string('\'"')).chain( - result => r.ReferencePath.skip( - P.string(result.split("").reverse().join("")) - ) - ), - (referenceType, _, referencePath) => new ObjectReferenceEntity({ type: referenceType, path: referencePath }) - ) - ) - - /** @param {Grammar} r */ - AttributeName = r => r.Word.sepBy1(P.string(".")).tieWith(".").desc('words separated by ""') - - /** @param {Grammar} r */ - AttributeAnyValue = r => P.alt(r.Null, r.None, r.Boolean, r.Number, r.Integer, r.String, r.Guid, r.Reference, r.LocalizedText) - - /** @param {Grammar} r */ - LocalizedText = r => P.seqMap( - P.string(LocalizedTextEntity.lookbehind).skip(P.optWhitespace).skip(P.string("(")), - r.String.trim(P.optWhitespace), // namespace - P.string(","), - r.String.trim(P.optWhitespace), // key - P.string(","), - r.String.trim(P.optWhitespace), // value - P.string(")"), - (_, namespace, __, key, ___, value, ____) => new LocalizedTextEntity({ - namespace: namespace, - key: key, - value: value - }) - ) - - /** @param {Grammar} r */ - PinReference = r => P.seqMap( - r.PathSymbolEntity, - P.whitespace, - r.Guid, - (objectName, _, pinGuid) => new PinReferenceEntity({ - objectName: objectName, - pinGuid: pinGuid - }) - ) + /* --- Factory --- */ /** @param {Grammar} r */ static getGrammarForType(r, attributeType, defaultGrammar) { @@ -125,7 +31,7 @@ export default class Grammar { return r.String case GuidEntity: return r.Guid - case Identifier: + case IdentifierEntity: return r.Identifier case ObjectReferenceEntity: return r.Reference @@ -159,7 +65,7 @@ export default class Grammar { } /** @param {Grammar} r */ - static CreateAttributeGrammar = (r, entityType, valueSeparator = P.string("=").trim(P.optWhitespace)) => + static createAttributeGrammar = (r, entityType, valueSeparator = P.string("=").trim(P.optWhitespace)) => r.AttributeName.skip(valueSeparator) .chain(attributeName => { const attributeKey = attributeName.split(".") @@ -172,7 +78,7 @@ export default class Grammar { }) /** @param {Grammar} r */ - static CreateMultiAttributeGrammar = (r, entityType) => + static createMultiAttributeGrammar = (r, entityType) => /** * Basically this creates a parser that looks for a string like 'Key (A=False,B="Something",)' * Then it populates an object of type EntityType with the attribute values found inside the parentheses. @@ -181,7 +87,7 @@ export default class Grammar { entityType.lookbehind ? P.seq(P.string(entityType.lookbehind), P.optWhitespace, P.string("(")) : P.string("("), - Grammar.CreateAttributeGrammar(r, entityType) + Grammar.createAttributeGrammar(r, entityType) .trim(P.optWhitespace) .sepBy(P.string(",")) .skip(P.regex(/,?/).then(P.optWhitespace)), // Optional trailing comma @@ -192,11 +98,140 @@ export default class Grammar { return result }) - /** @param {Grammar} r */ - FunctionReference = r => Grammar.CreateMultiAttributeGrammar(r, FunctionReferenceEntity) + /* --- General --- */ /** @param {Grammar} r */ - Pin = r => Grammar.CreateMultiAttributeGrammar(r, PinEntity) + InlineWhitespace = r => P.regex(/[^\S\n]+/).desc("inline whitespace") + + /** @param {Grammar} r */ + InlineOptWhitespace = r => P.regex(/[^\S\n]*/).desc("inline optional whitespace") + + /** @param {Grammar} r */ + MultilineWhitespace = r => P.regex(/[^\S\n]*\n\s*/).desc("whitespace with at least a newline") + + /** @param {Grammar} r */ + Null = r => P.seq(P.string("("), r.InlineOptWhitespace, P.string(")")).map(_ => null).desc("null: ()") + + /** @param {Grammar} r */ + Boolean = r => P.alt(P.string("True"), P.string("False")).map(v => v === "True" ? true : false) + .desc("either True or False") + + /** @param {Grammar} r */ + Number = r => P.regex(/[\-\+]?[0-9]+(?:\.[0-9]+)?/).map(Number).desc("a number") + + /** @param {Grammar} r */ + Word = r => P.regex(/[a-zA-Z]+/).desc("a word") + + /** @param {Grammar} r */ + String = r => P.regex(/(?:[^"\\]|\\.)*/).wrap(P.string('"'), P.string('"')) + .desc('string (with possibility to escape the quote using \")') + + /** @param {Grammar} r */ + ReferencePath = r => P.seq( + P.string("/"), + r.PathSymbol + .map(v => v.toString()) + .sepBy1(P.string(".")) + .tieWith(".") + ) + .tie() + .atLeast(2) + .tie() + .desc('a path (words with possibly underscore, separated by ".", separated by "/")') + + /** @param {Grammar} r */ + AttributeName = r => r.Word.sepBy1(P.string(".")).tieWith(".").desc('words separated by ""') + + /* --- Entity --- */ + + /** @param {Grammar} r */ + None = r => P.string("None").map(_ => new ObjectReferenceEntity({ type: "None", path: "" })).desc("none") + + /** @param {Grammar} r */ + Integer = r => P.regex(/[\-\+]?[0-9]+/).map(v => new IntegerEntity(v)).desc("an integer") + + /** @param {Grammar} r */ + Guid = r => P.regex(/[0-9a-zA-Z]{32}/).map(v => new GuidEntity({ value: v })) + .desc("32 digit hexadecimal (accepts all the letters for safety) value") + + /** @param {Grammar} */ + Identifier = r => P.regex(/\w+/).map(v => new IdentifierEntity(v)) + + /** @param {Grammar} r */ + PathSymbol = r => P.regex(/[0-9a-zA-Z_]+/).map(v => new PathSymbolEntity({ value: v })) + + /** @param {Grammar} r */ + Reference = r => P.alt( + r.None, + ...[r.ReferencePath.map(path => new ObjectReferenceEntity({ type: "", path: path }))] + .flatMap(referencePath => [ + referencePath, // version having just path + referencePath.trim(P.string('"')) // Version having path surround with double quotes + ]), + P.seqMap( + r.Word, // Goes into referenceType + P.optWhitespace, // Goes into _ (ignored) + P.alt(...[r.ReferencePath].flatMap(referencePath => [ + referencePath.wrap(P.string(`"`), P.string(`"`)), + referencePath.wrap(P.string(`'"`), P.string(`"'`)) + ])), // Goes into referencePath + (referenceType, _, referencePath) => new ObjectReferenceEntity({ type: referenceType, path: referencePath }) + ), + r.Word.map(type => new ObjectReferenceEntity({ type: type, path: "" })), + ) + + /** @param {Grammar} r */ + LocalizedText = r => P.seqMap( + P.string(LocalizedTextEntity.lookbehind).skip(P.optWhitespace).skip(P.string("(")), // Goes into _ (ignored) + r.String.trim(P.optWhitespace), // Goes into namespace + P.string(","), // Goes into __ (ignored) + r.String.trim(P.optWhitespace), // Goes into key + P.string(","), // Goes into ___ (ignored) + r.String.trim(P.optWhitespace), // Goes into value + P.string(")"), // Goes into ____ (ignored) + (_, namespace, __, key, ___, value, ____) => new LocalizedTextEntity({ + namespace: namespace, + key: key, + value: value + }) + ) + + /** @param {Grammar} r */ + AttributeAnyValue = r => P.alt( + r.Null, + r.None, + r.Boolean, + r.Number, + r.Integer, + r.String, + r.Guid, + r.Reference, + r.LocalizedText) + + /** @param {Grammar} r */ + PinReference = r => P.seqMap( + r.PathSymbol, // Goes into objectNAme + P.whitespace, // Goes into _ (ignored) + r.Guid, // Goes into pinGuid + (objectName, _, pinGuid) => new PinReferenceEntity({ + objectName: objectName, + pinGuid: pinGuid + }) + ) + + /** @param {Grammar} r */ + FunctionReference = r => Grammar.createMultiAttributeGrammar(r, FunctionReferenceEntity) + + /** @param {Grammar} r */ + KeyBinding = r => P.alt( + r.Identifier.map(identifier => new KeyBindingEntity({ + Key: identifier + })), + Grammar.createMultiAttributeGrammar(r, KeyBindingEntity) + ) + + /** @param {Grammar} r */ + Pin = r => Grammar.createMultiAttributeGrammar(r, PinEntity) /** @param {Grammar} r */ CustomProperties = r => @@ -216,10 +251,10 @@ export default class Grammar { P .alt( r.CustomProperties, - Grammar.CreateAttributeGrammar(r, ObjectEntity) + Grammar.createAttributeGrammar(r, ObjectEntity) ) .sepBy1(P.whitespace), - P.seq(r.WhitespaceNewline, P.string("End"), P.whitespace, P.string("Object")), + P.seq(r.MultilineWhitespace, P.string("End"), P.whitespace, P.string("Object")), (_, attributes, __) => { let result = new ObjectEntity() attributes.forEach(attributeSetter => attributeSetter(result)) diff --git a/js/serialization/initializeSerializerFactory.js b/js/serialization/initializeSerializerFactory.js index 63f1d08..c89b121 100755 --- a/js/serialization/initializeSerializerFactory.js +++ b/js/serialization/initializeSerializerFactory.js @@ -2,7 +2,9 @@ import CustomSerializer from "./CustomSerializer" import FunctionReferenceEntity from "../entity/FunctionReferenceEntity" import GeneralSerializer from "./GeneralSerializer" import GuidEntity from "../entity/GuidEntity" +import IdentifierEntity from "../entity/IdentifierEntity" import IntegerEntity from "../entity/IntegerEntity" +import KeyBindingEntity from "../entity/KeyBindingEntity" import LocalizedTextEntity from "../entity/LocalizedTextEntity" import ObjectEntity from "../entity/ObjectEntity" import ObjectReferenceEntity from "../entity/ObjectReferenceEntity" @@ -12,29 +14,39 @@ import PinEntity from "../entity/PinEntity" import PinReferenceEntity from "../entity/PinReferenceEntity" import SerializerFactory from "./SerializerFactory" import ToStringSerializer from "./ToStringSerializer" -import Identifier from "../entity/Identifier" export default function initializeSerializerFactory() { + SerializerFactory.registerSerializer( ObjectEntity, new ObjectSerializer() ) + SerializerFactory.registerSerializer( PinEntity, new GeneralSerializer(v => `${PinEntity.lookbehind} (${v})`, PinEntity, "", ",", true) ) + SerializerFactory.registerSerializer( FunctionReferenceEntity, new GeneralSerializer(v => `(${v})`, FunctionReferenceEntity, "", ",", false) ) + + SerializerFactory.registerSerializer( + KeyBindingEntity, + new GeneralSerializer(v => `(${v})`, KeyBindingEntity, "", ",", false) + ) + SerializerFactory.registerSerializer( LocalizedTextEntity, new GeneralSerializer(v => `${LocalizedTextEntity.lookbehind}(${v})`, LocalizedTextEntity, "", ", ", false, "", _ => "") ) + SerializerFactory.registerSerializer( PinReferenceEntity, new GeneralSerializer(v => v, PinReferenceEntity, "", " ", false, "", _ => "") ) + SerializerFactory.registerSerializer( ObjectReferenceEntity, new CustomSerializer( @@ -45,8 +57,12 @@ export default function initializeSerializerFactory() { : "" )) ) - SerializerFactory.registerSerializer(Identifier, new ToStringSerializer(Identifier)) + + SerializerFactory.registerSerializer(IdentifierEntity, new ToStringSerializer(IdentifierEntity)) + SerializerFactory.registerSerializer(PathSymbolEntity, new ToStringSerializer(PathSymbolEntity)) + SerializerFactory.registerSerializer(GuidEntity, new ToStringSerializer(GuidEntity)) + SerializerFactory.registerSerializer(IntegerEntity, new ToStringSerializer(IntegerEntity)) } diff --git a/js/template/NodeTemplate.js b/js/template/NodeTemplate.js index a779fe7..50506fa 100755 --- a/js/template/NodeTemplate.js +++ b/js/template/NodeTemplate.js @@ -28,6 +28,9 @@ export default class NodeTemplate extends SelectableDraggableTemplate { + ` } diff --git a/js/template/SelectorTemplate.js b/js/template/SelectorTemplate.js index 342cfdc..fea3157 100755 --- a/js/template/SelectorTemplate.js +++ b/js/template/SelectorTemplate.js @@ -12,7 +12,6 @@ export default class SelectorTemplate extends ITemplate { */ apply(selector) { super.apply(selector) - selector.classList.add("ueb-positioned") this.applyFinishSelecting(selector) } diff --git a/rollup.config.js b/rollup.config.js index 7887b55..15f78e7 100755 --- a/rollup.config.js +++ b/rollup.config.js @@ -18,7 +18,7 @@ export default { copy({ targets: [ { - src: ["font/*"], + src: ["assets/fonts/*"], dest: "dist/font" } ] diff --git a/scss/ueblueprint-node-value-type-color.scss b/scss/ueblueprint-node-value-type-color.scss index bb1e3e4..a425d24 100755 --- a/scss/ueblueprint-node-value-type-color.scss +++ b/scss/ueblueprint-node-value-type-color.scss @@ -6,12 +6,12 @@ } .ueb-pin-boolean { - $ueb-pin-color : #930000; + $ueb-pin-color : rgb(30%, 0, 0); --ueb-pin-color: #{$ueb-pin-color}; } -.ueb-pin-int { - $ueb-pin-color : #1fe0ad; +.ueb-pin-class { + $ueb-pin-color : #5800bb; --ueb-pin-color: #{$ueb-pin-color}; } @@ -20,8 +20,19 @@ --ueb-pin-color: #{$ueb-pin-color}; } -.ueb-pin-vector { - $ueb-pin-color : #fcc823; +.ueb-pin-int { + $ueb-pin-color : #1fe0ad; + --ueb-pin-color: #{$ueb-pin-color}; +} + +.ueb-pin-name { + $ueb-pin-color : #cb81fc; + --ueb-pin-color: #{$ueb-pin-color}; +} + +.ueb-pin-object { + $ueb-pin-color : #00a8f2; + $ueb-pin-color : rgba(0%, 40%, 01%); --ueb-pin-color: #{$ueb-pin-color}; } @@ -36,12 +47,7 @@ --ueb-pin-background: linear-gradient(90deg, #fc00d220, #fc00d280 15%, #fc00d250 85%, transparent); } -.ueb-pin-name { - $ueb-pin-color : #cb81fc; - --ueb-pin-color: #{$ueb-pin-color}; -} - -.ueb-pin-object { - $ueb-pin-color : #00a8f2; +.ueb-pin-vector { + $ueb-pin-color : #fcc823; --ueb-pin-color: #{$ueb-pin-color}; } \ No newline at end of file diff --git a/scss/ueblueprint-style.scss b/scss/ueblueprint-style.scss index 7680898..166c6f4 100644 --- a/scss/ueblueprint-style.scss +++ b/scss/ueblueprint-style.scss @@ -262,6 +262,14 @@ ueb-blueprint[data-drag-scrolling="false"][data-selecting="false"] ueb-node { padding-right: 8px; } +.ueb-node-expand { + display: none; +} + +ueb-node[data-advanced-display] .ueb-node-expand { + display: block; +} + ueb-pin { display: block; padding: 1px 2px;