Initial commit
This commit is contained in:
parent
f642adede1
commit
a57110514f
|
@ -0,0 +1,93 @@
|
||||||
|
import { SVG } from './DOM.js'
|
||||||
|
import { makeDraggable } from './Draggable.js'
|
||||||
|
import { Group } from './Group.js'
|
||||||
|
|
||||||
|
const Box = ( title, inputs, outputs )=> {
|
||||||
|
let $dom = makeDraggable( Group('Box') ), $fo, $box
|
||||||
|
|
||||||
|
$dom.append(
|
||||||
|
$fo = SVG`<foreignObject width="180" height="100">
|
||||||
|
<box xmlns="http://www.w3.org/1999/xhtml">
|
||||||
|
<header>${title}</header>
|
||||||
|
${outputs.map( str=> `<socket output>${str}</socket>`).join('')}
|
||||||
|
${inputs.map( str=> `<socket input>${str}</socket>`).join('')}
|
||||||
|
</box>
|
||||||
|
</foreignObject>`[0]
|
||||||
|
)
|
||||||
|
$box = $fo.$`box`
|
||||||
|
$dom.resize = ({ width, height })=> {
|
||||||
|
width && $fo.setAttribute('width', width )
|
||||||
|
height && $fo.setAttribute('height', height )
|
||||||
|
$dom.updateAnchors()
|
||||||
|
}
|
||||||
|
|
||||||
|
let heightInvalidated = false
|
||||||
|
$dom.invalidateHeight = ()=> {
|
||||||
|
heightInvalidated = true
|
||||||
|
requestAnimationFrame( ()=> $dom.resize({height: $box.offsetHeight }) )
|
||||||
|
}
|
||||||
|
let widthInvalidated = false
|
||||||
|
$dom.invalidateWidth = ()=> {
|
||||||
|
widthInvalidated = true
|
||||||
|
requestAnimationFrame( ()=> $dom.resize({width: $box.offsetWidth }) )
|
||||||
|
}
|
||||||
|
|
||||||
|
setTimeout( $dom.invalidateHeight, 1000 )
|
||||||
|
|
||||||
|
|
||||||
|
// Draw anchors
|
||||||
|
$box.$$`socket`.map( sock=> sock.anchor = SVG`<circle class="anchor"/>`[0] )
|
||||||
|
.map( a=> $dom.append(a) )
|
||||||
|
|
||||||
|
$dom.updateAnchors = ()=>
|
||||||
|
$box.$$`socket`
|
||||||
|
.map( sock=>
|
||||||
|
sock.anchor.moveTo({
|
||||||
|
x: sock.hasAttribute('output')
|
||||||
|
? $box.offsetWidth
|
||||||
|
: 0
|
||||||
|
, y: sock.offsetTop + ( sock.offsetHeight / 2 ) + 1
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
$dom.on`contextmenu`( e=> e.menu = Object.assign(e.menu || {}, {
|
||||||
|
'Box': {
|
||||||
|
'Debug': e=> console.log($dom)
|
||||||
|
, 'Convert to >': {
|
||||||
|
ZigZag: e=> $box.style.setProperty('background','orange')
|
||||||
|
, Flat: e=> $dom.style.setProperty('--wave-color','var(--fl-color)')
|
||||||
|
, Impulsive: e=> $dom.style.setProperty('--wave-color','var(--im-color)')
|
||||||
|
}
|
||||||
|
// 'Convert to ZZ':e=> $dom.style.setProperty('--wave-color','var(--zz-color)')
|
||||||
|
// , ...( $dom.fibos ? {['Toggle fibos']: $dom.toggleFibos} : {} )
|
||||||
|
// , ...( w.isEmpty ? {['Subdivise']: $dom.subdivise} : {} )
|
||||||
|
}
|
||||||
|
}) , {capture:true} )
|
||||||
|
|
||||||
|
return $dom
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// class Box extends HTMLElement {
|
||||||
|
|
||||||
|
// static styles = CSS`
|
||||||
|
|
||||||
|
// `
|
||||||
|
|
||||||
|
// constructor()
|
||||||
|
// {
|
||||||
|
// super()
|
||||||
|
// this.$svg =
|
||||||
|
// this.addListeners()
|
||||||
|
|
||||||
|
// }
|
||||||
|
|
||||||
|
// addListeners()
|
||||||
|
// {
|
||||||
|
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// customElements.define( 'vp-box', Box )
|
||||||
|
|
||||||
|
export { Box }
|
|
@ -0,0 +1,265 @@
|
||||||
|
import { EventojLancilo } from './EventojLancilo.js'
|
||||||
|
EventojLancilo( EventTarget ) // same event handler for everybody
|
||||||
|
|
||||||
|
String.merge = (ss,...pp)=> [].concat(ss).map( (s,i)=> s+(i in pp?pp[i]:'') ).join('')
|
||||||
|
const logNpass = o=> console.log(o)||o
|
||||||
|
// const ss_pp = (ss,...pp)=> [].concat(ss).map( (s,i)=> s+(i in pp?pp[i]:'') ).join('')
|
||||||
|
// const $ = (ss,...pp)=> document.querySelector( ss_pp(ss,...pp) )
|
||||||
|
// const $$ = (ss,...pp)=> [...document.querySelectorAll( ss_pp(ss,...pp) )]
|
||||||
|
const DOM = (ss,...pp)=> {
|
||||||
|
let t = document.createElementNS( 'http://www.w3.org/1999/xhtml', 'template' )
|
||||||
|
t.innerHTML = String.merge(ss,...pp)
|
||||||
|
return t.content
|
||||||
|
}
|
||||||
|
const SVG = (ss,...pp)=> DOM`<svg xmlns="http://www.w3.org/2000/svg">${String.merge(ss,...pp)}</svg>`
|
||||||
|
.children[0].childNodes
|
||||||
|
|
||||||
|
const CSS = (ss,...pp)=> {
|
||||||
|
let styles = new CSSStyleSheet()
|
||||||
|
styles.replaceSync( String.merge(ss,...pp) )
|
||||||
|
return styles
|
||||||
|
}
|
||||||
|
|
||||||
|
const $ = (ss,...pp)=> document.$( ss,...pp )
|
||||||
|
const $$ = (ss,...pp)=> document.$$( ss,...pp )
|
||||||
|
Node.prototype.$ = function(ss,...pp)
|
||||||
|
{
|
||||||
|
return this.querySelector( String.merge(ss,...pp) )
|
||||||
|
}
|
||||||
|
Node.prototype.$$ = function(ss,...pp)
|
||||||
|
{
|
||||||
|
return [...this.querySelectorAll( String.merge(ss,...pp) )]
|
||||||
|
}
|
||||||
|
Node.prototype.add = function( ...args )
|
||||||
|
{
|
||||||
|
this.append( ...args )
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
// Node.prototype.on = function(ss,...pp)
|
||||||
|
// {
|
||||||
|
// return (...args)=> ( this.addEventListener( ss_pp(ss,...pp), ...args ), this )
|
||||||
|
// }
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Object.defineProperties( SVGRect.prototype, {
|
||||||
|
center: {
|
||||||
|
get(){ return {
|
||||||
|
x: this.x + ( this.width/2 )
|
||||||
|
, y: this.y + ( this.height/2 )
|
||||||
|
}}
|
||||||
|
, set( v ){}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
/** <line> **/
|
||||||
|
|
||||||
|
// @see https://cp-algorithms.com/geometry/segment-to-line.html
|
||||||
|
// SVGLineElement.prototype.getCoef = function line()
|
||||||
|
// {
|
||||||
|
// let P = { x: this.x1.baseVal.value, y: this.y1.baseVal.value }
|
||||||
|
// let Q = { x: this.x2.baseVal.value, y: this.y2.baseVal.value }
|
||||||
|
// //console.log(P,Q)
|
||||||
|
// let A = P.y - Q.y
|
||||||
|
// let B = Q.x - P.x
|
||||||
|
// let C = ( -A * P.x ) - ( B * P.y )
|
||||||
|
// return [A,B,C]
|
||||||
|
// }
|
||||||
|
// // @see https://cp-algorithms.com/geometry/lines-intersection.html
|
||||||
|
// SVGLineElement.prototype.intersect = function intersect( line )
|
||||||
|
// {
|
||||||
|
// let [ a1,b1,c1 ] = this.getCoef()
|
||||||
|
// // console.log(this.getCoef())
|
||||||
|
// let [ a2,b2,c2 ] = line.getCoef()
|
||||||
|
// // console.log(line.getCoef())
|
||||||
|
// let x = - ((c1*b2)-(c2*b1))/((a1*b2)-(a2*b1))
|
||||||
|
// let y = - ((a1*c2)-(a2*c1))/((a1*b2)-(a2*b1))
|
||||||
|
// return { x, y }
|
||||||
|
// }
|
||||||
|
//PointProperty('_p1','x1','y1')
|
||||||
|
const PointProperty = ( _, x, y )=> ({
|
||||||
|
get(){ return this[_] = this[_] || new DOMPoint(
|
||||||
|
this[x].baseVal.value,
|
||||||
|
this[y].baseVal.value
|
||||||
|
)}
|
||||||
|
, set( obj ){
|
||||||
|
if( 'x' in obj && 'y' in obj )
|
||||||
|
{
|
||||||
|
const ev = e=> {
|
||||||
|
this.setAttribute( x, obj.x )
|
||||||
|
this.setAttribute( y, obj.y )
|
||||||
|
this.dispatchEvent( new Event('move') )
|
||||||
|
}
|
||||||
|
this[_]
|
||||||
|
&& this[_].removeEventListener
|
||||||
|
&& this[_].removeEventListener('move', ev)
|
||||||
|
this[_] = obj
|
||||||
|
obj.addEventListener && obj.addEventListener('move', ev)
|
||||||
|
ev()
|
||||||
|
this.dispatchEvent( new Event('pointchange') )
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
Object.defineProperties(SVGLineElement.prototype, {
|
||||||
|
p1: PointProperty('_p1','x1','y1')
|
||||||
|
// {
|
||||||
|
// get(){ return this._p1 = this._p1 || new DOMPoint(
|
||||||
|
// this.x1.baseVal.value,
|
||||||
|
// this.y1.baseVal.value
|
||||||
|
// )
|
||||||
|
// }
|
||||||
|
// , set( obj ){
|
||||||
|
// if( 'x' in obj && 'y' in obj )
|
||||||
|
// {
|
||||||
|
// const ev = e=> {
|
||||||
|
// this.setAttribute('x1', obj.x)
|
||||||
|
// this.setAttribute('y1', obj.y)
|
||||||
|
// this.dispatchEvent( new Event('move') )
|
||||||
|
// }
|
||||||
|
// this._p1
|
||||||
|
// && this._p1.removeEventListener
|
||||||
|
// && this._p1.removeEventListener('move', ev)
|
||||||
|
// this._p1 = obj
|
||||||
|
// obj.addEventListener && obj.addEventListener('move', ev)
|
||||||
|
// ev()
|
||||||
|
// this.dispatchEvent( new Event('pointchange') )
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
, p2: PointProperty('_p2','x2','y2')
|
||||||
|
// {
|
||||||
|
// get(){ return this._p2 = this._p2 || new DOMPoint(
|
||||||
|
// this.x2.baseVal.value,
|
||||||
|
// this.y2.baseVal.value
|
||||||
|
// )}
|
||||||
|
// , set( obj ){
|
||||||
|
// if( 'x' in obj && 'y' in obj )
|
||||||
|
// {
|
||||||
|
// const ev = e=> {
|
||||||
|
// this.setAttribute('x2', obj.x)
|
||||||
|
// this.setAttribute('y2', obj.y)
|
||||||
|
// this.dispatchEvent( new Event('move') )
|
||||||
|
// }
|
||||||
|
// this._p2
|
||||||
|
// && this._p2.removeEventListener
|
||||||
|
// && this._p2.removeEventListener('move', ev)
|
||||||
|
// this._p2 = obj
|
||||||
|
// obj.addEventListener && obj.addEventListener('move', ev)
|
||||||
|
// ev()
|
||||||
|
// this.dispatchEvent( new Event('pointchange') )
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
})
|
||||||
|
|
||||||
|
//@TODO Debounce move event fired twice when changing x and y
|
||||||
|
//@TODO Adapt which attribute is set ( x, cx, x1, transform ), depending on localName ( <rect>, <circle>, <g>, ... )
|
||||||
|
Object.defineProperties(SVGElement.prototype, {
|
||||||
|
x: {
|
||||||
|
get(){ return parseFloat((this.attributes.x||this.attributes.cx||this.attributes.x1||{value:0}).value) }
|
||||||
|
, set( v ){
|
||||||
|
if( !isFinite(v) ) return v
|
||||||
|
if( this.localName == 'g' )
|
||||||
|
this.setAttribute('transform',`translate(${v},${this.y})`)
|
||||||
|
this.setAttribute('x',v)
|
||||||
|
this.setAttribute('cx',v)
|
||||||
|
this.setAttribute('x1',v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
, y: {
|
||||||
|
get(){ return parseFloat((this.attributes.y||this.attributes.cy||this.attributes.y1||{value:0}).value) }
|
||||||
|
, set( v ){
|
||||||
|
if( !isFinite(v) ) return v
|
||||||
|
if( this.localName == 'g' )
|
||||||
|
this.setAttribute('transform',`translate(${this.x},${v})`)
|
||||||
|
this.setAttribute('y',v)
|
||||||
|
this.setAttribute('cy',v)
|
||||||
|
this.setAttribute('y1',v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
, moveTo: {
|
||||||
|
value: function({ x, y }){
|
||||||
|
this.x = x
|
||||||
|
this.y = y
|
||||||
|
this.dispatchEvent( new Event('move') )
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
}
|
||||||
|
, moveBy: {
|
||||||
|
value: function({ x, y }){
|
||||||
|
this.x += x
|
||||||
|
this.y += y
|
||||||
|
this.dispatchEvent( new Event('move') )
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
Object.defineProperties(SVGSVGElement.prototype, {
|
||||||
|
zoom: {
|
||||||
|
get(){ return parseFloat((this.attributes.x||this.attributes.cx||this.attributes.x1||{value:0}).value) }
|
||||||
|
, set( v ){
|
||||||
|
if( !isFinite(v) ) return v
|
||||||
|
if( this.localName == 'g' )
|
||||||
|
this.setAttribute('transform',`translate(${v},${this.y})`)
|
||||||
|
this.setAttribute('x',v)
|
||||||
|
this.setAttribute('cx',v)
|
||||||
|
this.setAttribute('x1',v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
, pan: //PointProperty('_pan','pan-x','pan-y')
|
||||||
|
{
|
||||||
|
get(){ return this._pan = this._pan || new DOMPoint(
|
||||||
|
this.x2.baseVal.value,
|
||||||
|
this.y2.baseVal.value
|
||||||
|
)}
|
||||||
|
, set( obj ){
|
||||||
|
if( 'x' in obj && 'y' in obj )
|
||||||
|
{
|
||||||
|
const ev = e=> {
|
||||||
|
this.setAttribute('x2', obj.x)
|
||||||
|
this.setAttribute('y2', obj.y)
|
||||||
|
this.dispatchEvent( new Event('move') )
|
||||||
|
}
|
||||||
|
this._p2
|
||||||
|
&& this._p2.removeEventListener
|
||||||
|
&& this._p2.removeEventListener('move', ev)
|
||||||
|
this._p2 = obj
|
||||||
|
obj.addEventListener && obj.addEventListener('move', ev)
|
||||||
|
ev()
|
||||||
|
this.dispatchEvent( new Event('pointchange') )
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
, panAndZoom: {
|
||||||
|
value( x, y, z )
|
||||||
|
{
|
||||||
|
let { x:vx, y:vy, width:vw, height:vh } = this.viewBox.baseVal
|
||||||
|
, vz
|
||||||
|
vz = Math.min( vw, vh ) / 2
|
||||||
|
vx = vx + ( vw / 2 )
|
||||||
|
vy = vy + ( vh / 2 )
|
||||||
|
|
||||||
|
requestAnimationFrame( ()=> {
|
||||||
|
// this.setAttribute('viewBox', `${typeof x == 'number' ? x-(z||vz) : vx} ${typeof y == 'number' ? y-(z||vz) : vy} ${z ? z*2 : vw} ${z ? z*2 : vh}`)
|
||||||
|
this.setAttribute('viewBox', `${(x||vx)-(z||vz)} ${(y||vy)-(z||vz)} ${(z||vz)*2} ${(z||vz)*2}`)
|
||||||
|
console.log('viewBox', `${(x||vx)-(z||vz)} ${(y||vy)-(z||vz)} ${(z||vz)*2} ${(z||vz)*2}`)
|
||||||
|
|
||||||
|
// this.setAttribute('pan', `${x} ${y}`)
|
||||||
|
// this.setAttribute('zoom', `${z}`)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
export {
|
||||||
|
logNpass
|
||||||
|
, DOM
|
||||||
|
, SVG
|
||||||
|
, CSS
|
||||||
|
, $
|
||||||
|
, $$
|
||||||
|
}
|
|
@ -0,0 +1,135 @@
|
||||||
|
// const UI = {}
|
||||||
|
|
||||||
|
|
||||||
|
// SVG.Point = ( x, y )=> Object.assign( document.documentElement.createSVGPoint(), {x,y} )
|
||||||
|
|
||||||
|
Symbol.Draggable = Symbol`Draggable`
|
||||||
|
|
||||||
|
var Draggable = obj=> {
|
||||||
|
|
||||||
|
if( Symbol.Draggable in obj ) return obj
|
||||||
|
obj[Symbol.Draggable] = true
|
||||||
|
|
||||||
|
obj.startDrag = e=> {
|
||||||
|
let mouse = new DOMPoint( e.clientX , e.clientY )//.matrixTransform( obj.getCTM().inverse() )
|
||||||
|
mouse.x /= obj.ownerSVGElement.currentScale
|
||||||
|
mouse.y /= obj.ownerSVGElement.currentScale
|
||||||
|
|
||||||
|
obj.drag.offset = new DOMPoint( mouse.x - obj.x, mouse.y - obj.y )
|
||||||
|
obj.setAttribute( 'dragged', true )
|
||||||
|
console.log(e, mouse, obj.drag.offset)
|
||||||
|
}
|
||||||
|
|
||||||
|
obj.drag = function(e)
|
||||||
|
{//debugger;
|
||||||
|
if(e.buttons === 1)
|
||||||
|
{
|
||||||
|
let mouse = new DOMPoint( e.clientX / obj.ownerSVGElement.currentScale, e.clientY / obj.ownerSVGElement.currentScale )//.matrixTransform( obj.getScreenCTM().inverse() )
|
||||||
|
, pos = new DOMPoint( mouse.x - obj.drag.offset.x, mouse.y - obj.drag.offset.y)
|
||||||
|
|
||||||
|
// pos.x /= obj.ownerSVGElement.currentScale
|
||||||
|
// pos.y /= obj.ownerSVGElement.currentScale
|
||||||
|
this.moveTo( pos )
|
||||||
|
this.dispatchEvent( new Event('move') )
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
obj.stopDrag = e=> obj.removeAttribute('dragged')
|
||||||
|
|
||||||
|
obj.on`mousedown`( obj.startDrag )
|
||||||
|
// ,onmousemove:e=> e.buttons === 1 && e.target.drag(e)
|
||||||
|
//obj.onmouseup = e=> e.currentTarget.setAttribute(' dragged', false )
|
||||||
|
|
||||||
|
return obj
|
||||||
|
}
|
||||||
|
Draggable.startGlobalDrag = e=> e.target.$$`[dragged]`.filter(el=>el.draggable).map( el=> el.drag(e) )
|
||||||
|
Draggable.stopGlobalDrag = e=> e.target.$$`[dragged]`.map( el=> el.removeAttribute('dragged') )
|
||||||
|
|
||||||
|
// $$`[draggable]`
|
||||||
|
// .map( Draggable )
|
||||||
|
|
||||||
|
// document.documentElement.on`mousemove`( e=> $$`[dragged]`.filter(el=>el.draggable).map( el=> el.drag(e) ) , true )
|
||||||
|
// document.documentElement.onmouseup = e=> $$`[dragged]`.map( el=> el.removeAttribute('dragged') )
|
||||||
|
|
||||||
|
|
||||||
|
// Makes an element in an SVG document draggable.
|
||||||
|
// Fires custom `dragstart`, `drag`, and `dragend` events on the
|
||||||
|
// element with the `detail` property of the event carrying XY
|
||||||
|
// coordinates for the location of the element.
|
||||||
|
function makeDraggable(el)
|
||||||
|
{
|
||||||
|
if (!el) return console.error('makeDraggable() needs an element')
|
||||||
|
|
||||||
|
let svg, pt, doc, root
|
||||||
|
//var svg = el;
|
||||||
|
//while (svg && svg.tagName!='svg') svg=svg.parentNode;
|
||||||
|
//if (!svg) return console.error(el,'must be inside an SVG wrapper');
|
||||||
|
//var pt=svg.createSVGPoint(), doc=svg.ownerDocument;
|
||||||
|
|
||||||
|
//var root = doc.rootElement || doc.body || svg;
|
||||||
|
let xlate, txStartX, txStartY, mouseStart
|
||||||
|
let xforms = el.transform.baseVal
|
||||||
|
|
||||||
|
el.addEventListener('mousedown', e=> e.which == 1 && startMove(e), false )
|
||||||
|
|
||||||
|
function startMove(evt)
|
||||||
|
{
|
||||||
|
svg = el.ownerSVGElement
|
||||||
|
pt = svg.createSVGPoint()
|
||||||
|
doc = svg.ownerDocument
|
||||||
|
root = doc.rootElement || doc.body || svg
|
||||||
|
|
||||||
|
// We listen for mousemove/up on the root-most
|
||||||
|
// element in case the mouse is not over el.
|
||||||
|
root.addEventListener('mousemove',handleMove,false)
|
||||||
|
root.addEventListener('mouseup', finishMove,false)
|
||||||
|
|
||||||
|
// Ensure that the first transform is a translate()
|
||||||
|
xlate = xforms.numberOfItems>0 && xforms.getItem(0)
|
||||||
|
if (!xlate || xlate.type != SVGTransform.SVG_TRANSFORM_TRANSLATE){
|
||||||
|
xlate = xforms.createSVGTransformFromMatrix( svg.createSVGMatrix() )
|
||||||
|
xforms.insertItemBefore( xlate, 0 )
|
||||||
|
}
|
||||||
|
txStartX = xlate.matrix.e
|
||||||
|
txStartY = xlate.matrix.f
|
||||||
|
mouseStart = inElementSpace(evt)
|
||||||
|
fireEvent('dragstart')
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleMove(evt)
|
||||||
|
{
|
||||||
|
var point = inElementSpace(evt)
|
||||||
|
xlate.setTranslate(
|
||||||
|
txStartX + point.x - mouseStart.x,
|
||||||
|
txStartY + point.y - mouseStart.y
|
||||||
|
)
|
||||||
|
fireEvent('drag')
|
||||||
|
}
|
||||||
|
|
||||||
|
function finishMove(evt)
|
||||||
|
{
|
||||||
|
root.removeEventListener('mousemove',handleMove,false)
|
||||||
|
root.removeEventListener('mouseup', finishMove,false)
|
||||||
|
fireEvent('dragend')
|
||||||
|
}
|
||||||
|
|
||||||
|
function fireEvent(eventName)
|
||||||
|
{
|
||||||
|
var event = new Event(eventName)
|
||||||
|
event.detail = { x:xlate.matrix.e, y:xlate.matrix.f }
|
||||||
|
return el.dispatchEvent(event)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert mouse position from screen space to coordinates of el
|
||||||
|
function inElementSpace(evt)
|
||||||
|
{
|
||||||
|
pt.x = evt.clientX
|
||||||
|
pt.y = evt.clientY
|
||||||
|
return pt.matrixTransform( el.parentNode.getScreenCTM().inverse() )
|
||||||
|
}
|
||||||
|
|
||||||
|
return el
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export { Draggable, makeDraggable }
|
|
@ -0,0 +1,98 @@
|
||||||
|
|
||||||
|
var Event = Event || class Event { constructor(type){this.type=type} }
|
||||||
|
// eventoj lancilo = events launcher
|
||||||
|
var EventojLancilo = function( obj )
|
||||||
|
{
|
||||||
|
obj = this instanceof EventojLancilo ? this : obj
|
||||||
|
obj = obj instanceof Function ? obj.prototype : obj
|
||||||
|
|
||||||
|
let has = {
|
||||||
|
add: obj.addEventListener || obj.on
|
||||||
|
, rem: obj.removeEventListener || obj.off
|
||||||
|
, emit: obj.dispatchEvent || obj.emit || obj.fire || obj.fireEvent
|
||||||
|
}
|
||||||
|
// console.log(has)
|
||||||
|
if( !has.add )
|
||||||
|
obj[Symbol.Eventoj] = {}
|
||||||
|
|
||||||
|
// obj.on = tplOrCall(add) tpl?
|
||||||
|
// obj.on('a',cb) n ok
|
||||||
|
// obj.on('a',cb,opt) n ok
|
||||||
|
// obj.on('a b',cb) n ok
|
||||||
|
// obj.on(['a','b'],cb) y ko
|
||||||
|
// obj.on(['a','b'],cb,opt) n ok
|
||||||
|
// obj.on(['a','b','c'],cb,{}) y ko
|
||||||
|
// obj.on({a:cba,b:cb2,c:cb3}) n ok
|
||||||
|
|
||||||
|
// obj.on('a')(cb) n ko
|
||||||
|
// obj.on(['a'])(cb) y ok
|
||||||
|
|
||||||
|
// obj.on`a`(cb) y ok
|
||||||
|
// obj.on`${str}change`(cb) y ok
|
||||||
|
function tplOrCall( fn )
|
||||||
|
{
|
||||||
|
return function( ss, ...pp )
|
||||||
|
{
|
||||||
|
let isTpl = Array.isArray( ss ) && ss.every( o=> typeof o == 'string' ) && ss.length == pp.length + 1
|
||||||
|
return isTpl
|
||||||
|
? (...a)=> fn.bind(this)( String.merge(ss,...pp), ...a )
|
||||||
|
: fn.bind(this)( ss, ...pp )
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// obj.on(['a','b'],cb,opt)
|
||||||
|
// obj.on({a:cba,b:cb2,c:cb3},opt)
|
||||||
|
// obj.on('a b',cb,opt)
|
||||||
|
function multi( fn )
|
||||||
|
{
|
||||||
|
return ( ev, ...args )=>
|
||||||
|
Array.isArray( ev )
|
||||||
|
? ev.map( e=> fn(e,...args) )
|
||||||
|
: typeof ev == 'object'
|
||||||
|
? Object.keys( ev ).map( e=> fn(e,ev[e],...args) )
|
||||||
|
: fn(ev,...args)
|
||||||
|
}
|
||||||
|
function on( event, handler, options )
|
||||||
|
{
|
||||||
|
let evs = this[Symbol.Eventoj][event] = this[Symbol.Eventoj][event] || []
|
||||||
|
!evs.includes( handler )
|
||||||
|
&& evs.push( handler )
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
function one( event, handler, options )
|
||||||
|
{
|
||||||
|
return on( event, (...args)=> off(handler) && handler(...args) )
|
||||||
|
}
|
||||||
|
function off( event, handler )
|
||||||
|
{
|
||||||
|
let evs = obj[Symbol.Eventoj][event] = obj[Symbol.Eventoj][event] || []
|
||||||
|
evs.includes( handler )
|
||||||
|
&& evs.splice( evs.indexOf(handler), 1 )
|
||||||
|
return obj
|
||||||
|
}
|
||||||
|
function fire( event, ...args )
|
||||||
|
{
|
||||||
|
event = typeof event == 'string'
|
||||||
|
? new Event( event )
|
||||||
|
: event
|
||||||
|
event.target = obj
|
||||||
|
event.details = args
|
||||||
|
event.type in obj[Symbol.Eventoj]
|
||||||
|
&& obj[Symbol.Eventoj][event.type].map( h=> h.call(obj, event, ...args) )
|
||||||
|
return event
|
||||||
|
}
|
||||||
|
Object.defineProperties( obj, {
|
||||||
|
on: { value: tplOrCall(has.add ? has.add : on) }
|
||||||
|
, off: { value: tplOrCall(has.rem ? has.rem : off) }
|
||||||
|
, fire: { value: tplOrCall(has.emit ? has.emit : fire) }
|
||||||
|
, one: { value: tplOrCall(has.add ? has.add : one) }
|
||||||
|
, once: { value: tplOrCall(has.add ? has.add : one) }
|
||||||
|
, addEventListener: { value: tplOrCall(has.add ? has.add : on) }
|
||||||
|
, removeEventListener: { value: tplOrCall(has.rem ? has.rem : off) }
|
||||||
|
, dispatchEvent: { value: tplOrCall(has.emit ? has.emit : fire) }
|
||||||
|
, emit: { value: tplOrCall(has.emit ? has.emit : fire) }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
Symbol.Eventoj = Symbol`Eventoj`
|
||||||
|
|
||||||
|
|
||||||
|
export { Event, EventojLancilo }
|
|
@ -0,0 +1,8 @@
|
||||||
|
import { SVG } from './DOM.js'
|
||||||
|
|
||||||
|
const Group = ( cls )=> {
|
||||||
|
let $dom = SVG`<g${cls ? ` class="${cls}"` : ''}/>`[0]
|
||||||
|
return $dom
|
||||||
|
}
|
||||||
|
|
||||||
|
export { Group }
|
|
@ -0,0 +1,61 @@
|
||||||
|
import { SVG, DOM } from './DOM.js'
|
||||||
|
/**
|
||||||
|
* Context menus globally managed
|
||||||
|
* <node>s that have a contextmenu handler at capture time put their menu
|
||||||
|
* under event.menu property and this final bubble handler on document
|
||||||
|
* creates the menu HTML in a SVG's <foreignObject>.
|
||||||
|
*/
|
||||||
|
document.xxxoncontextmenu = e=> {
|
||||||
|
console.log(e)
|
||||||
|
// console.log(e.path)
|
||||||
|
console.log(e.menu)
|
||||||
|
// let items = e.menu//path.reverse().reduce( (m,n)=> ({...m,...(n.menu?n.menu:{})}),{})
|
||||||
|
// console.log(items, Object.keys(items).length )
|
||||||
|
if( e.menu )
|
||||||
|
{
|
||||||
|
e.preventDefault()
|
||||||
|
// let menu
|
||||||
|
// , layer = UI.Overlay( menu = UI.Menu( items ) )
|
||||||
|
// menu.setAttribute( 'x', e.clientX )
|
||||||
|
// menu.setAttribute( 'y', e.clientY )
|
||||||
|
// menu.style.transform = `translate( ${e.clientX}px, ${e.clientY}px )`
|
||||||
|
// menu.style.position = 'absolute'
|
||||||
|
// document.documentElement.append( layer )
|
||||||
|
document.documentElement.append(
|
||||||
|
Overlay(
|
||||||
|
ContextMenu( e, e.menu )
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const Overlay = ( ...items )=> {
|
||||||
|
let html = SVG`<foreignObject width="100%" height="100%"/>`[0]
|
||||||
|
html.append( ...items )
|
||||||
|
html.one`click`( e=> /*e.target == html && */html.remove() )
|
||||||
|
return html
|
||||||
|
}
|
||||||
|
const Menu = ( items, label )=> {
|
||||||
|
let html = DOM`<menu xmlns="http://www.w3.org/1999/xhtml"${label ? ` label="${label}"` : ''}></menu>`.children[0]
|
||||||
|
, btn
|
||||||
|
html.append(
|
||||||
|
...Object.keys( items )
|
||||||
|
.map( k=> typeof items[k] == 'object'
|
||||||
|
? Menu( items[k], k )
|
||||||
|
: ( btn = DOM`<button>${k}</button>`.children[0], btn.on`click`( items[k] ), btn )
|
||||||
|
)
|
||||||
|
// ...items.map( item=> DOM`<button>${item.label}</button>`)
|
||||||
|
)
|
||||||
|
return html
|
||||||
|
}
|
||||||
|
const ContextMenu = ( {x,y}, items )=> {
|
||||||
|
let menu = Menu( items )
|
||||||
|
menu.style.transform = `translate( ${x}px, ${y}px )`
|
||||||
|
menu.style.position = 'absolute'
|
||||||
|
return menu
|
||||||
|
}
|
||||||
|
|
||||||
|
export {
|
||||||
|
Overlay
|
||||||
|
, Menu
|
||||||
|
, ContextMenu
|
||||||
|
}
|
|
@ -0,0 +1,393 @@
|
||||||
|
import { DOM, SVG, CSS } from './DOM.js'
|
||||||
|
import { makeDraggable, Draggable } from './Draggable.js'
|
||||||
|
|
||||||
|
class Viewport extends HTMLElement {
|
||||||
|
|
||||||
|
static stylesForSVG = `
|
||||||
|
@namespace html "http://www.w3.org/1999/xhtml";
|
||||||
|
|
||||||
|
/*TitilliumWeb-ExtraLight.ttf*/
|
||||||
|
:root {
|
||||||
|
--canvas-size: 1000000px;
|
||||||
|
background-color: hsl(259 14% 12% / 1);
|
||||||
|
--grid-color-hsl: 256deg 5% 46%;
|
||||||
|
--grid-color: hsl(var(--grid-color-hsl));
|
||||||
|
|
||||||
|
display: inline-flex;
|
||||||
|
}
|
||||||
|
:root([debug]) g.Box foreignObject { border: 1px dashed red; }
|
||||||
|
|
||||||
|
[coordinate] { display: none }
|
||||||
|
:root([grid]) [coordinate] { display: inline }
|
||||||
|
|
||||||
|
[coordinate] [origin],
|
||||||
|
[coordinate] [x-axis],
|
||||||
|
[coordinate] [y-axis] {
|
||||||
|
fill: var(--grid-color);
|
||||||
|
stroke: var(--grid-color);
|
||||||
|
stroke-width: 3px;
|
||||||
|
}
|
||||||
|
[coordinate] [x-axis] {
|
||||||
|
x1: calc(var(--canvas-size) * -1);
|
||||||
|
x2: var(--canvas-size);
|
||||||
|
}
|
||||||
|
[coordinate] [y-axis] {
|
||||||
|
y1: calc(var(--canvas-size) * -1);
|
||||||
|
y2: var(--canvas-size);
|
||||||
|
}
|
||||||
|
[coordinate] [grid] {
|
||||||
|
x: calc(var(--canvas-size) * -1);
|
||||||
|
y: calc(var(--canvas-size) * -1);
|
||||||
|
width: calc(var(--canvas-size) * 2);
|
||||||
|
height: calc(var(--canvas-size) * 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
[mouse] { pointer-events:none }
|
||||||
|
|
||||||
|
[draggable] { cursor: move }
|
||||||
|
|
||||||
|
g.Box { --color: rgb(55, 50, 66); }
|
||||||
|
g.Box foreignObject { font-family: 'Titillium Web', sans-serif; filter: drop-shadow(black 0px 2px 3px) drop-shadow(rgba(0, 0, 0, 0.5) 0px 2px 10px); }
|
||||||
|
g.Box foreignObject * { pointer-events: none; user-select: none }
|
||||||
|
g.Box foreignObject > html|box {
|
||||||
|
display: flex;
|
||||||
|
background: var(--color);
|
||||||
|
border-radius: 5px;
|
||||||
|
padding: 2em .5em .3em;
|
||||||
|
color: white;
|
||||||
|
flex-direction: column;
|
||||||
|
place-items: stretch;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
g.Box foreignObject > html|box > html|header {
|
||||||
|
position: fixed;
|
||||||
|
width: 100%;
|
||||||
|
background: rgba(255,255,255,0.3);
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
border-radius: 5px 5px 0 0;
|
||||||
|
padding: .3em;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
g.Box foreignObject > html|box > html|socket[output] {
|
||||||
|
align-self: flex-end;
|
||||||
|
}
|
||||||
|
g.Box .anchor {
|
||||||
|
r: 5px;
|
||||||
|
fill: rgb(22, 199, 118);
|
||||||
|
stroke: var(--color);
|
||||||
|
stroke-width: 2;
|
||||||
|
}
|
||||||
|
g.Box .anchor.string {
|
||||||
|
fill: chocolate;
|
||||||
|
}
|
||||||
|
`
|
||||||
|
static styles = CSS`
|
||||||
|
|
||||||
|
menu {
|
||||||
|
padding: .2em;
|
||||||
|
margin: 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
color: wheat;
|
||||||
|
background: #FFF1;
|
||||||
|
font-family: sans-serif;
|
||||||
|
font-size: 10px;
|
||||||
|
/* box-shadow: 0 2px 4px black; */
|
||||||
|
}
|
||||||
|
|
||||||
|
menu button {
|
||||||
|
white-space: nowrap;
|
||||||
|
background: transparent;
|
||||||
|
border: none;
|
||||||
|
text-align: start;
|
||||||
|
font-family: sans-serif;
|
||||||
|
color: wheat;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
menu[label]::before {
|
||||||
|
content: attr(label);
|
||||||
|
}
|
||||||
|
|
||||||
|
button:hover {
|
||||||
|
background: #FFF2;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreignObject > menu {
|
||||||
|
box-shadow: 0 2px 4px black;
|
||||||
|
padding: .3em;
|
||||||
|
background: #131722e0;
|
||||||
|
backdrop-filter: blur(3px);
|
||||||
|
}
|
||||||
|
|
||||||
|
`
|
||||||
|
|
||||||
|
constructor()
|
||||||
|
{
|
||||||
|
super()
|
||||||
|
this.attachShadow({ mode: 'open' })
|
||||||
|
this.shadowRoot.insertBefore()
|
||||||
|
this.shadowRoot.innerHTML =
|
||||||
|
`<svg width="100%" height="100%">
|
||||||
|
<defs>
|
||||||
|
<pattern id="smallGrid" width="10" height="10" patternUnits="userSpaceOnUse">
|
||||||
|
<path d="M 10 0 L 0 0 0 10" fill="none" stroke="var(--grid-color)" stroke-width="0.5"></path>
|
||||||
|
</pattern>
|
||||||
|
<pattern id="grid100" width="100" height="100" patternUnits="userSpaceOnUse">
|
||||||
|
<rect width="100" height="100" fill="url(#smallGrid)"></rect>
|
||||||
|
<path d="M 100 0 L 0 0 0 100" fill="none" stroke="var(--grid-color)" stroke-width="1"></path>
|
||||||
|
</pattern>
|
||||||
|
<pattern id="grid" width="1000" height="1000" patternUnits="userSpaceOnUse">
|
||||||
|
<rect width="1000" height="1000" fill="url(#grid100)"></rect>
|
||||||
|
<path d="M 1000 0 L 0 0 0 1000" fill="none" stroke="var(--grid-color)" stroke-width="2.5"></path>
|
||||||
|
</pattern>
|
||||||
|
</defs>
|
||||||
|
<g canvas coordinate>
|
||||||
|
<rect grid fill="url(#grid)"></rect>
|
||||||
|
<circle origin r=3 />
|
||||||
|
<line y-axis />
|
||||||
|
<line x-axis />
|
||||||
|
</g>
|
||||||
|
<g mouse>
|
||||||
|
<circle fill="red" r=5 />
|
||||||
|
<line y-axis stroke="red" y1="-1000000" y2="1000000" />
|
||||||
|
<line x-axis stroke="red" x1="-1000000" x2="1000000" />
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
<slot></slot>`
|
||||||
|
// <rect view stroke="red" fill="aliceblue" width=1366 height=663 />
|
||||||
|
this.shadowRoot.adoptedStyleSheets = [Viewport.styles]
|
||||||
|
this.$svg = this.shadowRoot.querySelector('svg')
|
||||||
|
this.$svg.append(
|
||||||
|
DOM`<style>${Viewport.stylesForSVG}</style>`
|
||||||
|
)
|
||||||
|
|
||||||
|
//.ownerDocument.adoptedStyleSheets = [Viewport.stylesForSVG]
|
||||||
|
this.$canvas = this.$svg.querySelector('g[canvas]')
|
||||||
|
this.$mouse = this.$svg.querySelector('g[mouse]')
|
||||||
|
// this.$view = this.$svg.querySelector('rect[view]')
|
||||||
|
// makeDraggable( this.$view )
|
||||||
|
// this.$view.on`drag`( e=> this.$svg.setAttribute('viewBox',`${e.target.transform.baseVal[0].matrix.e} ${e.target.transform.baseVal[0].matrix.f} ${e.target.width.baseVal.value} ${e.target.width.baseVal.value}`) )
|
||||||
|
this.$slot = this.shadowRoot.querySelector('slot')
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
connectedCallback()
|
||||||
|
{
|
||||||
|
if( !this.$svg.hasAttribute('viewBox') )
|
||||||
|
this.$svg.setAttribute('viewBox', `0 0 ${this.offsetWidth} ${this.offsetHeight}`)
|
||||||
|
this.addListeners()
|
||||||
|
}
|
||||||
|
|
||||||
|
addListeners()
|
||||||
|
{
|
||||||
|
// Children svg append
|
||||||
|
this.$slot.on`slotchange`( e=>
|
||||||
|
this.$slot.assignedNodes()
|
||||||
|
.filter( node=> node.$svg )
|
||||||
|
.map( node=> this.$canvas.append(node.$svg) )
|
||||||
|
|
||||||
|
)
|
||||||
|
|
||||||
|
// Mouse
|
||||||
|
this.onmousemove = e=> /*console.log('mouse',e.movementX,e.movementY)||*/ requestAnimationFrame( ()=>this.$mouse.moveTo(getMousePosition(e) ) )
|
||||||
|
|
||||||
|
// Zoom
|
||||||
|
//@TODO Disable zoom while panning
|
||||||
|
//@TODO Get min(width,height)
|
||||||
|
this.onmousewheel = e=>
|
||||||
|
this.$svg.panAndZoom( false, false, (this.$svg.viewBox.baseVal.height/2) * ( 1-(e.deltaY/1000) ) )
|
||||||
|
// this.onmousewheel = e=> this.$svg.currentScale *= 1 - ( e.deltaY / 1000 )
|
||||||
|
// this.onmousewheel = e=> this.setView({
|
||||||
|
// width: 1 + ( e.deltaY / 1000 ),
|
||||||
|
// height: 1 + ( e.deltaY / 1000 )
|
||||||
|
// })
|
||||||
|
|
||||||
|
const getMousePosition = e=> {
|
||||||
|
var CTM = this.$svg.getScreenCTM()
|
||||||
|
if (e.touches) { e = e.touches[0] }
|
||||||
|
return {
|
||||||
|
x: (e.x - CTM.e) / CTM.a,
|
||||||
|
y: (e.y - CTM.f) / CTM.d
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert mouse position from screen space to coordinates of el
|
||||||
|
const inElementSpace = e=> {
|
||||||
|
var pt = this.$svg.createSVGPoint()
|
||||||
|
pt.x = e.x
|
||||||
|
pt.y = e.y
|
||||||
|
return pt.matrixTransform( this.$svg.getScreenCTM().inverse() )
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pan
|
||||||
|
//@TODO x Change to moving $canvas
|
||||||
|
//@TODO x Get initial CTM when start pan and don't update it while panning
|
||||||
|
this.pan = e=> {
|
||||||
|
|
||||||
|
this._mouseOffset = { x: e.clientX, y: e.clientY }//{ x: this.$mouse.x, y: this.$mouse.y }
|
||||||
|
// this.$svg.dataset.dragX = e.x
|
||||||
|
// this.$svg.dataset.dragY = e.y
|
||||||
|
// this._panStartCTM = this.$svg.getScreenCTM()
|
||||||
|
// let rect = this.$svg.viewBox.baseVal
|
||||||
|
// this.$svg.dataset.curTrX = rect.x + ( rect.width/2 )//this.$svg.currentTranslate.x
|
||||||
|
// this.$svg.dataset.curTrY = rect.y + ( rect.height/2 )//this.$svg.currentTranslate.y
|
||||||
|
// this._startPan = {
|
||||||
|
// x: rect.x + ( rect.width/2 )
|
||||||
|
// , y: rect.y + ( rect.height/2 )
|
||||||
|
// }
|
||||||
|
this._startPan = this.$svg.viewBox.baseVal.center
|
||||||
|
this.addEventListener('mousemove', this.panning )
|
||||||
|
|
||||||
|
}
|
||||||
|
this.panning = e=> {
|
||||||
|
// console.log(e)
|
||||||
|
// this.$svg.currentTranslate.x = this.$svg.dataset.curTrX - ( this.$svg.dataset.dragX - e.x )
|
||||||
|
// this.$svg.currentTranslate.y = this.$svg.dataset.curTrY - ( this.$svg.dataset.dragY - e.y )
|
||||||
|
// let mouse = getMousePosition( e )
|
||||||
|
let _mouseDiff = { x: e.clientX - this._mouseOffset.x, y: e.clientY - this._mouseOffset.y }
|
||||||
|
_mouseDiff.x /= this.$svg.clientHeight / this.$svg.viewBox.baseVal.height
|
||||||
|
_mouseDiff.y /= this.$svg.clientHeight / this.$svg.viewBox.baseVal.height
|
||||||
|
// let _mouseDiffCTM = getMousePosition( _mouseDiff )
|
||||||
|
// let _mouseDiffSpace = inElementSpace( _mouseDiff )
|
||||||
|
// let rect = this.$svg.viewBox.baseVal
|
||||||
|
// let center = this.$svg.viewBox.baseVal.center//{
|
||||||
|
// x: rect.x + ( rect.width/2 )
|
||||||
|
// , y: rect.y + ( rect.height/2 )
|
||||||
|
// }
|
||||||
|
this.$svg.panAndZoom(
|
||||||
|
// this._startPan.x - ( mouse.x - this._mouseOffset.x )
|
||||||
|
// , this._startPan.y - ( mouse.y - this._mouseOffset.y )
|
||||||
|
this._startPan.x - _mouseDiff.x
|
||||||
|
, this._startPan.y - _mouseDiff.y
|
||||||
|
// center.x - ( _mouseDiffCTM.x )
|
||||||
|
// , center.y - ( _mouseDiffCTM.y )
|
||||||
|
)
|
||||||
|
}
|
||||||
|
this.finishPan = e=> this.removeEventListener('mousemove', this.panning )
|
||||||
|
this.cancelPan = e=> console.warn('@TODO')
|
||||||
|
|
||||||
|
this.onmousedown = e=> {
|
||||||
|
if( e.which == 2 )// Wheel click
|
||||||
|
this.pan( e )
|
||||||
|
}
|
||||||
|
this.onmouseup = e=> this.finishPan( e )
|
||||||
|
|
||||||
|
// Draggable stuff
|
||||||
|
Draggable.startGlobalDrag = e=> this.$svg.$$`[dragged]`.filter(el=>el.draggable).map( el=> el.drag(e) )
|
||||||
|
Draggable.stopGlobalDrag = e=> this.$svg.$$`[dragged]`.map( el=> el.removeAttribute('dragged') )
|
||||||
|
|
||||||
|
// Draggable
|
||||||
|
window.on`mousemove`( Draggable.startGlobalDrag , true )
|
||||||
|
window.on`mouseup`( Draggable.stopGlobalDrag )
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Context menus globally managed
|
||||||
|
* <node>s that have a contextmenu handler at capture time put their menu
|
||||||
|
* under event.menu property and this final bubble handler on document
|
||||||
|
* creates the menu HTML in a SVG's <foreignObject>.
|
||||||
|
*/
|
||||||
|
this.$svg.oncontextmenu = e=> {
|
||||||
|
console.log(e)
|
||||||
|
// console.log(e.path)
|
||||||
|
console.log(e.menu)
|
||||||
|
// let items = e.menu//path.reverse().reduce( (m,n)=> ({...m,...(n.menu?n.menu:{})}),{})
|
||||||
|
// console.log(items, Object.keys(items).length )
|
||||||
|
if( e.menu )
|
||||||
|
{
|
||||||
|
e.preventDefault()
|
||||||
|
// let menu
|
||||||
|
// , layer = UI.Overlay( menu = UI.Menu( items ) )
|
||||||
|
// menu.setAttribute( 'x', e.clientX )
|
||||||
|
// menu.setAttribute( 'y', e.clientY )
|
||||||
|
// menu.style.transform = `translate( ${e.clientX}px, ${e.clientY}px )`
|
||||||
|
// menu.style.position = 'absolute'
|
||||||
|
// document.documentElement.append( layer )
|
||||||
|
this.$svg.append(
|
||||||
|
Overlay(
|
||||||
|
ContextMenu( e, e.menu )
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
newDocument()
|
||||||
|
{
|
||||||
|
let doc = DOM`<svg width="100%" height="100%">
|
||||||
|
<defs>
|
||||||
|
<pattern id="smallGrid" width="10" height="10" patternUnits="userSpaceOnUse">
|
||||||
|
<path d="M 10 0 L 0 0 0 10" fill="none" stroke="var(--grid-color)" stroke-width="0.5"></path>
|
||||||
|
</pattern>
|
||||||
|
<pattern id="grid100" width="100" height="100" patternUnits="userSpaceOnUse">
|
||||||
|
<rect width="100" height="100" fill="url(#smallGrid)"></rect>
|
||||||
|
<path d="M 100 0 L 0 0 0 100" fill="none" stroke="var(--grid-color)" stroke-width="1"></path>
|
||||||
|
</pattern>
|
||||||
|
<pattern id="grid" width="1000" height="1000" patternUnits="userSpaceOnUse">
|
||||||
|
<rect width="1000" height="1000" fill="url(#grid100)"></rect>
|
||||||
|
<path d="M 1000 0 L 0 0 0 1000" fill="none" stroke="var(--grid-color)" stroke-width="2.5"></path>
|
||||||
|
</pattern>
|
||||||
|
</defs>
|
||||||
|
<g canvas coordinate>
|
||||||
|
<rect grid fill="url(#grid)"></rect>
|
||||||
|
<circle origin r=3 />
|
||||||
|
<line y-axis />
|
||||||
|
<line x-axis />
|
||||||
|
</g>
|
||||||
|
<g mouse>
|
||||||
|
<circle fill="red" r=5 />
|
||||||
|
<line y-axis stroke="red" y1="-1000000" y2="1000000" />
|
||||||
|
<line x-axis stroke="red" x1="-1000000" x2="1000000" />
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
`
|
||||||
|
return doc
|
||||||
|
}
|
||||||
|
|
||||||
|
loadDocument()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
openDocument( doc )
|
||||||
|
{
|
||||||
|
this.sha
|
||||||
|
}
|
||||||
|
|
||||||
|
async saveToFile()
|
||||||
|
{
|
||||||
|
let file = await showSaveFilePicker()
|
||||||
|
, W = await file.createWritable()
|
||||||
|
await W.write( this.toString() )
|
||||||
|
await W.close()
|
||||||
|
}
|
||||||
|
|
||||||
|
toString()
|
||||||
|
{
|
||||||
|
let styles
|
||||||
|
, dolly = this.$svg.cloneNode(true)
|
||||||
|
// dolly.append(
|
||||||
|
// styles = DOM`<style>${
|
||||||
|
// [...Viewport.stylesForSVG.rules]
|
||||||
|
// .map( r=> r.cssText )
|
||||||
|
// .join('\n')
|
||||||
|
// }</style>`
|
||||||
|
// )
|
||||||
|
|
||||||
|
dolly.removeAttribute('width')
|
||||||
|
dolly.removeAttribute('height')
|
||||||
|
|
||||||
|
dolly.setAttribute('xmlns', 'http://www.w3.org/2000/svg' )
|
||||||
|
dolly.setAttribute('xmlns:svg', 'http://www.w3.org/2000/svg' )
|
||||||
|
dolly.setAttribute('xmlns:html', 'http://www.w3.org/1999/xhtml' )
|
||||||
|
|
||||||
|
return dolly.outerHTML
|
||||||
|
}
|
||||||
|
}
|
||||||
|
customElements.define( 'nb-viewport', Viewport )
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
export { Viewport }
|
Binary file not shown.
|
@ -0,0 +1,38 @@
|
||||||
|
<style>
|
||||||
|
@import url('https://fonts.googleapis.com/css2?family=Titillium+Web:wght@200&display=swap');
|
||||||
|
html, body, svg, vp-viewport {
|
||||||
|
margin: 0; padding: 0; width: 100%; height: 100%;
|
||||||
|
--grid-color: grey;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<script src="./EventojLancilo.js"></script>
|
||||||
|
<script src="./DOM.js"></script>
|
||||||
|
<script src="./Draggable.js"></script>
|
||||||
|
<script src="./Overlay.js"></script>
|
||||||
|
<script src="./Group.js"></script>
|
||||||
|
<script src="./Box.js"></script>
|
||||||
|
|
||||||
|
<script src="./Viewport.js"></script>
|
||||||
|
|
||||||
|
|
||||||
|
<vp-viewport grid></vp-viewport>
|
||||||
|
|
||||||
|
<!-- <vp-box title="Name tag">
|
||||||
|
<vp-socket output name="output1"></vp-socket>
|
||||||
|
<vp-socket input name="input1"></vp-socket>
|
||||||
|
<vp-socket input name="input2"></vp-socket>
|
||||||
|
</vp-box>
|
||||||
|
</vp-viewport> -->
|
||||||
|
|
||||||
|
<script>
|
||||||
|
const view = $`vp-viewport`.$svg
|
||||||
|
view.append(
|
||||||
|
Box('Name',['aze'],['qsd'])
|
||||||
|
, Box('Other',['float', 'list', 'list', 'list', 'list', 'list', 'list', 'list'],['foo'])
|
||||||
|
, Box('Awesome',['position:Vector3'],['scale:Vector3'])
|
||||||
|
, Box('SVG output',['svg:SVGSvgElement'],[])
|
||||||
|
, Box('String input',[],['string:String'])
|
||||||
|
, Box('Selector',['css:String','root:Node'],['nodes:NodeList'])
|
||||||
|
)
|
||||||
|
</script>
|
|
@ -0,0 +1,75 @@
|
||||||
|
<style>
|
||||||
|
@import url('./fonts/TitilliumWeb/TitilliumWeb.css');
|
||||||
|
html, body, svg, vp-viewport {
|
||||||
|
font-family: 'Titillium Web', sans-serif;
|
||||||
|
margin: 0; padding: 0; width: 100%; height: 100%;
|
||||||
|
--grid-color: grey;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<!--
|
||||||
|
<script src="./EventojLancilo.js"></script>
|
||||||
|
<script src="./DOM.js"></script>
|
||||||
|
<script src="./Draggable.js"></script>
|
||||||
|
<script src="./Overlay.js"></script>
|
||||||
|
<script src="./Group.js"></script>
|
||||||
|
<script src="./Box.js"></script>
|
||||||
|
|
||||||
|
<script src="./Viewport.js"></script>
|
||||||
|
-->
|
||||||
|
<script type="module">
|
||||||
|
import {
|
||||||
|
EventojLancilo
|
||||||
|
, logNpass
|
||||||
|
, DOM
|
||||||
|
, SVG
|
||||||
|
, CSS
|
||||||
|
, $
|
||||||
|
, $$
|
||||||
|
, Draggable
|
||||||
|
, Overlay
|
||||||
|
, Group
|
||||||
|
, Box
|
||||||
|
, Viewport
|
||||||
|
} from "./index.js"
|
||||||
|
|
||||||
|
window.nouilles = {
|
||||||
|
EventojLancilo
|
||||||
|
, logNpass
|
||||||
|
, DOM
|
||||||
|
, SVG
|
||||||
|
, CSS
|
||||||
|
, $
|
||||||
|
, $$
|
||||||
|
, Draggable
|
||||||
|
, Overlay
|
||||||
|
, Group
|
||||||
|
, Box
|
||||||
|
, Viewport
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<nb-viewport grid></nb-viewport>
|
||||||
|
|
||||||
|
<!-- <vp-box title="Name tag">
|
||||||
|
<vp-socket output name="output1"></vp-socket>
|
||||||
|
<vp-socket input name="input1"></vp-socket>
|
||||||
|
<vp-socket input name="input2"></vp-socket>
|
||||||
|
</vp-box>
|
||||||
|
</vp-viewport> -->
|
||||||
|
|
||||||
|
<script type="module">
|
||||||
|
import { $ } from './DOM.js'
|
||||||
|
import { Box } from './Box.js'
|
||||||
|
|
||||||
|
const view = $`vp-viewport`
|
||||||
|
view.$svg.append(
|
||||||
|
Box('Name',['aze'],['qsd'])
|
||||||
|
, Box('Other',['float', 'list', 'list', 'list', 'list', 'list', 'list', 'list'],['foo'])
|
||||||
|
, Box('Awesome',['position:Vector3'],['scale:Vector3'])
|
||||||
|
, Box('SVG output',['svg:SVGSvgElement'],[])
|
||||||
|
, Box('String input',[],['string:String'])
|
||||||
|
, Box('Selector',['css:String','root:Node'],['nodes:NodeList'])
|
||||||
|
)
|
||||||
|
|
||||||
|
window.view = view
|
||||||
|
</script>
|
|
@ -0,0 +1,16 @@
|
||||||
|
|
||||||
|
export { EventojLancilo } from './EventojLancilo.js'
|
||||||
|
export {
|
||||||
|
logNpass
|
||||||
|
, DOM
|
||||||
|
, SVG
|
||||||
|
, CSS
|
||||||
|
, $
|
||||||
|
, $$
|
||||||
|
} from './DOM.js'
|
||||||
|
export { Draggable, makeDraggable } from './Draggable.js'
|
||||||
|
export { Overlay, Menu } from './Overlay.js'
|
||||||
|
export { Group } from './Group.js'
|
||||||
|
export { Box } from './Box.js'
|
||||||
|
|
||||||
|
export { Viewport } from './Viewport.js'
|
Loading…
Reference in New Issue