Files
open-design/src/router.ts
T
pftom a98096a042 Add initial project structure with essential files
- Created .gitignore to exclude build artifacts and dependencies.
- Added index.html as the main entry point for the application.
- Included LICENSE file with Apache 2.0 terms.
- Initialized package.json and package-lock.json for project dependencies.
- Added pnpm-lock.yaml for package management.
- Created QUICKSTART.md for setup instructions.
- Added README.md and README.zh-CN.md for project documentation in English and Chinese.
2026-04-28 12:25:59 +08:00

66 lines
2.2 KiB
TypeScript

// Tiny URL router. We avoid pulling in react-router for two reasons:
// the surface area we need is small (three routes, plain pushState), and
// we want a single source of truth for "what file is open" — encoding
// that in the URL is the simplest way to make it deep-linkable.
import { useEffect, useState } from 'react';
export type Route =
| { kind: 'home' }
| { kind: 'project'; projectId: string; fileName: string | null };
export function parseRoute(pathname: string): Route {
const parts = pathname.replace(/\/+$/, '').split('/').filter(Boolean);
if (parts.length === 0) return { kind: 'home' };
if (parts[0] === 'projects' && parts[1]) {
const projectId = decodeURIComponent(parts[1]);
if (parts[2] === 'files' && parts[3]) {
return {
kind: 'project',
projectId,
fileName: decodeURIComponent(parts.slice(3).join('/')),
};
}
return { kind: 'project', projectId, fileName: null };
}
return { kind: 'home' };
}
export function buildPath(route: Route): string {
if (route.kind === 'home') return '/';
const id = encodeURIComponent(route.projectId);
if (route.fileName) {
const file = route.fileName
.split('/')
.map((s) => encodeURIComponent(s))
.join('/');
return `/projects/${id}/files/${file}`;
}
return `/projects/${id}`;
}
// Centralized navigation. Components call this instead of mutating
// `window.location` directly so we can fan the change out to any
// `useRoute()` subscriber via a custom event.
export function navigate(route: Route, opts: { replace?: boolean } = {}): void {
const target = buildPath(route);
const current = window.location.pathname;
if (target === current) return;
if (opts.replace) {
window.history.replaceState(null, '', target);
} else {
window.history.pushState(null, '', target);
}
window.dispatchEvent(new PopStateEvent('popstate'));
}
export function useRoute(): Route {
const [route, setRoute] = useState<Route>(() => parseRoute(window.location.pathname));
useEffect(() => {
const onPop = () => setRoute(parseRoute(window.location.pathname));
window.addEventListener('popstate', onPop);
return () => window.removeEventListener('popstate', onPop);
}, []);
return route;
}