Browse Source

added toolbar

pull/1/head
Stephanie Gredell 7 months ago
parent
commit
8d9282a646
  1. 149
      game.html

149
game.html

@ -199,6 +199,41 @@
stroke-width: 2; stroke-width: 2;
} }
#canvas-toolbar {
position: absolute;
top: 12px;
left: 12px;
z-index: 20;
display: flex;
gap: 8px;
background: var(--color-bg-component);
border: 1px solid var(--color-border);
border-radius: var(--radius-small);
padding: 6px;
box-shadow: 0 0 8px rgba(0, 0, 0, 0.4);
}
.toolbar-btn {
background: none;
border: 1px solid var(--color-border);
color: var(--color-text-primary);
padding: 6px 10px;
border-radius: var(--radius-small);
font-size: 14px;
cursor: pointer;
font-family: var(--font-family-mono);
}
.toolbar-btn:hover {
background-color: var(--color-bg-hover);
border-color: var(--color-border-accent);
}
.toolbar-btn.active {
background-color: var(--color-bg-accent);
color: var(--color-text-white);
border-color: var(--color-button);
}
/* === PANELS === */ /* === PANELS === */
#info-panel { #info-panel {
position: absolute; position: absolute;
@ -218,8 +253,6 @@
#node-props-panel { #node-props-panel {
position: absolute; position: absolute;
top: 20px;
right: 20px;
width: 220px; width: 220px;
background-color: var(--color-bg-sidebar); background-color: var(--color-bg-sidebar);
border: 1px solid var(--color-border); border: 1px solid var(--color-border);
@ -237,6 +270,17 @@
color: var(--color-text-primary); color: var(--color-text-primary);
} }
#node-props-save {
margin-top: 8px;
padding: 10px;
background-color: var(--color-button);
color: var(--color-text-white);
border: none;
border-radius: var(--radius-small);
cursor: pointer;
font-size: 14px;
}
.prop-group { .prop-group {
display: none; display: none;
margin-bottom: 12px; margin-bottom: 12px;
@ -261,8 +305,7 @@
} }
/* === BUTTONS === */ /* === BUTTONS === */
#run-button, #run-button {
#node-props-panel button {
margin-top: auto; margin-top: auto;
padding: 10px; padding: 10px;
background-color: var(--color-button); background-color: var(--color-button);
@ -368,7 +411,7 @@
.challenge-difficulty.easy { .challenge-difficulty.easy {
color: #3fb950; color: #3fb950;
} }
.challenge-difficulty.medium { .challenge-difficulty.medium {
color: #d29922; color: #d29922;
} }
@ -376,7 +419,7 @@
.challenge-difficulty.hard { .challenge-difficulty.hard {
color: #f85149 color: #f85149
} }
.challenge-item:hover { .challenge-item:hover {
background: #30363d; background: #30363d;
} }
@ -430,7 +473,7 @@
.requirement-item { .requirement-item {
position: relative; position: relative;
padding: 8px 0 0 25px; padding: 8px 0 8px 25px;
margin: 0; margin: 0;
border-bottom: 1px solid #30363d;; border-bottom: 1px solid #30363d;;
} }
@ -489,6 +532,15 @@
<li class="requirement-item">Something else</li> <li class="requirement-item">Something else</li>
</ul> </ul>
</div> </div>
<div class="requirements-section">
<h3>Non-Functional Requirements</h3>
<ul class="requirements-list">
<li class="requirement-item">Something</li>
<li class="requirement-item">Something else</li>
</ul>
</div>
</div> </div>
<!-- Design--> <!-- Design-->
@ -499,7 +551,7 @@
<span class="tooltip">simulates user traffic</span> <span class="tooltip">simulates user traffic</span>
</div> </div>
<div class="component-icon" draggable="true" data-type="loadbalancer"> <div class="component-icon" draggable="true" data-type="load balancer">
load balancer load balancer
<span class="tooltip">cost: $5/mo<br>distributes traffic evenly<br>latency: 5 ms</span> <span class="tooltip">cost: $5/mo<br>distributes traffic evenly<br>latency: 5 ms</span>
</div> </div>
@ -519,12 +571,12 @@
<span class="tooltip">cost: $20/mo<br>read capacity: 150 rps<br>base latency: 80 ms<br>supports replication</span> <span class="tooltip">cost: $20/mo<br>read capacity: 150 rps<br>base latency: 80 ms<br>supports replication</span>
</div> </div>
<div class="component-icon" draggable="true" data-type="cachestandard"> <div class="component-icon" draggable="true" data-type="cache (standard)">
cache (standard) cache (standard)
<span class="tooltip">cost: $10/mo<br>capacity: 100 rps<br>latency: 5 ms<br>80% hit rate with 1hr ttl</span> <span class="tooltip">cost: $10/mo<br>capacity: 100 rps<br>latency: 5 ms<br>80% hit rate with 1hr ttl</span>
</div> </div>
<div class="component-icon" draggable="true" data-type="cachelarge"> <div class="component-icon" draggable="true" data-type="cache (large)">
cache (large) cache (large)
<span class="tooltip">cost: $20/mo<br>capacity: 200 rps<br>latency: 5 ms<br>higher hit rate for large datasets</span> <span class="tooltip">cost: $20/mo<br>capacity: 200 rps<br>latency: 5 ms<br>higher hit rate for large datasets</span>
</div> </div>
@ -535,7 +587,7 @@
</div> </div>
<div class="component-icon" draggable="true" data-type="cdn"> <div class="component-icon" draggable="true" data-type="cdn">
cdn/edge cache CDN
<span class="tooltip">cost: $0.03/gb<br>improves global latency<br>caches static content</span> <span class="tooltip">cost: $0.03/gb<br>improves global latency<br>caches static content</span>
</div> </div>
@ -544,25 +596,26 @@
<span class="tooltip">cost: $10/mo<br>stateless container<br>use for modular logic</span> <span class="tooltip">cost: $10/mo<br>stateless container<br>use for modular logic</span>
</div> </div>
<div class="component-icon" draggable="true" data-type="datapipeline"> <div class="component-icon" draggable="true" data-type="data pipeline">
data pipeline data pipeline
<span class="tooltip">cost: $25/mo<br>stream or batch processing<br>used for analytics / etl</span> <span class="tooltip">cost: $25/mo<br>stream or batch processing<br>used for analytics / etl</span>
</div> </div>
<div class="component-icon" draggable="true" data-type="monitoring"> <div class="component-icon" draggable="true" data-type="monitoring/alerting">
monitoring/alerting monitoring/alerting
<span class="tooltip">cost: $5/mo<br>health checks + logs<br>alerts on failures</span> <span class="tooltip">cost: $5/mo<br>health checks + logs<br>alerts on failures</span>
</div> </div>
<div class="component-icon" draggable="true" data-type="thirdparty"> <div class="component-icon" draggable="true" data-type="third party service">
third-party service third-party service
<span class="tooltip">external apis<br>latency + cost vary<br>examples: payment, email, search</span> <span class="tooltip">external apis<br>latency + cost vary<br>examples: payment, email, search</span>
</div> </div>
<div id="arrow-tool">arrow tool</div>
</div> </div>
<div id="canvas-container"> <div id="canvas-container">
<div id="canvas-toolbar">
<button id="arrow-tool-btn" class="toolbar-btn">Arrow Tool</button>
</div>
<div id="info-panel"> <div id="info-panel">
<div id="constraints-panel"> <div id="constraints-panel">
<div class="panel-title">level constraints</div> <div class="panel-title">level constraints</div>
@ -656,7 +709,7 @@
x: x + app.componentSize.width / 2, x: x + app.componentSize.width / 2,
y: y + app.componentSize.height / 2 + 5, y: y + app.componentSize.height / 2 + 5,
'text-anchor': 'middle', 'text-anchor': 'middle',
'font-size': 14, 'font-size': 16,
fill: '#ccc' fill: '#ccc'
}); });
this.text.textContent = this.props.label; this.text.textContent = this.props.label;
@ -677,6 +730,11 @@
} else { } else {
app.clearSelection(); app.clearSelection();
this.select(); this.select();
}
});
this.group.addEventListener('dblclick', (e) => {
e.stopPropagation();
if (!app.arrowMode) {
app.showPropsPanel(this); app.showPropsPanel(this);
} }
}); });
@ -763,26 +821,26 @@
getConnectionPointToward(otherNode) { getConnectionPointToward(otherNode) {
const bbox = this.group.getBBox(); const bbox = this.group.getBBox();
const ctm = this.group.getCTM(); const ctm = this.group.getCTM();
const centerX = ctm.e + bbox.x + bbox.width / 2; const centerX = ctm.e + bbox.x + bbox.width / 2;
const centerY = ctm.f + bbox.y + bbox.height / 2; const centerY = ctm.f + bbox.y + bbox.height / 2;
const otherCenter = otherNode.getCenter(); const otherCenter = otherNode.getCenter();
let edgeX = centerX; let edgeX = centerX;
let edgeY = centerY; let edgeY = centerY;
const dx = otherCenter.x - centerX; const dx = otherCenter.x - centerX;
const dy = otherCenter.y - centerY; const dy = otherCenter.y - centerY;
if (Math.abs(dx) > Math.abs(dy)) { if (Math.abs(dx) > Math.abs(dy)) {
edgeX += dx > 0 ? bbox.width / 2 : -bbox.width / 2; edgeX += dx > 0 ? bbox.width / 2 : -bbox.width / 2;
} else { } else {
edgeY += dy > 0 ? bbox.height / 2 : -bbox.height / 2; edgeY += dy > 0 ? bbox.height / 2 : -bbox.height / 2;
} }
return { x: edgeX, y: edgeY }; return { x: edgeX, y: edgeY };
} }
} }
@ -812,7 +870,7 @@
updatePosition() { updatePosition() {
const s = this.start.getConnectionPointToward(this.end); const s = this.start.getConnectionPointToward(this.end);
const e = this.end.getConnectionPointToward(this.start); const e = this.end.getConnectionPointToward(this.start);
this.line.setAttribute('x1', s.x); this.line.setAttribute('x1', s.x);
this.line.setAttribute('y1', s.y); this.line.setAttribute('y1', s.y);
this.line.setAttribute('x2', e.x); this.line.setAttribute('x2', e.x);
@ -849,7 +907,7 @@ const e = this.end.getConnectionPointToward(this.start);
this.selectedConnection = null; this.selectedConnection = null;
this.sidebar = document.getElementById('sidebar'); this.sidebar = document.getElementById('sidebar');
this.arrowTool = document.getElementById('arrow-tool'); this.arrowToolBtn = document.getElementById('arrow-tool-btn');
this.canvasContainer = document.getElementById('canvas-container'); this.canvasContainer = document.getElementById('canvas-container');
this.canvas = document.getElementById('canvas'); this.canvas = document.getElementById('canvas');
this.runButton = document.getElementById('run-button'); this.runButton = document.getElementById('run-button');
@ -860,24 +918,34 @@ const e = this.end.getConnectionPointToward(this.start);
this.cacheGroup = document.getElementById('cache-group'); this.cacheGroup = document.getElementById('cache-group');
this.selectedNode = null; this.selectedNode = null;
this.placeholderText = createSVGElement('text', {
x: '50%',
y: '50%',
'text-anchor': 'middle',
'dominant-baseline': 'middle',
fill: '#444',
'font-size': 18,
'pointer-events': 'none'
});
this.placeholderText.textContent = 'Drag and drop elements to start building your system. Press backspace or delete to remove elements.';
this.canvas.appendChild(this.placeholderText);
this.initEventHandlers(); this.initEventHandlers();
} }
initEventHandlers() { initEventHandlers() {
this.arrowTool.addEventListener('click', () => { this.arrowToolBtn.addEventListener('click', () => {
this.arrowMode = !this.arrowMode; this.arrowMode = !this.arrowMode;
if (this.arrowMode) { if (this.arrowMode) {
this.arrowTool.classList.add('active'); this.arrowToolBtn.classList.add('active');
this.hidePropsPanel(); this.hidePropsPanel();
} else { } else {
this.arrowTool.classList.remove('active'); this.arrowToolBtn.classList.remove('active');
if (this.connectionStart) { if (this.connectionStart) {
this.connectionStart.group.classList.remove('selected'); this.connectionStart.group.classList.remove('selected');
this.connectionStart = null; this.connectionStart = null;
} }
} }
}); });
this.sidebar.addEventListener('dragstart', (e) => { this.sidebar.addEventListener('dragstart', (e) => {
if (e.target.classList.contains('component-icon')) { if (e.target.classList.contains('component-icon')) {
e.dataTransfer.setData('text/plain', e.target.getAttribute('data-type')); e.dataTransfer.setData('text/plain', e.target.getAttribute('data-type'));
@ -901,6 +969,10 @@ const e = this.end.getConnectionPointToward(this.start);
const x = svgP.x - this.componentSize.width / 2; const x = svgP.x - this.componentSize.width / 2;
const y = svgP.y - this.componentSize.height / 2; const y = svgP.y - this.componentSize.height / 2;
new Node(type, x, y, this); new Node(type, x, y, this);
if (this.placeholderText) {
this.placeholderText.remove();
this.placeholderText = null;
}
}); });
this.runButton.addEventListener('click', () => { this.runButton.addEventListener('click', () => {
@ -979,7 +1051,6 @@ const e = this.end.getConnectionPointToward(this.start);
} }
showPropsPanel(nodeObj) { showPropsPanel(nodeObj) {
// ... unchanged ...
this.activeNode = nodeObj; this.activeNode = nodeObj;
const panel = this.nodePropsPanel; const panel = this.nodePropsPanel;

Loading…
Cancel
Save