Touch: in walk and fly the left thumb drives a virtual joystick while the right thumb looks around; pinch zooms in every view (and climbs when flying); help text switches for coarse pointers. Tour (T or the Tour button) flies the helicopter over the six largest districts with captions of their file and line counts, circling each before moving on, and stops on any input. Copy link encodes the GitHub repository, view, city camera and selected file in the URL fragment. Opening such a link loads the repository (including through GitHub sign-in for private repos) and restores the place. Fragments are validated: only github.com repositories and finite camera values are accepted. Helpers are unit tested.
181 lines
14 KiB
HTML
181 lines
14 KiB
HTML
<!doctype html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="utf-8">
|
||
<meta name="viewport" content="width=device-width,initial-scale=1,viewport-fit=cover">
|
||
<meta name="theme-color" content="#0b0d13">
|
||
<meta name="description" content="Explore an entire codebase as a fast, searchable 2D and 3D landscape.">
|
||
<title>CodeNavigator</title>
|
||
<link rel="icon" href="./favicon.svg" type="image/svg+xml">
|
||
<link rel="preload" href="./inter-latin.woff2" as="font" type="font/woff2" crossorigin>
|
||
<link rel="stylesheet" href="./styles.css">
|
||
</head>
|
||
<body>
|
||
<main id="app">
|
||
<header class="toolbar" aria-label="Codebase controls">
|
||
<div class="brand" aria-label="CodeNavigator"><svg viewBox="0 0 28 28" aria-hidden="true"><defs><linearGradient id="brandGradient" x1="0" y1="0" x2="1" y2="1"><stop offset="0" stop-color="#9a7dff"/><stop offset=".5" stop-color="#5f9dff"/><stop offset="1" stop-color="#36d3c2"/></linearGradient></defs><rect x="3" y="11" width="6" height="14" rx="1.6" fill="url(#brandGradient)" opacity=".7"/><rect x="11" y="3" width="6" height="22" rx="1.6" fill="url(#brandGradient)"/><rect x="19" y="15" width="6" height="10" rx="1.6" fill="url(#brandGradient)" opacity=".5"/></svg><span class="brand-name">Code<em>Navigator</em></span></div>
|
||
<div class="tool-group">
|
||
<button class="tool primary" id="loadButton" title="Open codebase" aria-label="Open codebase"><svg viewBox="0 0 24 24"><path d="M3.5 7.5h6l2-2h4l2 2h3v11h-17z"/><path d="M3.5 9.5h17"/></svg></button>
|
||
<button class="tool active" id="mapButton" title="Code landscape" aria-label="Code landscape"><svg viewBox="0 0 24 24"><rect x="3.5" y="3.5" width="17" height="17" rx="2"/><path d="M3.5 10h17M10 3.5v17M10 15h10.5"/></svg></button>
|
||
<button class="tool" id="layersButton" title="Semantic layers" aria-label="Semantic layers"><svg viewBox="0 0 24 24"><path d="m4 8 8-4 8 4-8 4zM4 12l8 4 8-4M4 16l8 4 8-4"/></svg></button>
|
||
</div>
|
||
<span class="separator"></span>
|
||
<div class="tool-group view-switch" role="group" aria-label="View mode">
|
||
<button class="tool active" data-view="2d" title="Flat map" aria-label="Flat map"><svg viewBox="0 0 24 24"><rect x="3.5" y="3.5" width="17" height="17" rx="1.5"/><path d="M3.5 11h17M12 3.5v17"/></svg></button>
|
||
<button class="tool" data-view="3d" title="3D landscape" aria-label="3D landscape"><svg viewBox="0 0 24 24"><path d="m12 3 8 4.5v9L12 21l-8-4.5v-9zM4 7.5l8 4.5 8-4.5M12 12v9"/></svg></button>
|
||
<button class="tool" data-view="city" title="City" aria-label="City"><svg viewBox="0 0 24 24"><path d="M3 21h18M5 21V10l4-2v13M9 21V4l6 3v14M15 21v-9l4-1.5V21"/><path d="M11.5 9v.01M11.5 12v.01M11.5 15v.01"/></svg></button>
|
||
<button class="tool" id="codeButton" title="Toggle source texture" aria-label="Toggle source texture"><svg viewBox="0 0 24 24"><path d="M4 18 9 6l4 8 2-5 5 9z"/><path d="M4 20h16"/></svg></button>
|
||
</div>
|
||
<span class="separator"></span>
|
||
<div class="metric-switch" role="group" aria-label="Map metric">
|
||
<button class="metric active" data-metric="lines"><b>#</b> <span>Lines</span></button>
|
||
<button class="metric" data-metric="references"><b>⌁</b> <span>References</span></button>
|
||
</div>
|
||
<button class="tool palette" id="paletteButton" title="Change palette" aria-label="Change palette"><svg viewBox="0 0 24 24"><path d="M20 13a8 8 0 1 0-8 7h1.5a1.5 1.5 0 0 0 0-3H12a2 2 0 0 1 0-4z"/><circle cx="7.5" cy="11" r="1"/><circle cx="10" cy="7.5" r="1"/><circle cx="14.5" cy="7.5" r="1"/><circle cx="17" cy="11" r="1"/></svg></button>
|
||
<div class="toolbar-spacer"></div>
|
||
<div class="scene-stats" id="sceneStats" aria-live="polite">
|
||
<span id="headlineStats">No codebase loaded</span>
|
||
<small id="fileStats">Open a folder or public repository</small>
|
||
</div>
|
||
<label class="filter-wrap">
|
||
<span class="sr-only">Filter files and symbols</span>
|
||
<input id="searchInput" type="search" placeholder="Find a file or symbol…" autocomplete="off" spellcheck="false">
|
||
<kbd>/</kbd>
|
||
</label>
|
||
<button class="tool" id="settingsButton" title="Performance details" aria-label="Performance details" aria-haspopup="dialog" aria-controls="settingsDialog"><svg viewBox="0 0 24 24"><circle cx="12" cy="12" r="3"/><path d="M19 13.5v-3l-2-.7-.7-1.7.9-1.9-2.1-2.1-1.9.9-1.7-.7-.7-2h-3l-.7 2-1.7.7-1.9-.9-2.1 2.1.9 1.9-.7 1.7-2 .7v3l2 .7.7 1.7-.9 1.9 2.1 2.1 1.9-.9 1.7.7.7 2h3l.7-2 1.7-.7 1.9.9 2.1-2.1-.9-1.9.7-1.7z"/></svg></button>
|
||
<span class="live-dot" role="status" aria-label="Connecting to indexer" title="Connecting to indexer"></span>
|
||
</header>
|
||
|
||
<section class="workspace">
|
||
<div class="viewport" id="viewport">
|
||
<canvas id="glCanvas"></canvas>
|
||
<canvas id="overlayCanvas"></canvas>
|
||
<div class="welcome" id="welcome">
|
||
<span class="eyebrow"><i></i>Codebase landscapes</span>
|
||
<h1>See your code<br><span>as a place.</span></h1>
|
||
<p>Every file becomes a tile sized by its lines, grouped by folder and coloured by purpose. Fly over it in 2D or 3D, search every symbol, and follow imports from file to file.</p>
|
||
<div class="welcome-actions"><button type="button" class="button-primary" id="welcomeOpen">Open a codebase</button><span><kbd>Ctrl</kbd> <kbd>O</kbd></span></div>
|
||
<div class="welcome-features"><span>Public & private GitHub repos</span><span>Local folders</span><span>Symbol search</span><span>Import graph</span></div>
|
||
</div>
|
||
<div class="empty-state" id="emptyState" hidden>
|
||
<strong>No source files found</strong>
|
||
<span>Try another folder or public GitHub repository.</span>
|
||
</div>
|
||
<div class="progress-card" id="progressCard" role="status" aria-live="polite" hidden>
|
||
<button type="button" class="progress-dismiss close-button" id="dismissProgress" aria-label="Dismiss notification"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="m6 6 12 12M18 6 6 18"/></svg></button>
|
||
<div class="progress-copy">
|
||
<strong id="progressTitle">Indexing codebase</strong>
|
||
<span id="progressMessage">Discovering files…</span>
|
||
</div>
|
||
<div class="progress-track"><i id="progressFill"></i></div>
|
||
<span class="progress-count" id="progressCount"></span>
|
||
<a class="dialog-button progress-action" id="progressAction" hidden></a>
|
||
</div>
|
||
<div class="tooltip" id="tooltip" hidden></div>
|
||
<div class="camera-help" id="cameraHelp" hidden>
|
||
<button type="button" data-camera-preset="isometric">Isometric</button>
|
||
<button type="button" data-camera-preset="top">Top</button>
|
||
<button type="button" data-camera-preset="front">Front</button>
|
||
<span><b>Drag</b> orbit</span>
|
||
<span><b>Shift + drag</b> pan</span>
|
||
<span><b>Scroll</b> dolly</span>
|
||
<span><b>F</b> reset</span>
|
||
</div>
|
||
<div class="city-hud" id="cityHud" hidden>
|
||
<div class="city-views" role="group" aria-label="City camera">
|
||
<button type="button" data-city-view="heli" title="Helicopter (1)"><kbd>1</kbd> Helicopter</button>
|
||
<button type="button" data-city-view="walk" title="Walk (2)"><kbd>2</kbd> Walk</button>
|
||
<button type="button" data-city-view="fly" title="Fly (3)"><kbd>3</kbd> Fly</button>
|
||
</div>
|
||
<div class="city-actions">
|
||
<button type="button" id="tourButton" title="Guided tour (T)">Tour</button>
|
||
<button type="button" id="shareButton" title="Copy a link to this place">Copy link</button>
|
||
</div>
|
||
<span class="city-help" id="cityHelp"></span>
|
||
</div>
|
||
<div class="crosshair" id="crosshair" hidden></div>
|
||
<div class="tour-caption" id="tourCaption" hidden></div>
|
||
<div class="joystick" id="joystick" hidden><i></i></div>
|
||
<div class="city-target" id="cityTarget" hidden></div>
|
||
<div class="city-address" id="cityAddress" hidden></div>
|
||
<canvas class="minimap" id="minimap" width="220" height="150" hidden title="Click to travel"></canvas>
|
||
<nav class="breadcrumb" id="breadcrumb" aria-label="Selected file path">
|
||
<button id="homeButton" aria-label="Fit entire codebase">←</button>
|
||
<span id="breadcrumbText">No codebase loaded</span>
|
||
</nav>
|
||
<div class="fps" id="fps">— fps</div>
|
||
<div class="legend" id="legend"></div>
|
||
<div class="canvas-controls" aria-label="Landscape navigation"><button type="button" id="zoomOutButton" title="Zoom out" aria-label="Zoom out">−</button><button type="button" id="fitButton" title="Fit codebase (F)" aria-label="Fit codebase"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M8 4H4v4m12-4h4v4M4 16v4h4m12-4v4h-4"/></svg></button><button type="button" id="zoomInButton" title="Zoom in" aria-label="Zoom in">+</button></div>
|
||
</div>
|
||
|
||
<aside class="inspector" id="inspector">
|
||
<div class="tabs" role="tablist">
|
||
<button class="tab active" data-tab="inspector" role="tab">Inspector</button>
|
||
<button class="tab" data-tab="results" role="tab">Results <span id="resultBadge"></span></button>
|
||
<button class="tab" data-tab="history" role="tab">History</button>
|
||
</div>
|
||
<section class="panel active" id="panel-inspector" role="tabpanel">
|
||
<p class="panel-hint" id="inspectHint">Select an entity on the map</p>
|
||
<div id="inspectContent"></div>
|
||
</section>
|
||
<section class="panel" id="panel-results" role="tabpanel">
|
||
<div class="result-summary" id="resultSummary">Type in the filter to search files and symbols.</div>
|
||
<div class="result-list" id="resultList"></div>
|
||
</section>
|
||
<section class="panel" id="panel-history" role="tabpanel">
|
||
<div class="result-summary">Recently inspected</div>
|
||
<div class="result-list" id="historyList"></div>
|
||
</section>
|
||
</aside>
|
||
</section>
|
||
</main>
|
||
|
||
<dialog id="loadDialog" aria-labelledby="loadTitle">
|
||
<form method="dialog" class="dialog-shell">
|
||
<div class="dialog-head">
|
||
<div><small>YOUR NEXT PERSPECTIVE</small><h1 id="loadTitle">Open a codebase</h1><p class="dialog-description">See the big picture. Explore every detail.</p></div>
|
||
<button class="close-button" value="cancel" aria-label="Close"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="m6 6 12 12M18 6 6 18"/></svg></button>
|
||
</div>
|
||
<button type="button" class="source-card" id="localFolderButton">
|
||
<span class="source-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M3 7h7l2-2h4l2 2h3v12H3zM3 10h18"/></svg></span>
|
||
<span><strong>Local folder</strong><small>Temporarily indexed by this server</small></span>
|
||
<b>→</b>
|
||
</button>
|
||
<div class="source-card github-card">
|
||
<span class="source-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><circle cx="7" cy="5" r="2"/><circle cx="7" cy="19" r="2"/><circle cx="17" cy="5" r="2"/><path d="M7 7v10m10-10v3a4 4 0 0 1-4 4H7"/></svg></span>
|
||
<label for="githubInput"><strong>GitHub repository</strong><small>Public, or private once you sign in with GitHub</small></label>
|
||
<div class="github-row"><input id="githubInput" type="url" autocomplete="off" spellcheck="false" aria-describedby="githubError" placeholder="https://github.com/owner/repo"><button type="button" id="githubButton">Explore →</button></div>
|
||
<p class="form-error" id="githubError" role="alert"></p>
|
||
<p class="github-account" id="githubAccount" hidden></p>
|
||
</div>
|
||
<p class="dialog-foot">Code is held temporarily by this server, visible only to this browser, and cleared after an hour of inactivity.</p>
|
||
</form>
|
||
</dialog>
|
||
|
||
<dialog id="settingsDialog" class="settings-dialog" aria-labelledby="settingsTitle">
|
||
<form method="dialog" class="dialog-shell settings-shell">
|
||
<div class="dialog-head">
|
||
<div><small>MAKE IT YOURS</small><h1 id="settingsTitle">Workspace settings</h1><p class="dialog-description">A workspace that feels right for you.</p></div>
|
||
<button class="close-button" value="cancel" aria-label="Close settings"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="m6 6 12 12M18 6 6 18"/></svg></button>
|
||
</div>
|
||
<div id="settingsContent"></div>
|
||
<div class="preference-row"><label for="paletteSelect">Landscape palette<small>Color-code the layers of your codebase</small></label><select id="paletteSelect"><option value="0">Botanical</option><option value="1">Prismatic</option><option value="2">Autumn</option></select></div>
|
||
<div class="preference-row"><label for="sourceToggle">Source text<small>Show code directly in the landscape</small></label><input type="checkbox" class="switch" id="sourceToggle" checked></div>
|
||
<div class="preference-row"><label for="fpsToggle">Performance monitor<small>Display frame rate on the canvas</small></label><input type="checkbox" class="switch" id="fpsToggle"></div>
|
||
<div class="shortcut-guide"><span>Keyboard essentials</span><div><kbd>/</kbd> Search <kbd>F</kbd> Fit landscape <kbd>Esc</kbd> Close</div></div>
|
||
<div class="settings-note">
|
||
<span class="settings-note-icon" aria-hidden="true">⌁</span>
|
||
<p><strong>Room to explore</strong><small>Move from the whole codebase to a single line. Scroll to zoom; drag to move. In 3D, hold Shift while dragging to pan.</small></p>
|
||
</div>
|
||
<div class="settings-actions">
|
||
<button class="dialog-button" value="close">Done</button>
|
||
</div>
|
||
</form>
|
||
</dialog>
|
||
<input id="folderFallback" type="file" webkitdirectory multiple hidden>
|
||
|
||
<script src="./config.js"></script>
|
||
<script type="module" src="./app.js"></script>
|
||
</body>
|
||
</html>
|