opencroc 1.4.0 → 1.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/index.js +113 -8
- package/dist/cli/index.js.map +1 -1
- package/dist/web/index.html +127 -33
- package/package.json +1 -1
package/dist/web/index.html
CHANGED
|
@@ -127,7 +127,7 @@
|
|
|
127
127
|
const S = {
|
|
128
128
|
project:null, graph:{nodes:[],edges:[]}, agents:[], ws:null,
|
|
129
129
|
pan:{x:0,y:0}, zoom:1, dragging:false, dragStart:{x:0,y:0},
|
|
130
|
-
nodePos:new Map(), hoveredNode:null, running:false
|
|
130
|
+
nodePos:new Map(), hoveredNode:null, running:false, _userPanned:false
|
|
131
131
|
};
|
|
132
132
|
|
|
133
133
|
async function fetchProject(){
|
|
@@ -198,19 +198,47 @@ function updateStats(){
|
|
|
198
198
|
function layoutGraph(){
|
|
199
199
|
const nodes=S.graph.nodes; if(!nodes.length)return;
|
|
200
200
|
const c=document.getElementById('graph-canvas');
|
|
201
|
-
const w=c.clientWidth||800,h=c.clientHeight||500
|
|
201
|
+
const w=c.clientWidth||800,h=c.clientHeight||500;
|
|
202
|
+
// Group non-module nodes by module
|
|
202
203
|
const mods=new Map();
|
|
203
|
-
|
|
204
|
-
const
|
|
204
|
+
S.modMeta=new Map(); // module → {cx,cy,radius,color}
|
|
205
|
+
for(const n of nodes){
|
|
206
|
+
if(n.type==='module') continue;
|
|
207
|
+
const m=n.module||'other';
|
|
208
|
+
if(!mods.has(m)) mods.set(m,[]);
|
|
209
|
+
mods.get(m).push(n);
|
|
210
|
+
}
|
|
211
|
+
const keys=[...mods.keys()].sort((a,b)=>(mods.get(b).length-mods.get(a).length));
|
|
212
|
+
const nMods=keys.length||1;
|
|
213
|
+
// Module color palette (10 distinct colors, cycle)
|
|
214
|
+
const palette=['#4ecca3','#e94560','#f39c12','#3498db','#9b59b6','#1abc9c','#e67e22','#2ecc71','#e84393','#00cec9'];
|
|
215
|
+
// Adaptive ring radius
|
|
216
|
+
const totalMembers=nodes.filter(n=>n.type!=='module').length;
|
|
217
|
+
const baseRadius=Math.max(300, Math.sqrt(totalMembers)*30);
|
|
205
218
|
for(let i=0;i<keys.length;i++){
|
|
206
|
-
const mn=mods.get(keys[i]);
|
|
207
|
-
const
|
|
208
|
-
const mcx=
|
|
219
|
+
const mn=mods.get(keys[i]); if(!mn)continue;
|
|
220
|
+
const modAngle=(i/nMods)*Math.PI*2-Math.PI/2;
|
|
221
|
+
const mcx=w/2+Math.cos(modAngle)*baseRadius;
|
|
222
|
+
const mcy=h/2+Math.sin(modAngle)*baseRadius;
|
|
223
|
+
const subRadius=Math.max(50, Math.sqrt(mn.length)*22);
|
|
224
|
+
const col=palette[i%palette.length];
|
|
225
|
+
// Store module meta for cluster rendering
|
|
226
|
+
const modNodeId='module:'+keys[i];
|
|
227
|
+
S.nodePos.set(modNodeId,{x:mcx,y:mcy});
|
|
228
|
+
S.modMeta.set(keys[i],{cx:mcx,cy:mcy,radius:subRadius+30,color:col,count:mn.length});
|
|
209
229
|
for(let j=0;j<mn.length;j++){
|
|
210
|
-
const na=(j/mn.length)*Math.PI*2
|
|
211
|
-
S.nodePos.set(mn[j].id,{x:mcx+Math.cos(na)*
|
|
230
|
+
const na=(j/mn.length)*Math.PI*2;
|
|
231
|
+
S.nodePos.set(mn[j].id,{x:mcx+Math.cos(na)*subRadius,y:mcy+Math.sin(na)*subRadius});
|
|
212
232
|
}
|
|
213
233
|
}
|
|
234
|
+
// Auto-center
|
|
235
|
+
if(nodes.length>0&&!S._userPanned){
|
|
236
|
+
let minX=Infinity,maxX=-Infinity,minY=Infinity,maxY=-Infinity;
|
|
237
|
+
for(const[,p] of S.nodePos){minX=Math.min(minX,p.x);maxX=Math.max(maxX,p.x);minY=Math.min(minY,p.y);maxY=Math.max(maxY,p.y);}
|
|
238
|
+
const gw=maxX-minX+300,gh=maxY-minY+300,gcx=minX+(maxX-minX)/2,gcy=minY+(maxY-minY)/2;
|
|
239
|
+
S.zoom=Math.min(1, Math.min(w/gw, h/gh));
|
|
240
|
+
S.pan.x=w/2-gcx*S.zoom;S.pan.y=h/2-gcy*S.zoom;
|
|
241
|
+
}
|
|
214
242
|
}
|
|
215
243
|
|
|
216
244
|
function renderCanvas(){
|
|
@@ -220,44 +248,83 @@ function renderCanvas(){
|
|
|
220
248
|
ctx.scale(dpr,dpr);
|
|
221
249
|
const w=canvas.clientWidth,h=canvas.clientHeight;
|
|
222
250
|
ctx.clearRect(0,0,w,h); ctx.save(); ctx.translate(S.pan.x,S.pan.y); ctx.scale(S.zoom,S.zoom);
|
|
223
|
-
//
|
|
251
|
+
// Subtle grid
|
|
224
252
|
ctx.strokeStyle='#151530';ctx.lineWidth=.5;
|
|
225
|
-
|
|
226
|
-
for(let
|
|
253
|
+
const gridStep=40;
|
|
254
|
+
for(let x=-2000;x<4000;x+=gridStep){ctx.beginPath();ctx.moveTo(x,-2000);ctx.lineTo(x,4000);ctx.stroke();}
|
|
255
|
+
for(let y=-2000;y<4000;y+=gridStep){ctx.beginPath();ctx.moveTo(-2000,y);ctx.lineTo(4000,y);ctx.stroke();}
|
|
256
|
+
|
|
227
257
|
const edges=S.graph.edges||[],nodes=S.graph.nodes||[];
|
|
228
|
-
|
|
258
|
+
const largeGraph=nodes.length>80;
|
|
259
|
+
|
|
260
|
+
// Draw module cluster backgrounds
|
|
261
|
+
if(S.modMeta){
|
|
262
|
+
for(const[name,m] of S.modMeta){
|
|
263
|
+
ctx.beginPath(); ctx.arc(m.cx,m.cy,m.radius,0,Math.PI*2);
|
|
264
|
+
ctx.fillStyle=m.color+'10'; ctx.fill();
|
|
265
|
+
ctx.strokeStyle=m.color+'30'; ctx.lineWidth=2; ctx.setLineDash([6,4]); ctx.stroke(); ctx.setLineDash([]);
|
|
266
|
+
// Module label at top of cluster
|
|
267
|
+
ctx.font='bold 13px "Courier New"'; ctx.fillStyle=m.color+'cc'; ctx.textAlign='center'; ctx.textBaseline='bottom';
|
|
268
|
+
ctx.fillText(name+' ('+m.count+')',m.cx,m.cy-m.radius-6);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// Draw "uses" edges with curved lines + gradient
|
|
229
273
|
for(const e of edges){
|
|
274
|
+
if(e.relation==='contains') continue; // never draw contains
|
|
230
275
|
const s=S.nodePos.get(e.source),t=S.nodePos.get(e.target);if(!s||!t)continue;
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
const
|
|
234
|
-
ctx.
|
|
235
|
-
|
|
236
|
-
|
|
276
|
+
const dx=t.x-s.x,dy=t.y-s.y,dist=Math.sqrt(dx*dx+dy*dy);
|
|
277
|
+
// Curved edge via quadratic bezier, perpendicular offset
|
|
278
|
+
const mx=(s.x+t.x)/2+dy*0.15, my=(s.y+t.y)/2-dx*0.15;
|
|
279
|
+
const grad=ctx.createLinearGradient(s.x,s.y,t.x,t.y);
|
|
280
|
+
grad.addColorStop(0,'rgba(233,69,96,.5)'); // controller (red)
|
|
281
|
+
grad.addColorStop(1,'rgba(78,204,163,.5)'); // model (green)
|
|
282
|
+
ctx.strokeStyle=grad;ctx.lineWidth=2;
|
|
283
|
+
ctx.beginPath();ctx.moveTo(s.x,s.y);ctx.quadraticCurveTo(mx,my,t.x,t.y);ctx.stroke();
|
|
284
|
+
// Arrowhead
|
|
285
|
+
const t2=0.95,at2x=(1-t2)*(1-t2)*s.x+2*(1-t2)*t2*mx+t2*t2*t.x,at2y=(1-t2)*(1-t2)*s.y+2*(1-t2)*t2*my+t2*t2*t.y;
|
|
286
|
+
const a=Math.atan2(t.y-at2y,t.x-at2x),al=8;
|
|
287
|
+
ctx.fillStyle='rgba(78,204,163,.6)';ctx.beginPath();ctx.moveTo(t.x,t.y);
|
|
288
|
+
ctx.lineTo(t.x-al*Math.cos(a-.4),t.y-al*Math.sin(a-.4));
|
|
289
|
+
ctx.lineTo(t.x-al*Math.cos(a+.4),t.y-al*Math.sin(a+.4));ctx.closePath();ctx.fill();
|
|
237
290
|
}
|
|
291
|
+
|
|
292
|
+
// Draw nodes
|
|
238
293
|
const tc={model:'#4ecca3',controller:'#e94560',api:'#f39c12',dto:'#3498db',module:'#9b59b6'};
|
|
239
294
|
const sc={idle:'#444',testing:'#f39c12',passed:'#4ecca3',failed:'#e94560'};
|
|
240
|
-
const icons={model:'📦',controller:'🎮',api:'🔌',dto:'📋',module:'📁'};
|
|
241
295
|
for(const n of nodes){
|
|
296
|
+
if(n.type==='module') continue; // drawn as cluster bg instead
|
|
242
297
|
const p=S.nodePos.get(n.id);if(!p)continue;
|
|
243
|
-
const sz=
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
else if(n.status==='
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
ctx.
|
|
250
|
-
ctx.
|
|
251
|
-
ctx.
|
|
252
|
-
ctx.
|
|
253
|
-
|
|
298
|
+
const sz=10,c=tc[n.type]||'#888',ol=sc[n.status]||'#444',hov=S.hoveredNode===n.id;
|
|
299
|
+
// Glow for active status
|
|
300
|
+
if(n.status==='testing'){ctx.shadowColor=sc.testing;ctx.shadowBlur=14;}
|
|
301
|
+
else if(n.status==='passed'){ctx.shadowColor=sc.passed;ctx.shadowBlur=10;}
|
|
302
|
+
else if(n.status==='failed'){ctx.shadowColor=sc.failed;ctx.shadowBlur=12;}
|
|
303
|
+
// Node circle instead of rect for cleaner look
|
|
304
|
+
ctx.beginPath();ctx.arc(p.x,p.y,sz,0,Math.PI*2);
|
|
305
|
+
ctx.fillStyle=c;ctx.fill();
|
|
306
|
+
ctx.shadowBlur=0;
|
|
307
|
+
ctx.strokeStyle=hov?'#fff':ol;ctx.lineWidth=hov?3:1.5;ctx.stroke();
|
|
308
|
+
// Icon
|
|
309
|
+
ctx.font=(sz)+'px serif';ctx.textAlign='center';ctx.textBaseline='middle';
|
|
310
|
+
ctx.fillText(n.type==='model'?'📦':'🎮',p.x,p.y);
|
|
311
|
+
// Label — show at higher zoom or on hover
|
|
312
|
+
if(S.zoom>0.4||hov){
|
|
313
|
+
ctx.font='9px "Courier New"';ctx.fillStyle=hov?'#fff':'#aaa';ctx.textAlign='center';ctx.textBaseline='top';
|
|
314
|
+
ctx.fillText((n.label||n.id.split(':').pop()).substring(0,18),p.x,p.y+sz+3);
|
|
315
|
+
}
|
|
254
316
|
}
|
|
255
317
|
ctx.restore();
|
|
318
|
+
|
|
319
|
+
// HUD legend
|
|
320
|
+
ctx.font='10px "Courier New"';ctx.textAlign='left';
|
|
321
|
+
const leg=[['📦 Model','#4ecca3'],['🎮 Controller','#e94560'],['─── uses','rgba(200,100,130,.8)']];
|
|
322
|
+
for(let i=0;i<leg.length;i++){ctx.fillStyle=leg[i][1];ctx.fillText(leg[i][0],10,h-40+i*14);}
|
|
256
323
|
}
|
|
257
324
|
|
|
258
325
|
function setupCanvas(){
|
|
259
326
|
const c=document.getElementById('graph-canvas');
|
|
260
|
-
c.addEventListener('mousedown',e=>{S.dragging=true;S.dragStart={x:e.clientX-S.pan.x,y:e.clientY-S.pan.y};c.style.cursor='grabbing';});
|
|
327
|
+
c.addEventListener('mousedown',e=>{S.dragging=true;S._userPanned=true;S.dragStart={x:e.clientX-S.pan.x,y:e.clientY-S.pan.y};c.style.cursor='grabbing';});
|
|
261
328
|
c.addEventListener('mousemove',e=>{
|
|
262
329
|
if(S.dragging){S.pan.x=e.clientX-S.dragStart.x;S.pan.y=e.clientY-S.dragStart.y;renderCanvas();}
|
|
263
330
|
const rect=c.getBoundingClientRect(),mx=(e.clientX-rect.left-S.pan.x)/S.zoom,my=(e.clientY-rect.top-S.pan.y)/S.zoom;
|
|
@@ -288,7 +355,34 @@ function updateAll(){
|
|
|
288
355
|
function renderModList(){
|
|
289
356
|
const el=document.getElementById('mod-list'),mods=S.graph.nodes.filter(n=>n.type==='module');
|
|
290
357
|
if(!mods.length){el.innerHTML='<div style="padding:8px;color:#555;font-size:10px">No modules found</div>';return;}
|
|
291
|
-
|
|
358
|
+
// Sort by member count descending
|
|
359
|
+
const sorted=mods.sort((a,b)=>{
|
|
360
|
+
const ca=S.modMeta&&S.modMeta.get(a.label)?S.modMeta.get(a.label).count:0;
|
|
361
|
+
const cb=S.modMeta&&S.modMeta.get(b.label)?S.modMeta.get(b.label).count:0;
|
|
362
|
+
return cb-ca;
|
|
363
|
+
});
|
|
364
|
+
el.innerHTML=sorted.map(m=>{
|
|
365
|
+
const meta=S.modMeta&&S.modMeta.get(m.label);
|
|
366
|
+
const cnt=meta?meta.count:'';
|
|
367
|
+
const col=meta?meta.color:'#888';
|
|
368
|
+
return '<div class="mod-item" data-mod="'+esc(m.label)+'" style="border-left:3px solid '+col+';padding-left:6px">'
|
|
369
|
+
+'<div class="dot '+m.status+'"></div>'
|
|
370
|
+
+esc(m.label)+' <span style="color:#555;font-size:9px">('+cnt+')</span></div>';
|
|
371
|
+
}).join('');
|
|
372
|
+
// Click to navigate
|
|
373
|
+
el.querySelectorAll('.mod-item').forEach(item=>{
|
|
374
|
+
item.addEventListener('click',()=>{
|
|
375
|
+
const name=item.getAttribute('data-mod');
|
|
376
|
+
const meta=S.modMeta&&S.modMeta.get(name);
|
|
377
|
+
if(meta){
|
|
378
|
+
const c=document.getElementById('graph-canvas');
|
|
379
|
+
S.zoom=0.8;S._userPanned=true;
|
|
380
|
+
S.pan.x=c.clientWidth/2-meta.cx*S.zoom;
|
|
381
|
+
S.pan.y=c.clientHeight/2-meta.cy*S.zoom;
|
|
382
|
+
renderCanvas();
|
|
383
|
+
}
|
|
384
|
+
});
|
|
385
|
+
});
|
|
292
386
|
}
|
|
293
387
|
function renderAgentSB(){
|
|
294
388
|
document.getElementById('agent-sidebar').innerHTML=S.agents.map(a=>
|