struct graph
This commit is contained in:
@@ -6,6 +6,7 @@ import 'monaco-editor/esm/vs/editor/contrib/folding/browser/folding';
|
||||
import 'monaco-editor/esm/vs/editor/contrib/find/browser/findController';
|
||||
import EditorWorker from 'monaco-editor/esm/vs/editor/editor.worker?worker';
|
||||
import './style.css';
|
||||
import { installSidebarResize } from './resize';
|
||||
import { installTheme } from './theme';
|
||||
import { installNavigation } from './navigation';
|
||||
import { installGraph } from './graph';
|
||||
@@ -21,7 +22,7 @@ type Hit = { path: string; line: number; column: number; end_column: number; pre
|
||||
type SearchResults = { hits: Hit[]; truncated: boolean; cancelled: boolean; skipped_files: number };
|
||||
const $ = <T extends HTMLElement = HTMLElement>(id: string) => document.getElementById(id) as T;
|
||||
document.querySelector<HTMLDivElement>('#app')!.innerHTML = `
|
||||
<header><div class="brand">cex<span>CODE EXPLORER</span></div><div id="project-name">No project open</div><select id="theme-select" aria-label="Color theme" title="Color theme"><option value="system">System</option><option value="light">Light</option><option value="dark">Dark</option></select><button id="toggle-sidebar" class="icon-button" aria-label="Toggle right sidebar" title="Toggle right sidebar" aria-expanded="false" aria-controls="explanation-pane" disabled><svg viewBox="0 0 20 20" width="18" height="18" fill="none" stroke="currentColor" stroke-width="1.5" aria-hidden="true"><rect x="2.5" y="3.5" width="15" height="13" rx="2"/><path d="M12.5 4v12"/></svg></button><button id="workspace-button">Open project…</button></header>
|
||||
<header><details id="file-menu"><summary>File</summary><div class="file-dropdown"><button id="file-open">Open Project…</button><details id="file-recents"><summary>Open Recent</summary><div id="file-recent-list"><span class="hint">Loading…</span></div></details><button id="file-welcome">Return to Welcome</button></div></details><div class="brand">cex<span>CODE EXPLORER</span></div><div id="project-name">No project open</div><select id="theme-select" aria-label="Color theme" title="Color theme"><option value="system">System</option><option value="light">Light</option><option value="dark">Dark</option></select><button id="toggle-sidebar" class="icon-button" aria-label="Toggle right sidebar" title="Toggle right sidebar" aria-expanded="false" aria-controls="explanation-pane" disabled><svg viewBox="0 0 20 20" width="18" height="18" fill="none" stroke="currentColor" stroke-width="1.5" aria-hidden="true"><rect x="2.5" y="3.5" width="15" height="13" rx="2"/><path d="M12.5 4v12"/></svg></button><button id="workspace-button">Open project…</button></header>
|
||||
<main class="landing">
|
||||
<section id="landing" aria-label="Open Recent"><div class="landing-intro"><div class="welcome-mark">{ cex }</div><h1>Open Recent</h1><p>Pick up where you left off, or explore a new project.</p><button id="recent-open" class="primary">Open a project</button></div><div class="recent-heading"><h2>Recent projects</h2><button id="refresh-recents" aria-label="Refresh recent projects">Refresh</button></div><p id="recent-status" class="hint" role="status">Loading recent projects…</p><div id="recent-projects"></div><small class="hint">History is stored in ~/.cex. Project settings stay with each project.</small></section>
|
||||
<aside><nav><button id="files-tab" class="active">Files</button><button id="search-tab">Search</button><button id="navigation-tab">Navigation</button></nav>
|
||||
@@ -75,11 +76,14 @@ const navigation = installNavigation(editor, { workspace: () => workspace, path:
|
||||
const explanations = installExplanations(editor, { workspace: () => workspace, path: () => currentPath, epoch: () => epoch, ready: navigation.whenReady, open: openFile });
|
||||
const graph = installGraph(editor, { workspace: () => workspace, path: () => currentPath, epoch: () => epoch, ready: navigation.whenReady, open: openFile });
|
||||
const vim = installVim(editor, navigation);
|
||||
installSidebarResize(editor);
|
||||
function status(message: string, error = false) { $('status').textContent = message; $('status').classList.toggle('error', error); }
|
||||
function errorText(error: unknown) { return error instanceof Error ? error.message : String(error); }
|
||||
function button(text: string, className = '') { const el = document.createElement('button'); el.textContent = text; el.className = className; return el; }
|
||||
|
||||
let closingWorkspace = false;
|
||||
function showSetup() {
|
||||
if (closingWorkspace) return;
|
||||
source.value = workspace?.root ?? '';
|
||||
build.value = workspace?.settings.build_directory ?? '';
|
||||
clangdPath.value = workspace?.settings.clangd_path ?? 'clangd';
|
||||
@@ -312,10 +316,7 @@ async function loadRecents() {
|
||||
const path = document.createElement('span'); path.textContent = project.root;
|
||||
const date = document.createElement('small'); date.textContent = `Last opened ${new Date(project.last_opened * 1000).toLocaleString()}`;
|
||||
open.append(name, path, date); open.title = project.root;
|
||||
open.onclick = async () => {
|
||||
showSetup(); source.value = project.root; await inspectSource();
|
||||
if (inspection && !inspection.warning && source.value === project.root) $<HTMLFormElement>('setup-form').requestSubmit();
|
||||
};
|
||||
open.onclick = () => void openRecent(project.root);
|
||||
const remove = button('×', 'recent-remove'); remove.setAttribute('aria-label', `Remove ${name.textContent} from recent projects`);
|
||||
remove.title = 'Remove from history; project files are preserved';
|
||||
remove.onclick = async () => {
|
||||
@@ -331,3 +332,61 @@ async function loadRecents() {
|
||||
$('recent-open').onclick = () => $('workspace-button').click();
|
||||
$('refresh-recents').onclick = () => void loadRecents();
|
||||
void loadRecents();
|
||||
|
||||
|
||||
function closeFileMenu() { $<HTMLDetailsElement>('file-menu').open = false; }
|
||||
async function openRecent(root: string) {
|
||||
if (closingWorkspace) return;
|
||||
closeFileMenu(); showSetup(); source.value = root; await inspectSource();
|
||||
if (setup.open && inspection && !inspection.warning && source.value === root) $<HTMLFormElement>('setup-form').requestSubmit();
|
||||
}
|
||||
$('file-open').onclick = () => {
|
||||
if (closingWorkspace) return;
|
||||
closeFileMenu(); showSetup(); ++inspecting;
|
||||
source.value = ''; build.value = ''; clangdPath.value = 'clangd'; inspectedRoot = ''; inspection = null;
|
||||
reset.checked = false; reset.disabled = true; $('saved-state').textContent = ''; $('setup-title').textContent = 'Open project'; source.focus();
|
||||
};
|
||||
$('file-recents').addEventListener('toggle', async () => {
|
||||
if (!$<HTMLDetailsElement>('file-recents').open) return;
|
||||
$('file-recent-list').textContent = 'Loading…';
|
||||
try {
|
||||
const projects = await invoke<{root:string;last_opened:number}[]>('recent_projects');
|
||||
$('file-recent-list').replaceChildren(...projects.map(project => {
|
||||
const item = button('', 'file-recent');
|
||||
const name = document.createElement('strong'); name.textContent = project.root.split('/').pop() || project.root;
|
||||
const path = document.createElement('small'); path.textContent = project.root;
|
||||
item.append(name,path); item.title = project.root; item.onclick = () => void openRecent(project.root); return item;
|
||||
}));
|
||||
if (!projects.length) $('file-recent-list').textContent = 'No recent projects yet.';
|
||||
} catch(error) { $('file-recent-list').textContent = errorText(error); }
|
||||
});
|
||||
$('file-menu').addEventListener('toggle', () => {
|
||||
if ($<HTMLDetailsElement>('file-menu').open) {
|
||||
$<HTMLButtonElement>('file-welcome').disabled = !workspace || closingWorkspace;
|
||||
$<HTMLDetailsElement>('file-recents').open = false;
|
||||
}
|
||||
});
|
||||
document.addEventListener('pointerdown', event => { if (!$('file-menu').contains(event.target as Node)) closeFileMenu(); });
|
||||
document.addEventListener('keydown', event => {
|
||||
if (event.key === 'Escape' && $<HTMLDetailsElement>('file-menu').open) { closeFileMenu(); $('file-menu').querySelector('summary')!.focus(); }
|
||||
});
|
||||
$('file-welcome').onclick = async () => {
|
||||
if (!workspace || closingWorkspace) return;
|
||||
const root = workspace.root;
|
||||
closingWorkspace = true; closeFileMenu(); ++epoch; ++fileRequest; ++searchRequest;
|
||||
clearTimeout(searchTimer); graph.reset(); explanations.reset();
|
||||
status('Closing project…');
|
||||
try {
|
||||
await rememberQueue;
|
||||
await invoke('close_workspace', {root});
|
||||
workspace = null; currentPath = ''; viewStates.clear(); navigation.reset(); vim.reset();
|
||||
const model = editor.getModel(); editor.setModel(null); model?.dispose();
|
||||
graph.reset(); $<HTMLButtonElement>('toggle-sidebar').disabled = true;
|
||||
$('landing').hidden = false; document.querySelector('main')!.classList.add('landing');
|
||||
$('project-name').textContent = 'No project open'; $('project-name').title = '';
|
||||
$('workspace-button').textContent = 'Open project…'; $('file-path').textContent = 'Welcome to CEX';
|
||||
$('cursor').textContent = ''; $('tree').replaceChildren(); $('results').replaceChildren(); query.value = '';
|
||||
status('Ready'); await loadRecents(); $('recent-open').focus();
|
||||
} catch(error) { status(`Could not close project: ${errorText(error)}`, true); }
|
||||
finally { closingWorkspace = false; }
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user