diff --git a/docs/straws/index.html b/docs/straws/index.html index a4c8569..55d51ec 100644 --- a/docs/straws/index.html +++ b/docs/straws/index.html @@ -19,6 +19,10 @@ back

Straws UI Prototype

+

Add component

+
+
+

HPSF from the graph

diff --git a/docs/straws/sketch.js b/docs/straws/sketch.js index 58ef3a9..40ce7ab 100644 --- a/docs/straws/sketch.js +++ b/docs/straws/sketch.js @@ -3,6 +3,7 @@ let bg = 0; let zoomLevel = 1; let components = []; let connections = []; +let componentKinds = new Map(); let partialConnection = null; let dragComponent = null; let dragPort = null; @@ -17,7 +18,7 @@ function verticalCurve(p1, p2) { c1.y = (p1.y + p2.y) / 2; c2.y = (p1.y + p2.y) / 2; noFill(); - stroke(0); + stroke(240); strokeWeight(2); bezier(p1.x, p1.y, c1.x, c1.y, c2.x, c2.y, p2.x, p2.y); // line(p1.x, p1.y, p2.x, p2.y); @@ -44,9 +45,10 @@ class Shape { } class Port { - constructor(parent, type, label, col) { - this.parent = parent; + constructor(parent, type, direction, label, col) { this.type = type; + this.parent = parent; + this.direction = direction; this.label = label; this.col = col; this.x = 0; @@ -60,7 +62,7 @@ class Port { draw() { let align = BOTTOM; let offset = this.portRadius; - if (this.type === 'input') { + if (this.direction === 'input') { align = TOP; offset = -this.portRadius; } @@ -78,7 +80,7 @@ class Port { // ask if x, y is inside the port hit(x, y) { let offset = this.portRadius; - if (this.type === 'input') { + if (this.direction === 'input') { offset = -this.portRadius; } return dist(x, y, this.x, this.y + offset) <= this.portRadius; @@ -86,9 +88,10 @@ class Port { yaml(indent) { let spaces = ' '.repeat(indent) + '- '; - let yaml = spaces + "Name: " + this.label + '\n'; + let yaml = spaces + 'Name: ' + this.label + '\n'; spaces = ' '.repeat(indent+2); - yaml += spaces + "Direction: " + this.type + '\n'; + yaml += spaces + 'Direction: ' + this.direction + '\n'; + yaml += spaces + 'Type: ' + this.type + '\n'; return yaml; } } @@ -105,13 +108,13 @@ class ComponentShape extends Shape { this.textFill = textCol; } - addInput(parent, label, col) { - this.inputs.set(label, new Port(parent, "input", label, col)); + addInput(parent, type, label, col) { + this.inputs.set(label, new Port(parent, type, 'input', label, col)); this._layoutPorts(); } - addOutput(parent, label, col) { - this.outputs.set(label, new Port(parent, "output", label, col)); + addOutput(parent, type, label, col) { + this.outputs.set(label, new Port(parent, type, 'output', label, col)); this._layoutPorts(); } @@ -183,31 +186,81 @@ class ComponentShape extends Shape { } } +class ComponentKind { + constructor(kind, ports, w, h, col) { + this.kind = kind; + this.ports = ports; + this.w = w; + this.h = h; + this.col = col; + this.labelNumber = 0; + this.properties = new Map(); + componentKinds.set(kind, this); + } + + addProperty(name, value) { + this.properties.set(name, value); + } + + addPortsTo(component) { + for (let port of this.ports) { + if (port.direction === 'input') { + component.addInput(port.type, port.label, port.col); + } else { + component.addOutput(port.type, port.label, port.col); + } + } + } +} + +function addComponentKind(kind, ports, w=150, h=50, col=200) { + let k = new ComponentKind(kind, ports, w, h, col); + button = createButton(kind); + buttondiv = select('#button-holder'); + button.parent(buttondiv); + let createComponent = () => { + let c = new Component(kind, 250+10*(k.labelNumber+1), 200+10*(k.labelNumber+1)); + components.push(c); + changed = true; + } + button.mousePressed(createComponent); + return k; +} + class Component { - constructor(label, x, y) { + constructor(kind, x, y, label='') { + let ckind = componentKinds.get(kind); + this.kind = kind; this.label = label; + if (label === '') { + ckind.labelNumber += 1; + this.label = kind + '_' + ckind.labelNumber; + } this.x = x; this.y = y; - this.shape = new ComponentShape(x, y, 150, 50, label); + this.shape = new ComponentShape(x, y, ckind.w, ckind.h, this.label); + this.properties = ckind.properties; this.dragging = false; this.hovering = false; this.dragX = 0; this.dragY = 0; + ckind.addPortsTo(this); } - addInput(label, col) { - this.shape.addInput(this, label, col); + addInput(type, label, col) { + this.shape.addInput(this, type, label, col); } - addOutput(label, col) { - this.shape.addOutput(this, label, col); + addOutput(type, label, col) { + this.shape.addOutput(this, type, label, col); } draw() { + let ckind = componentKinds.get(this.kind); if (this.hovering) { - this.shape.bodyfill = color(255, 255, 0); + this.shape.bodyfill = lerpColor(ckind.col, color(255), 0.5); } else { - this.shape.bodyfill = color(255); + this.shape.bodyfill = ckind.col; } this.shape.draw(); } @@ -275,17 +328,46 @@ class Component { } } + select() { + let container = select('#selected-component'); + let html = '

' + this.label + '

'; + html += ''; + // set up the table + for (let [name, value] of this.properties) { + let tfield = ''; + html += ''; + } + html += '
' + name + '' + tfield + '
'; + // add the table to the container + container.html(html); + // set up the event handlers + for (let [name, value] of this.properties) { + let input = select('#prop-' + name); + input.changed(() => { + this.properties.set(name, input.value()); + changed = true; + }); + } + } + yaml(indent) { let spaces = ' '.repeat(indent) + '- '; - let yaml = spaces + "Name: " + this.label + '\n'; + let yaml = spaces + 'Name: ' + this.label + '\n'; spaces = ' '.repeat(indent+2); - yaml += spaces + "Ports:\n"; + yaml += spaces + 'Kind: ' + this.kind + '\n'; + yaml += spaces + 'Ports:\n'; for (let input of this.shape.inputs.values()) { yaml += input.yaml(indent+2); } for (let output of this.shape.outputs.values()) { yaml += output.yaml(indent+2); } + yaml += spaces + 'Properties:\n'; + let sp = '- '; + for (let [name, value] of this.properties) { + yaml += spaces + sp + name + ': ' + value + '\n'; + sp = ' '; + } return yaml; } } @@ -294,7 +376,8 @@ class Connection { constructor(frComp, frPort, toComp, toPort) { // always go from the output to the input let p = frComp.port(frPort); - if (frComp.port(frPort).type == 'input') { + print(frComp); + if (frComp.port(frPort).direction == 'input') { // print('reversing connection'); this.frComp = toComp; this.frPort = toPort; @@ -332,22 +415,42 @@ class Connection { function restart() { - bg = 128; - zoomLevel = 2; - inputCol = color(128, 128, 255); - outputCol = color(255, 128, 128); - - c1 = new Component('TraceGRPC', 100, 100); - c1.addOutput('TraceOut', outputCol); - c2 = new Component('DeterministicSampler', 100, 300); - c2.addInput('Input', inputCol); - c2.addOutput('Kept', outputCol); - c2.addOutput('Dropped', outputCol); - c3 = new Component('HoneycombExporter', 100, 500); - c3.addInput('Traces', inputCol); + bg = 80; + zoomLevel = 2; + inputCol = color(128, 255, 128); + outputCol = color(255, 192, 192); + buttondiv = select('#button-holder'); + buttondiv.html(''); + + let k = addComponentKind('TraceGRPC', [ + {direction: 'output', type: 'OtelTraces', label: 'TraceOut', col: outputCol} + ], 100, 70, color(128, 255, 128)); + k.addProperty('Port', 4317); + k = addComponentKind('DeterministicSampler', [ + {direction: 'input', type: 'OtelTraces', label: 'Input', col: inputCol}, + {direction: 'output', type: 'OtelTraces', label: 'Kept', col: outputCol}, + {direction: 'output', type: 'OtelTraces', label: 'Dropped', col: outputCol} + ], 150, 100, color(192, 192, 255)); + k.addProperty('SampleRate', 10); + k = addComponentKind('HoneycombExporter', [ + {direction: 'input', type: 'OtelTraces', label: 'Traces', col: inputCol} + ], 180, 80, color(255, 255, 128)); + k.addProperty('Dataset', 'mydataset'); + k.addProperty('APIKey', '$HONEYCOMB_APIKEY'); + k = addComponentKind('EMAThroughputSampler', [ + {direction: 'input', type: 'OtelTraces', label: 'Input', col: inputCol}, + {direction: 'output', type: 'OtelTraces', label: 'Kept', col: outputCol}, + {direction: 'output', type: 'OtelTraces', label: 'Dropped', col: outputCol} + ], 150, 100, color(192, 192, 255)); + k.addProperty('TPS', 100); + k.addProperty('KeyFields', 'key1, key2, key3'); + + c1 = new Component('TraceGRPC', 300, 100); + c2 = new Component('DeterministicSampler', 300, 300); + c3 = new Component('HoneycombExporter', 300, 500); components = [c1, c2, c3]; let connection = new Connection(c1, 'TraceOut', c2, 'Input'); - connections.push(connection); + connections = [connection]; generateYaml(); } @@ -397,6 +500,15 @@ function mousePressed() { } } +function mouseClicked() { + // print('clicked'); + for (let component of components) { + if (component.bodyHit(mouseX, mouseY)) { + component.select(); + } + } +} + function mouseReleased() { if (dragComponent) { dragComponent.endDrag(); @@ -404,10 +516,12 @@ function mouseReleased() { } partialConnection = null; if (dragPort) { + // print('dragPort', dragPort); dragPort.hovering = false; for (let component of components) { let port = component.portHit(mouseX, mouseY); - if (port && port !== dragPort && port.parent !== dragPort.parent && port.type !== dragPort.type) { + // print('port', port); + if (port && port !== dragPort && port.parent !== dragPort.parent && port.direction !== dragPort.direction) { // print('connecting', dragPort, 'to', port); let connection = new Connection(dragPort.parent, dragPort.label, component, port.label); connections.push(connection); diff --git a/docs/straws/style.css b/docs/straws/style.css index 275fb84..969e679 100644 --- a/docs/straws/style.css +++ b/docs/straws/style.css @@ -31,4 +31,30 @@ pre { padding: 20px; border-radius: 5px; overflow-x: auto; +} + +table { + margin-left: 2rem; + border-collapse: collapse; + width: 30rem; + margin-bottom: 20px; + background-color: beige; +} + +.propname { + color: #000080; + width: 35%; +} + +.propvalue { + text-align: left; + color: #1e90ff; +} + +.textfield { + width: 90%; + padding: 5px; + margin: 5px 0; + border: 1px solid #ccc; + border-radius: 4px; } \ No newline at end of file diff --git a/p5/straws/index.html b/p5/straws/index.html index a4c8569..55d51ec 100644 --- a/p5/straws/index.html +++ b/p5/straws/index.html @@ -19,6 +19,10 @@ back

Straws UI Prototype

+

Add component

+
+
+

HPSF from the graph

diff --git a/p5/straws/sketch.js b/p5/straws/sketch.js index 58b0ee3..40ce7ab 100644 --- a/p5/straws/sketch.js +++ b/p5/straws/sketch.js @@ -3,6 +3,7 @@ let bg = 0; let zoomLevel = 1; let components = []; let connections = []; +let componentKinds = new Map(); let partialConnection = null; let dragComponent = null; let dragPort = null; @@ -17,7 +18,7 @@ function verticalCurve(p1, p2) { c1.y = (p1.y + p2.y) / 2; c2.y = (p1.y + p2.y) / 2; noFill(); - stroke(0); + stroke(240); strokeWeight(2); bezier(p1.x, p1.y, c1.x, c1.y, c2.x, c2.y, p2.x, p2.y); // line(p1.x, p1.y, p2.x, p2.y); @@ -185,16 +186,65 @@ class ComponentShape extends Shape { } } +class ComponentKind { + constructor(kind, ports, w, h, col) { + this.kind = kind; + this.ports = ports; + this.w = w; + this.h = h; + this.col = col; + this.labelNumber = 0; + this.properties = new Map(); + componentKinds.set(kind, this); + } + + addProperty(name, value) { + this.properties.set(name, value); + } + + addPortsTo(component) { + for (let port of this.ports) { + if (port.direction === 'input') { + component.addInput(port.type, port.label, port.col); + } else { + component.addOutput(port.type, port.label, port.col); + } + } + } +} + +function addComponentKind(kind, ports, w=150, h=50, col=200) { + let k = new ComponentKind(kind, ports, w, h, col); + button = createButton(kind); + buttondiv = select('#button-holder'); + button.parent(buttondiv); + let createComponent = () => { + let c = new Component(kind, 250+10*(k.labelNumber+1), 200+10*(k.labelNumber+1)); + components.push(c); + changed = true; + } + button.mousePressed(createComponent); + return k; +} + class Component { - constructor(label, x, y) { + constructor(kind, x, y, label='') { + let ckind = componentKinds.get(kind); + this.kind = kind; this.label = label; + if (label === '') { + ckind.labelNumber += 1; + this.label = kind + '_' + ckind.labelNumber; + } this.x = x; this.y = y; - this.shape = new ComponentShape(x, y, 150, 50, label); + this.shape = new ComponentShape(x, y, ckind.w, ckind.h, this.label); + this.properties = ckind.properties; this.dragging = false; this.hovering = false; this.dragX = 0; this.dragY = 0; + ckind.addPortsTo(this); } addInput(type, label, col) { @@ -206,10 +256,11 @@ class Component { } draw() { + let ckind = componentKinds.get(this.kind); if (this.hovering) { - this.shape.bodyfill = color(255, 255, 0); + this.shape.bodyfill = lerpColor(ckind.col, color(255), 0.5); } else { - this.shape.bodyfill = color(255); + this.shape.bodyfill = ckind.col; } this.shape.draw(); } @@ -277,10 +328,33 @@ class Component { } } + select() { + let container = select('#selected-component'); + let html = '

' + this.label + '

'; + html += ''; + // set up the table + for (let [name, value] of this.properties) { + let tfield = ''; + html += ''; + } + html += '
' + name + '' + tfield + '
'; + // add the table to the container + container.html(html); + // set up the event handlers + for (let [name, value] of this.properties) { + let input = select('#prop-' + name); + input.changed(() => { + this.properties.set(name, input.value()); + changed = true; + }); + } + } + yaml(indent) { let spaces = ' '.repeat(indent) + '- '; let yaml = spaces + 'Name: ' + this.label + '\n'; spaces = ' '.repeat(indent+2); + yaml += spaces + 'Kind: ' + this.kind + '\n'; yaml += spaces + 'Ports:\n'; for (let input of this.shape.inputs.values()) { yaml += input.yaml(indent+2); @@ -288,6 +362,12 @@ class Component { for (let output of this.shape.outputs.values()) { yaml += output.yaml(indent+2); } + yaml += spaces + 'Properties:\n'; + let sp = '- '; + for (let [name, value] of this.properties) { + yaml += spaces + sp + name + ': ' + value + '\n'; + sp = ' '; + } return yaml; } } @@ -335,19 +415,39 @@ class Connection { function restart() { - bg = 128; + bg = 80; zoomLevel = 2; - inputCol = color(128, 128, 255); - outputCol = color(255, 128, 128); - - c1 = new Component('TraceGRPC', 100, 100); - c1.addOutput('OtelTraces', 'TraceOut', outputCol); - c2 = new Component('DeterministicSampler', 100, 300); - c2.addInput('OtelTraces', 'Input', inputCol); - c2.addOutput('OtelTraces', 'Kept', outputCol); - c2.addOutput('OtelTraces', 'Dropped', outputCol); - c3 = new Component('HoneycombExporter', 100, 500); - c3.addInput('OtelTraces', 'Traces', inputCol); + inputCol = color(128, 255, 128); + outputCol = color(255, 192, 192); + buttondiv = select('#button-holder'); + buttondiv.html(''); + + let k = addComponentKind('TraceGRPC', [ + {direction: 'output', type: 'OtelTraces', label: 'TraceOut', col: outputCol} + ], 100, 70, color(128, 255, 128)); + k.addProperty('Port', 4317); + k = addComponentKind('DeterministicSampler', [ + {direction: 'input', type: 'OtelTraces', label: 'Input', col: inputCol}, + {direction: 'output', type: 'OtelTraces', label: 'Kept', col: outputCol}, + {direction: 'output', type: 'OtelTraces', label: 'Dropped', col: outputCol} + ], 150, 100, color(192, 192, 255)); + k.addProperty('SampleRate', 10); + k = addComponentKind('HoneycombExporter', [ + {direction: 'input', type: 'OtelTraces', label: 'Traces', col: inputCol} + ], 180, 80, color(255, 255, 128)); + k.addProperty('Dataset', 'mydataset'); + k.addProperty('APIKey', '$HONEYCOMB_APIKEY'); + k = addComponentKind('EMAThroughputSampler', [ + {direction: 'input', type: 'OtelTraces', label: 'Input', col: inputCol}, + {direction: 'output', type: 'OtelTraces', label: 'Kept', col: outputCol}, + {direction: 'output', type: 'OtelTraces', label: 'Dropped', col: outputCol} + ], 150, 100, color(192, 192, 255)); + k.addProperty('TPS', 100); + k.addProperty('KeyFields', 'key1, key2, key3'); + + c1 = new Component('TraceGRPC', 300, 100); + c2 = new Component('DeterministicSampler', 300, 300); + c3 = new Component('HoneycombExporter', 300, 500); components = [c1, c2, c3]; let connection = new Connection(c1, 'TraceOut', c2, 'Input'); connections = [connection]; @@ -400,6 +500,15 @@ function mousePressed() { } } +function mouseClicked() { + // print('clicked'); + for (let component of components) { + if (component.bodyHit(mouseX, mouseY)) { + component.select(); + } + } +} + function mouseReleased() { if (dragComponent) { dragComponent.endDrag(); @@ -407,10 +516,12 @@ function mouseReleased() { } partialConnection = null; if (dragPort) { + // print('dragPort', dragPort); dragPort.hovering = false; for (let component of components) { let port = component.portHit(mouseX, mouseY); - if (port && port !== dragPort && port.parent !== dragPort.parent && port.type !== dragPort.type) { + // print('port', port); + if (port && port !== dragPort && port.parent !== dragPort.parent && port.direction !== dragPort.direction) { // print('connecting', dragPort, 'to', port); let connection = new Connection(dragPort.parent, dragPort.label, component, port.label); connections.push(connection); diff --git a/p5/straws/style.css b/p5/straws/style.css index 275fb84..969e679 100644 --- a/p5/straws/style.css +++ b/p5/straws/style.css @@ -31,4 +31,30 @@ pre { padding: 20px; border-radius: 5px; overflow-x: auto; +} + +table { + margin-left: 2rem; + border-collapse: collapse; + width: 30rem; + margin-bottom: 20px; + background-color: beige; +} + +.propname { + color: #000080; + width: 35%; +} + +.propvalue { + text-align: left; + color: #1e90ff; +} + +.textfield { + width: 90%; + padding: 5px; + margin: 5px 0; + border: 1px solid #ccc; + border-radius: 4px; } \ No newline at end of file