/ concept-collection / numbl-web-ide
Sign in
concept-collection / numbl-web-ide
numbl-web-ide / src / githubOpen.ts
76 lines · 3.3 KBBlameHistoryRaw
1import { applyThemeToElement, attachGitHubWorkspace, createIndexedDBFileSystem, parseGitHubSpec, type WorkbenchTheme } from 'minwebide';
2import { openNumblWorkbench, openStartingFile } from './ide';
3import './landing.css';
5// The #/github/<spec> route: a GitHub repository as a workspace of its own.
6// <spec> is anything parseGitHubSpec accepts — owner/repo, owner/repo@ref, or
7// a URL-encoded github.com URL. The URL is the identity: nothing is added to
8// the project registry. The first visit imports into a per-repo IndexedDB
9// database; later visits reopen that local copy, edits included, with the
10// Source Control view tracking changes against the imported commit (and
11// offering "Reload from GitHub" to start fresh).
13/** The per-repo workspace database backing a #/github route. */
14export function githubWorkspaceDbName(spec: { owner: string; repo: string; ref?: string; dir?: string }): string {
15 return `numbl-web-ide-gh-${spec.owner}-${spec.repo}${spec.ref ? `-${spec.ref}` : ''}${spec.dir ? `-${spec.dir}` : ''}`
16 .toLowerCase().replace(/[^a-z0-9._-]/g, '-');
19/** Handles a #/github/<spec> route. Returns a disposable view (the IDE, or an error screen). */
20export async function openGitHubRoute(container: HTMLElement, specText: string, theme: WorkbenchTheme): Promise<{ dispose(): void }> {
21 let fs: Awaited<ReturnType<typeof createIndexedDBFileSystem>> | undefined;
22 let ide: Awaited<ReturnType<typeof openNumblWorkbench>> | undefined;
23 try {
24 const spec = parseGitHubSpec(specText);
25 const name = `${spec.owner}/${spec.repo}`;
26 document.title = `${name} — numbl web IDE`;
28 fs = await createIndexedDBFileSystem({ dbName: githubWorkspaceDbName(spec) });
29 ide = await openNumblWorkbench(container, fs, name, theme);
30 ide.workbench.statusBar.removeItem('branding');
31 ide.workbench.statusBar.setItem('project', 'left', 'Projects', {
32 icon: 'arrow-left',
33 title: 'Back to projects',
34 onClick: () => { location.hash = '#/'; },
35 });
37 // imports on first visit (status bar progress + GitHub output channel);
38 // the README is left to openStartingFile, which prefers numbl entry points
39 const view = await attachGitHubWorkspace(ide.workbench, fs, spec, { autoOpenReadme: false, appName: 'numbl web IDE' });
40 await openStartingFile(fs, ide.workbench);
42 return {
43 dispose() {
44 view.dispose();
45 ide!.dispose();
46 fs!.dispose();
47 },
48 };
49 } catch (error) {
50 ide?.dispose();
51 fs?.dispose();
52 container.textContent = '';
53 const message = error instanceof Error ? error.message : String(error);
54 return renderErrorScreen(container, theme, `Could not open repository: ${message}`);
55 }
58function renderErrorScreen(container: HTMLElement, theme: WorkbenchTheme, text: string): { dispose(): void } {
59 const root = document.createElement('div');
60 root.className = 'landing';
61 applyThemeToElement(theme, root);
62 const inner = document.createElement('div');
63 inner.className = 'landing-inner';
64 const message = document.createElement('p');
65 message.className = 'landing-subtitle';
66 message.textContent = text;
67 inner.appendChild(message);
68 const back = document.createElement('a');
69 back.className = 'landing-link';
70 back.href = '#/';
71 back.textContent = 'Back to projects';
72 inner.appendChild(back);
73 root.appendChild(inner);
74 container.appendChild(root);
75 return { dispose: () => root.remove() };
moveopenescclose