|
475 | 475 | const baseCapacity = Math.max(4, Number(options.baseCapacity || 6)); |
476 | 476 | const capacityStep = Math.max(0, Number(options.capacityStep || 2)); |
477 | 477 | const angleOffset = Number(options.angleOffset || -90); |
| 478 | + const nodeDiameter = Math.max(80, Number(options.nodeDiameter || 120)); |
478 | 479 | const nodes = []; |
479 | 480 | const rings = []; |
480 | 481 | let index = 0; |
|
500 | 501 | return { |
501 | 502 | nodes, |
502 | 503 | rings, |
503 | | - lastRadius: rings.length ? rings[rings.length - 1].radius : Math.max(0, startRadius - ringStep) |
| 504 | + lastRadius: rings.length ? rings[rings.length - 1].radius : Math.max(0, startRadius - ringStep), |
| 505 | + nodeDiameter |
504 | 506 | }; |
505 | 507 | }; |
506 | 508 |
|
|
520 | 522 | root.innerHTML = `<div class="fv-docker-orbit-shell"><div class="fv-docker-orbit-empty"><h3>Orbit view failed to load</h3><p>${escapeHtml(message || 'Unknown error.')}</p><button type="button" class="fv-docker-orbit-button is-primary" data-fv-orbit-action="refresh">Retry</button></div></div>`; |
521 | 523 | }; |
522 | 524 |
|
523 | | - const renderNavigatorTree = (snapshot, folderModels) => { |
524 | | - const rootIds = snapshot.orderedFolderIds.filter((folderId) => !snapshot.hierarchy.parentById?.[folderId]); |
525 | | - const renderNode = (folderId) => { |
526 | | - const model = folderModels[folderId]; |
527 | | - if (!model) { |
528 | | - return ''; |
529 | | - } |
530 | | - const selected = folderId === selectedFolderId; |
531 | | - const children = Array.isArray(snapshot.hierarchy.childrenById?.[folderId]) |
532 | | - ? snapshot.hierarchy.childrenById[folderId].filter((childId) => Object.prototype.hasOwnProperty.call(folderModels, childId)) |
533 | | - : []; |
534 | | - return ` |
535 | | - <li class="fv-docker-orbit-nav-item"> |
536 | | - <button |
537 | | - type="button" |
538 | | - class="fv-docker-orbit-nav-button${selected ? ' is-selected' : ''}" |
539 | | - data-fv-orbit-action="select-folder" |
540 | | - data-folder-id="${escapeHtml(folderId)}" |
541 | | - style="--fv-docker-orbit-depth:${Number(model.depth || 0)};" |
542 | | - > |
543 | | - <img src="${model.icon}" class="fv-docker-orbit-nav-icon" alt="" loading="lazy" onerror='this.src="${DEFAULT_FOLDER_ICON}"'> |
544 | | - <span class="fv-docker-orbit-nav-copy"> |
545 | | - <span class="fv-docker-orbit-nav-name">${escapeHtml(model.name)}</span> |
546 | | - <span class="fv-docker-orbit-nav-meta">${model.branchMemberCount} in branch${model.updates > 0 ? ` • ${model.updates} updates` : ''}</span> |
547 | | - </span> |
548 | | - </button> |
549 | | - ${children.length ? `<ul class="fv-docker-orbit-nav-branch">${children.map(renderNode).join('')}</ul>` : ''} |
550 | | - </li> |
551 | | - `; |
552 | | - }; |
553 | | - return rootIds.length |
554 | | - ? `<ul class="fv-docker-orbit-nav-tree">${rootIds.map(renderNode).join('')}</ul>` |
555 | | - : '<div class="fv-docker-orbit-nav-empty">No folders yet.</div>'; |
556 | | - }; |
557 | | - |
558 | 525 | const renderSnapshot = (snapshot) => { |
559 | 526 | const root = ensureRoot(); |
560 | 527 | if (!(root instanceof HTMLElement)) { |
|
594 | 561 | } |
595 | 562 | const selectedMember = selectedMembers.find((member) => member.name === selectedMemberName) || null; |
596 | 563 | const breadcrumbs = buildBreadcrumbs(selectedFolderId, snapshot, folderModels); |
| 564 | + const relatedFolderIds = (() => { |
| 565 | + if (selectedFolder.childIds.length) { |
| 566 | + return selectedFolder.childIds.slice(); |
| 567 | + } |
| 568 | + if (selectedFolder.parentId) { |
| 569 | + return (snapshot.hierarchy.childrenById?.[selectedFolder.parentId] || []) |
| 570 | + .filter((folderId) => folderId !== selectedFolderId && Object.prototype.hasOwnProperty.call(folderModels, folderId)); |
| 571 | + } |
| 572 | + return orderedIds |
| 573 | + .filter((folderId) => folderId !== selectedFolderId && !folderModels[folderId]?.parentId); |
| 574 | + })(); |
| 575 | + const relatedFolders = relatedFolderIds |
| 576 | + .map((folderId) => folderModels[folderId]) |
| 577 | + .filter(Boolean) |
| 578 | + .map((model) => ({ |
| 579 | + folderId: model.folderId, |
| 580 | + name: model.name, |
| 581 | + icon: model.icon, |
| 582 | + count: model.branchMemberCount, |
| 583 | + running: model.running, |
| 584 | + updates: model.updates |
| 585 | + })); |
597 | 586 | const memberOrbit = layoutOrbitNodes(selectedFolder.directMembers, { |
598 | | - startRadius: 190, |
599 | | - ringStep: 88, |
600 | | - baseCapacity: 6, |
| 587 | + startRadius: 256, |
| 588 | + ringStep: 132, |
| 589 | + baseCapacity: 8, |
601 | 590 | capacityStep: 2 |
602 | 591 | }); |
603 | | - const childOrbit = layoutOrbitNodes(selectedFolder.childFolders, { |
604 | | - startRadius: Math.max(memberOrbit.lastRadius + 124, 320), |
605 | | - ringStep: 102, |
606 | | - baseCapacity: 8, |
| 592 | + const relatedFolderOrbit = layoutOrbitNodes(relatedFolders, { |
| 593 | + startRadius: Math.max(memberOrbit.lastRadius + 176, 420), |
| 594 | + ringStep: 148, |
| 595 | + baseCapacity: 10, |
607 | 596 | capacityStep: 2 |
608 | 597 | }); |
609 | | - const stageRadius = Math.max(220, memberOrbit.lastRadius, childOrbit.lastRadius); |
610 | | - const stageSize = Math.max(760, (stageRadius + 180) * 2); |
611 | | - const ringMarkup = [...memberOrbit.rings, ...childOrbit.rings] |
| 598 | + const stageRadius = Math.max( |
| 599 | + 260, |
| 600 | + memberOrbit.lastRadius + (memberOrbit.nodeDiameter / 2), |
| 601 | + relatedFolderOrbit.lastRadius + (relatedFolderOrbit.nodeDiameter / 2) |
| 602 | + ); |
| 603 | + const stageSize = Math.max(920, (stageRadius * 2) + 260); |
| 604 | + const ringMarkup = [...memberOrbit.rings, ...relatedFolderOrbit.rings] |
612 | 605 | .map((ring) => ring.radius) |
613 | 606 | .filter((radius, index, values) => values.indexOf(radius) === index) |
614 | 607 | .sort((left, right) => left - right) |
615 | 608 | .map((radius) => `<div class="fv-docker-orbit-ring" style="width:${radius * 2}px;height:${radius * 2}px;"></div>`) |
616 | 609 | .join(''); |
617 | | - const runtimeEntries = Object.values(snapshot.runtimeInfoByName || {}); |
618 | | - const cardsWithUpdates = orderedIds.filter((folderId) => folderModels[folderId]?.updates > 0).length; |
619 | | - const runningContainers = runtimeEntries.filter((entry) => resolveContainerState(entry) === 'running').length; |
620 | | - const updateReadyContainers = runtimeEntries.filter((entry) => containerHasUpdate(entry)).length; |
621 | 610 | const inspectorBody = selectedMember ? ` |
622 | 611 | <div class="fv-docker-orbit-inspector-card is-member ${escapeHtml(selectedMember.stateMeta.state)}"> |
623 | 612 | <div class="fv-docker-orbit-inspector-head"> |
|
653 | 642 | <div class="fv-docker-orbit-header"> |
654 | 643 | <div class="fv-docker-orbit-header-copy"> |
655 | 644 | <h2>Docker Orbit View</h2> |
656 | | - <p>Centered folder control with orbiting containers and child folders for faster branch navigation.</p> |
| 645 | + <p>Centered folder control with orbiting containers and related folders for faster branch navigation.</p> |
657 | 646 | </div> |
658 | 647 | <div class="fv-docker-orbit-toolbar"> |
659 | 648 | <button type="button" class="fv-docker-orbit-button" data-fv-orbit-action="refresh">Refresh</button> |
660 | | - <button type="button" class="fv-docker-orbit-button is-primary" data-fv-orbit-action="create-folder">Add Folder</button> |
| 649 | + <button type="button" class="fv-docker-orbit-button" data-fv-orbit-action="create-folder">Add Folder</button> |
661 | 650 | </div> |
662 | 651 | </div> |
663 | | - <div class="fv-docker-orbit-overview"> |
664 | | - <div class="fv-docker-orbit-overview-card"><span class="fv-docker-orbit-overview-value">${orderedIds.length}</span><span class="fv-docker-orbit-overview-label">folders</span></div> |
665 | | - <div class="fv-docker-orbit-overview-card"><span class="fv-docker-orbit-overview-value">${runtimeEntries.length}</span><span class="fv-docker-orbit-overview-label">containers</span></div> |
666 | | - <div class="fv-docker-orbit-overview-card"><span class="fv-docker-orbit-overview-value">${runningContainers}</span><span class="fv-docker-orbit-overview-label">running</span></div> |
667 | | - <div class="fv-docker-orbit-overview-card"><span class="fv-docker-orbit-overview-value">${cardsWithUpdates}</span><span class="fv-docker-orbit-overview-label">folders with updates</span></div> |
668 | | - <div class="fv-docker-orbit-overview-card"><span class="fv-docker-orbit-overview-value">${updateReadyContainers}</span><span class="fv-docker-orbit-overview-label">update ready</span></div> |
669 | | - </div> |
670 | 652 | <div class="fv-docker-orbit-layout"> |
671 | | - <aside class="fv-docker-orbit-nav"> |
672 | | - <div class="fv-docker-orbit-panel-title">Folders</div> |
673 | | - ${renderNavigatorTree(snapshot, folderModels)} |
674 | | - </aside> |
675 | 653 | <section class="fv-docker-orbit-main"> |
676 | 654 | <div class="fv-docker-orbit-breadcrumbs"> |
677 | 655 | ${breadcrumbs.map((crumb, index) => ` |
|
727 | 705 | ${member.updateReady ? '<span class="fv-docker-orbit-node-update">update</span>' : ''} |
728 | 706 | </button> |
729 | 707 | `).join('')} |
730 | | - ${childOrbit.nodes.map((child) => ` |
| 708 | + ${relatedFolderOrbit.nodes.map((child) => ` |
731 | 709 | <button |
732 | 710 | type="button" |
733 | 711 | class="fv-docker-orbit-node is-folder" |
|
0 commit comments