182 lines
5.3 KiB
TypeScript
182 lines
5.3 KiB
TypeScript
|
|
import * as plugins from './plugins.js';
|
||
|
|
import * as appstate from './appstate.js';
|
||
|
|
|
||
|
|
const SmartRouter = plugins.domtools.plugins.smartrouter.SmartRouter;
|
||
|
|
|
||
|
|
export const validViews = ['overview', 'network', 'emails', 'logs', 'configuration', 'security'] as const;
|
||
|
|
export const validEmailFolders = ['queued', 'sent', 'failed', 'security'] as const;
|
||
|
|
|
||
|
|
export type TValidView = typeof validViews[number];
|
||
|
|
export type TValidEmailFolder = typeof validEmailFolders[number];
|
||
|
|
|
||
|
|
class AppRouter {
|
||
|
|
private router: InstanceType<typeof SmartRouter>;
|
||
|
|
private initialized = false;
|
||
|
|
private suppressStateUpdate = false;
|
||
|
|
|
||
|
|
constructor() {
|
||
|
|
this.router = new SmartRouter({ debug: false });
|
||
|
|
}
|
||
|
|
|
||
|
|
public init(): void {
|
||
|
|
if (this.initialized) return;
|
||
|
|
this.setupRoutes();
|
||
|
|
this.setupStateSync();
|
||
|
|
this.handleInitialRoute();
|
||
|
|
this.initialized = true;
|
||
|
|
}
|
||
|
|
|
||
|
|
private setupRoutes(): void {
|
||
|
|
// Main views
|
||
|
|
for (const view of validViews) {
|
||
|
|
if (view === 'emails') {
|
||
|
|
// Email root - default to queued
|
||
|
|
this.router.on('/emails', async () => {
|
||
|
|
this.updateViewState('emails');
|
||
|
|
this.updateEmailFolder('queued');
|
||
|
|
});
|
||
|
|
|
||
|
|
// Email with folder parameter
|
||
|
|
this.router.on('/emails/:folder', async (routeInfo) => {
|
||
|
|
const folder = routeInfo.params.folder as string;
|
||
|
|
if (validEmailFolders.includes(folder as TValidEmailFolder)) {
|
||
|
|
this.updateViewState('emails');
|
||
|
|
this.updateEmailFolder(folder as TValidEmailFolder);
|
||
|
|
} else {
|
||
|
|
// Invalid folder, redirect to queued
|
||
|
|
this.navigateTo('/emails/queued');
|
||
|
|
}
|
||
|
|
});
|
||
|
|
} else {
|
||
|
|
this.router.on(`/${view}`, async () => {
|
||
|
|
this.updateViewState(view);
|
||
|
|
});
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// Root redirect
|
||
|
|
this.router.on('/', async () => {
|
||
|
|
this.navigateTo('/overview');
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
private setupStateSync(): void {
|
||
|
|
// Sync URL when state changes programmatically (not from router)
|
||
|
|
appstate.uiStatePart.state.subscribe((uiState) => {
|
||
|
|
if (this.suppressStateUpdate) return;
|
||
|
|
|
||
|
|
const currentPath = window.location.pathname;
|
||
|
|
const expectedPath = this.getExpectedPath(uiState.activeView);
|
||
|
|
|
||
|
|
// Only update URL if it doesn't match current state
|
||
|
|
if (!currentPath.startsWith(expectedPath)) {
|
||
|
|
this.suppressStateUpdate = true;
|
||
|
|
if (uiState.activeView === 'emails') {
|
||
|
|
const emailState = appstate.emailOpsStatePart.getState();
|
||
|
|
this.router.pushUrl(`/emails/${emailState.currentView}`);
|
||
|
|
} else {
|
||
|
|
this.router.pushUrl(`/${uiState.activeView}`);
|
||
|
|
}
|
||
|
|
this.suppressStateUpdate = false;
|
||
|
|
}
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
private getExpectedPath(view: string): string {
|
||
|
|
if (view === 'emails') {
|
||
|
|
return '/emails';
|
||
|
|
}
|
||
|
|
return `/${view}`;
|
||
|
|
}
|
||
|
|
|
||
|
|
private handleInitialRoute(): void {
|
||
|
|
const path = window.location.pathname;
|
||
|
|
|
||
|
|
if (!path || path === '/') {
|
||
|
|
// Redirect root to overview
|
||
|
|
this.router.pushUrl('/overview');
|
||
|
|
} else {
|
||
|
|
// Parse current path and update state
|
||
|
|
const segments = path.split('/').filter(Boolean);
|
||
|
|
const view = segments[0];
|
||
|
|
|
||
|
|
if (validViews.includes(view as TValidView)) {
|
||
|
|
this.updateViewState(view as TValidView);
|
||
|
|
|
||
|
|
if (view === 'emails' && segments[1]) {
|
||
|
|
const folder = segments[1];
|
||
|
|
if (validEmailFolders.includes(folder as TValidEmailFolder)) {
|
||
|
|
this.updateEmailFolder(folder as TValidEmailFolder);
|
||
|
|
} else {
|
||
|
|
this.updateEmailFolder('queued');
|
||
|
|
}
|
||
|
|
} else if (view === 'emails') {
|
||
|
|
this.updateEmailFolder('queued');
|
||
|
|
}
|
||
|
|
} else {
|
||
|
|
// Invalid view, redirect to overview
|
||
|
|
this.router.pushUrl('/overview');
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
private updateViewState(view: string): void {
|
||
|
|
this.suppressStateUpdate = true;
|
||
|
|
const currentState = appstate.uiStatePart.getState();
|
||
|
|
if (currentState.activeView !== view) {
|
||
|
|
appstate.uiStatePart.setState({
|
||
|
|
...currentState,
|
||
|
|
activeView: view,
|
||
|
|
});
|
||
|
|
}
|
||
|
|
this.suppressStateUpdate = false;
|
||
|
|
}
|
||
|
|
|
||
|
|
private updateEmailFolder(folder: TValidEmailFolder): void {
|
||
|
|
this.suppressStateUpdate = true;
|
||
|
|
const currentState = appstate.emailOpsStatePart.getState();
|
||
|
|
if (currentState.currentView !== folder) {
|
||
|
|
appstate.emailOpsStatePart.setState({
|
||
|
|
...currentState,
|
||
|
|
currentView: folder as appstate.IEmailOpsState['currentView'],
|
||
|
|
});
|
||
|
|
}
|
||
|
|
this.suppressStateUpdate = false;
|
||
|
|
}
|
||
|
|
|
||
|
|
public navigateTo(path: string): void {
|
||
|
|
this.router.pushUrl(path);
|
||
|
|
}
|
||
|
|
|
||
|
|
public navigateToView(view: string): void {
|
||
|
|
if (validViews.includes(view as TValidView)) {
|
||
|
|
this.navigateTo(`/${view}`);
|
||
|
|
} else {
|
||
|
|
this.navigateTo('/overview');
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
public navigateToEmailFolder(folder: string): void {
|
||
|
|
if (validEmailFolders.includes(folder as TValidEmailFolder)) {
|
||
|
|
this.navigateTo(`/emails/${folder}`);
|
||
|
|
} else {
|
||
|
|
this.navigateTo('/emails/queued');
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
public getCurrentView(): string {
|
||
|
|
return appstate.uiStatePart.getState().activeView;
|
||
|
|
}
|
||
|
|
|
||
|
|
public getCurrentEmailFolder(): string {
|
||
|
|
return appstate.emailOpsStatePart.getState().currentView;
|
||
|
|
}
|
||
|
|
|
||
|
|
public destroy(): void {
|
||
|
|
this.router.destroy();
|
||
|
|
this.initialized = false;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
export const appRouter = new AppRouter();
|